From a6286ba9e9035a3832d289e75d5733dba247a183 Mon Sep 17 00:00:00 2001 From: bemasher Date: Thu, 17 Dec 2015 20:30:26 -0700 Subject: [PATCH] Add prototype SCM+ parser. --- flags.go | 2 +- recv.go | 3 + scmplus/scmplus.go | 146 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 scmplus/scmplus.go diff --git a/flags.go b/flags.go index be4b62ab3..dd7a50154 100644 --- a/flags.go +++ b/flags.go @@ -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") diff --git a/recv.go b/recv.go index df5cd1734..36df10dcf 100644 --- a/recv.go +++ b/recv.go @@ -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" ) @@ -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) } diff --git a/scmplus/scmplus.go b/scmplus/scmplus.go new file mode 100644 index 000000000..2052f61e9 --- /dev/null +++ b/scmplus/scmplus.go @@ -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 . + +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 +}