Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SCM+ parser. #42

Merged
merged 1 commit into from
Dec 21, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}