Skip to content

Commit

Permalink
Merge pull request #42 from bemasher/SCM+
Browse files Browse the repository at this point in the history
Add SCM+ parser.
  • Loading branch information
bemasher committed Dec 21, 2015
2 parents 99f5b70 + a6286ba commit 64a0ded
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 1 deletion.
2 changes: 1 addition & 1 deletion flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ var logFile *os.File
var sampleFilename = flag.String("samplefile", os.DevNull, "raw signal dump file")
var sampleFile *os.File

var msgType = flag.String("msgtype", "scm", "message type to receive: scm, idm or r900")
var msgType = flag.String("msgtype", "scm", "message type to receive: scm, idm, r900 or scm+")

var symbolLength = flag.Int("symbollength", 72, "symbol length in samples")

Expand Down
3 changes: 3 additions & 0 deletions recv.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/bemasher/rtlamr/parse"
"github.com/bemasher/rtlamr/r900"
"github.com/bemasher/rtlamr/scm"
"github.com/bemasher/rtlamr/scmplus"
"github.com/bemasher/rtltcp"
)

Expand All @@ -51,6 +52,8 @@ func (rcvr *Receiver) NewReceiver() {
rcvr.p = idm.NewParser(*symbolLength, *decimation)
case "r900":
rcvr.p = r900.NewParser(*symbolLength, *decimation)
case "scm+":
rcvr.p = scmplus.NewParser(*symbolLength, *decimation)
default:
log.Fatalf("Invalid message type: %q\n", *msgType)
}
Expand Down
146 changes: 146 additions & 0 deletions scmplus/scmplus.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// RTLAMR - An rtl-sdr receiver for smart meters operating in the 900MHz ISM band.
// Copyright (C) 2015 Douglas Hall
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package scmplus

import (
"bytes"
"encoding/binary"
"fmt"
"strconv"

"github.com/bemasher/rtlamr/crc"
"github.com/bemasher/rtlamr/decode"
"github.com/bemasher/rtlamr/parse"
)

func NewPacketConfig(symbolLength int) (cfg decode.PacketConfig) {
cfg.CenterFreq = 912600155
cfg.DataRate = 32768
cfg.SymbolLength = symbolLength
cfg.PreambleSymbols = 16
cfg.PacketSymbols = 16 * 8
cfg.Preamble = "0001011010100011"

return
}

type Parser struct {
decode.Decoder
crc.CRC
}

func (p Parser) Dec() decode.Decoder {
return p.Decoder
}

func (p Parser) Cfg() decode.PacketConfig {
return p.Decoder.Cfg
}

func NewParser(symbolLength, decimation int) (p Parser) {
p.Decoder = decode.NewDecoder(NewPacketConfig(symbolLength), decimation)
p.CRC = crc.NewCRC("CCITT", 0xFFFF, 0x1021, 0x1D0F)
return
}

func (p Parser) Parse(indices []int) (msgs []parse.Message) {
seen := make(map[string]bool)

for _, pkt := range p.Decoder.Slice(indices) {
s := string(pkt)
if seen[s] {
continue
}
seen[s] = true

data := parse.NewDataFromBytes(pkt)

// If the checksum fails, bail.
if residue := p.Checksum(data.Bytes[2:]); residue != p.Residue {
continue
}

scm := NewSCM(data)

// If the meter id is 0, bail.
if scm.EndpointID == 0 {
continue
}

msgs = append(msgs, scm)
}

return
}

// Standard Consumption Message Plus
type SCM struct {
FrameSync uint16 `xml:",attr"`
ProtocolID uint8 `xml:",attr"`
EndpointType uint8 `xml:",attr"`
EndpointID uint32 `xml:",attr"`
Consumption uint32 `xml:",attr"`
Tamper uint16 `xml:",attr"`
PacketCRC uint16 `xml:"Checksum,attr",json:"Checksum"`
}

func NewSCM(data parse.Data) (scm SCM) {
binary.Read(bytes.NewReader(data.Bytes), binary.BigEndian, &scm)

return
}

func (scm SCM) MsgType() string {
return "SCM+"
}

func (scm SCM) MeterID() uint32 {
return scm.EndpointID
}

func (scm SCM) MeterType() uint8 {
return scm.EndpointType
}

func (scm SCM) Checksum() []byte {
checksum := make([]byte, 2)
binary.BigEndian.PutUint16(checksum, scm.PacketCRC)
return checksum
}

func (scm SCM) String() string {
return fmt.Sprintf("{ProtocolID:0x%02X EndpointType:0x%02X EndpointID:%10d Consumption:%10d Tamper:0x%04X PacketCRC:0x%04X}",
scm.ProtocolID,
scm.EndpointType,
scm.EndpointID,
scm.Consumption,
scm.Tamper,
scm.PacketCRC,
)
}

func (scm SCM) Record() (r []string) {
r = append(r, "0x"+strconv.FormatUint(uint64(scm.FrameSync), 16))
r = append(r, "0x"+strconv.FormatUint(uint64(scm.ProtocolID), 16))
r = append(r, "0x"+strconv.FormatUint(uint64(scm.EndpointType), 16))
r = append(r, strconv.FormatUint(uint64(scm.EndpointID), 10))
r = append(r, strconv.FormatUint(uint64(scm.Consumption), 10))
r = append(r, "0x"+strconv.FormatUint(uint64(scm.Tamper), 16))
r = append(r, "0x"+strconv.FormatUint(uint64(scm.PacketCRC), 16))

return
}

0 comments on commit 64a0ded

Please sign in to comment.