rtlamr/idm/idm.go
bemasher 42ceb2cce7 Remove FastMag
Simulation shows that FastMag is of roughly equivalent sensitivity to
MagLUT while not providing much better performance.
2015-07-11 01:36:07 -06:00

216 lines
7.1 KiB
Go

// 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 idm
import (
"encoding/binary"
"fmt"
"strconv"
"strings"
"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 = 32
cfg.PacketSymbols = 92 * 8
cfg.Preamble = "01010101010101010001011010100011"
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 packet is too short, bail.
if l := len(data.Bytes); l != 92 {
continue
}
// If the checksum fails, bail.
if residue := p.Checksum(data.Bytes[4:92]); residue != p.Residue {
continue
}
idm := NewIDM(data)
// If the meter id is 0, bail.
if idm.ERTSerialNumber == 0 {
continue
}
msgs = append(msgs, idm)
}
return
}
// Standard Consumption Message
type IDM struct {
Preamble uint32 // Training and Frame sync.
PacketTypeID uint8
PacketLength uint8 // Packet Length MSB
HammingCode uint8 // Packet Length LSB
ApplicationVersion uint8
ERTType uint8
ERTSerialNumber uint32
ConsumptionIntervalCount uint8
ModuleProgrammingState uint8
TamperCounters []byte // 6 Bytes
AsynchronousCounters uint16
PowerOutageFlags []byte // 6 Bytes
LastConsumptionCount uint32
DifferentialConsumptionIntervals Interval // 53 Bytes
TransmitTimeOffset uint16
SerialNumberCRC uint16
PacketCRC uint16
}
func NewIDM(data parse.Data) (idm IDM) {
idm.Preamble = binary.BigEndian.Uint32(data.Bytes[0:4])
idm.PacketTypeID = data.Bytes[4]
idm.PacketLength = data.Bytes[5]
idm.HammingCode = data.Bytes[6]
idm.ApplicationVersion = data.Bytes[7]
idm.ERTType = data.Bytes[8] & 0x0F
idm.ERTSerialNumber = binary.BigEndian.Uint32(data.Bytes[9:13])
idm.ConsumptionIntervalCount = data.Bytes[13]
idm.ModuleProgrammingState = data.Bytes[14]
idm.TamperCounters = data.Bytes[15:21]
idm.AsynchronousCounters = binary.BigEndian.Uint16(data.Bytes[21:23])
idm.PowerOutageFlags = data.Bytes[23:29]
idm.LastConsumptionCount = binary.BigEndian.Uint32(data.Bytes[29:33])
offset := 264
for idx := range idm.DifferentialConsumptionIntervals {
interval, _ := strconv.ParseUint(data.Bits[offset:offset+9], 2, 9)
idm.DifferentialConsumptionIntervals[idx] = uint16(interval)
offset += 9
}
idm.TransmitTimeOffset = binary.BigEndian.Uint16(data.Bytes[86:88])
idm.SerialNumberCRC = binary.BigEndian.Uint16(data.Bytes[88:90])
idm.PacketCRC = binary.BigEndian.Uint16(data.Bytes[90:92])
return
}
type Interval [47]uint16
func (interval Interval) Record() (r []string) {
for _, val := range interval {
r = append(r, strconv.FormatUint(uint64(val), 10))
}
return
}
func (idm IDM) MsgType() string {
return "IDM"
}
func (idm IDM) MeterID() uint32 {
return idm.ERTSerialNumber
}
func (idm IDM) MeterType() uint8 {
return idm.ERTType
}
func (idm IDM) Checksum() []byte {
checksum := make([]byte, 2)
binary.BigEndian.PutUint16(checksum, idm.PacketCRC)
return checksum
}
func (idm IDM) String() string {
var fields []string
fields = append(fields, fmt.Sprintf("Preamble:0x%08X", idm.Preamble))
fields = append(fields, fmt.Sprintf("PacketTypeID:0x%02X", idm.PacketTypeID))
fields = append(fields, fmt.Sprintf("PacketLength:0x%02X", idm.PacketLength))
fields = append(fields, fmt.Sprintf("HammingCode:0x%02X", idm.HammingCode))
fields = append(fields, fmt.Sprintf("ApplicationVersion:0x%02X", idm.ApplicationVersion))
fields = append(fields, fmt.Sprintf("ERTType:0x%02X", idm.ERTType))
fields = append(fields, fmt.Sprintf("ERTSerialNumber:% 10d", idm.ERTSerialNumber))
fields = append(fields, fmt.Sprintf("ConsumptionIntervalCount:%d", idm.ConsumptionIntervalCount))
fields = append(fields, fmt.Sprintf("ModuleProgrammingState:0x%02X", idm.ModuleProgrammingState))
fields = append(fields, fmt.Sprintf("TamperCounters:%02X", idm.TamperCounters))
fields = append(fields, fmt.Sprintf("AsynchronousCounters:0x%02X", idm.AsynchronousCounters))
fields = append(fields, fmt.Sprintf("PowerOutageFlags:%02X", idm.PowerOutageFlags))
fields = append(fields, fmt.Sprintf("LastConsumptionCount:%d", idm.LastConsumptionCount))
fields = append(fields, fmt.Sprintf("DifferentialConsumptionIntervals:%d", idm.DifferentialConsumptionIntervals))
fields = append(fields, fmt.Sprintf("TransmitTimeOffset:%d", idm.TransmitTimeOffset))
fields = append(fields, fmt.Sprintf("SerialNumberCRC:0x%04X", idm.SerialNumberCRC))
fields = append(fields, fmt.Sprintf("PacketCRC:0x%04X", idm.PacketCRC))
return "{" + strings.Join(fields, " ") + "}"
}
func (idm IDM) Record() (r []string) {
r = append(r, fmt.Sprintf("0x%08X", idm.Preamble))
r = append(r, fmt.Sprintf("0x%02X", idm.PacketTypeID))
r = append(r, fmt.Sprintf("0x%02X", idm.PacketLength))
r = append(r, fmt.Sprintf("0x%02X", idm.HammingCode))
r = append(r, fmt.Sprintf("0x%02X", idm.ApplicationVersion))
r = append(r, fmt.Sprintf("0x%02X", idm.ERTType))
r = append(r, fmt.Sprintf("%d", idm.ERTSerialNumber))
r = append(r, fmt.Sprintf("%d", idm.ConsumptionIntervalCount))
r = append(r, fmt.Sprintf("0x%02X", idm.ModuleProgrammingState))
r = append(r, fmt.Sprintf("%02X", idm.TamperCounters))
r = append(r, fmt.Sprintf("0x%02X", idm.AsynchronousCounters))
r = append(r, fmt.Sprintf("%02X", idm.PowerOutageFlags))
r = append(r, fmt.Sprintf("%d", idm.LastConsumptionCount))
r = append(r, idm.DifferentialConsumptionIntervals.Record()...)
r = append(r, fmt.Sprintf("%d", idm.TransmitTimeOffset))
r = append(r, fmt.Sprintf("0x%04X", idm.SerialNumberCRC))
r = append(r, fmt.Sprintf("0x%04X", idm.PacketCRC))
return
}