Initial Commit

This commit is contained in:
2018-08-27 21:22:50 -07:00
commit 15546c9782
137 changed files with 10509 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
#===========================================================================
#
# RainForest Eagle Electric meter reading package
#
#===========================================================================
__doc__ = """RainForest Eagle electric meter reader.
This package implements a web server which the RainForest Eagle can use as a cloud service. The Eagle will post data to the this module which parses the XML messages and sends them out as ZeroMQ messages (usually to a tHome.msgHub).
Logging object name: tHome.eagle
"""
#===========================================================================
#===========================================================================
from . import config
from . import get
from . import messages
from .parse import parse
#===========================================================================

View File

@@ -0,0 +1,36 @@
#===========================================================================
#
# Config file
#
#===========================================================================
__doc__ = """Config file parsing.
"""
from .. import util
from ..util import config as C
#===========================================================================
# Config file section name and defaults.
configEntries = [
# ( name, converter function, default value )
C.Entry( "httpPort", int, 22042 ),
C.Entry( "mqttEnergy", str ),
C.Entry( "mqttPower", str ),
C.Entry( "logFile", util.path.expand ),
C.Entry( "logLevel", int, 20 ), # INFO
]
#===========================================================================
def parse( configDir, configFile='eagle.py' ):
return C.readAndCheck( configDir, configFile, configEntries )
#===========================================================================
def log( config, logFile=None ):
if not logFile:
logFile = config.logFile
return util.log.get( "eagle", config.logLevel, logFile )
#===========================================================================

View File

@@ -0,0 +1,110 @@
from . import config
from . import messages as msg
#from . import convert
#from .DeviceData import DeviceData
#from .DeviceInfo import DeviceInfo
#from .InstantDemand import InstantDemand
#from .Reading import Reading
#from .Total import Total
import xml.etree.ElementTree as ET
import socket
#==========================================================================
def all():
# Newlines are required
xmlCmd = "<LocalCommand>\n<Name>get_device_data</Name>\n" \
"<MacId>%s</MacId>\n</LocalCommand>\n" % ( config.macAddress )
xmlData = sendXml( xmlCmd )
# Add fake wrapper for parsing list of elements
xmlData = "<root>%s</root>" % xmlData
root = ET.fromstring( xmlData )
return DeviceData( root )
#==========================================================================
def device():
# Newlines are required
xmlCmd = "<LocalCommand>\n<Name>list_devices</Name>\n</LocalCommand>\n"
xmlData = sendXml( xmlCmd )
root = ET.fromstring( xmlData )
return msg.DeviceInfo( root )
#==========================================================================
def instant():
# Newlines are required
xmlCmd = "<LocalCommand>\n<Name>get_instantaneous_demand</Name>\n" \
"<MacId>%s</MacId>\n</LocalCommand>\n" % ( config.macAddress )
xmlData = sendXml( xmlCmd )
root = ET.fromstring( xmlData )
return msg.InstantaneousDemand( root )
#==========================================================================
def history( start ):
"start == datetime in utc"
startHex = convert.fromTime( start )
# Newlines are required
xmlCmd = "<LocalCommand>\n<Name>get_history_data</Name>\n" \
"<MacId>%s</MacId>\n<StartTime>%s</StartTime>\n" \
"</LocalCommand>\n" % ( config.macAddress, startHex )
xmlData = sendXml( xmlCmd )
# Add fake wrapper for parsing list of elements
root = ET.fromstring( xmlData )
return [ Total( child ) for child in root ]
#==========================================================================
def instantHistory( interval ):
"interval = 'hour', 'day', 'week'"
assert( interval in [ 'hour', 'day', 'week' ] )
# Newlines are required
xmlCmd = "<LocalCommand>\n<Name>get_demand_values</Name>\n" \
"<MacId>%s</MacId>\n</LocalCommand>\n" % ( config.macAddress )
xmlData = sendXml( xmlCmd )
# Add fake wrapper for parsing list of elements
xmlData = "<root>%s</root>" % xmlData
root = ET.fromstring( xmlData )
return msg.Reading.xmlToList( root )
#==========================================================================
def totalHistory( interval ):
"interval = 'day', 'week', 'month', 'year'"
assert( interval in [ 'day', 'week', 'month', 'year' ] )
# Newlines are required
xmlCmd = "<LocalCommand>\n<Name>get_summation_values</Name>\n" \
"<MacId>%s</MacId>\n</LocalCommand>\n" % ( config.macAddress )
xmlData = sendXml( xmlCmd )
# Add fake wrapper for parsing list of elements
xmlData = "<root>%s</root>" % xmlData
root = ET.fromstring( xmlData )
return msg.Reading.xmlToList( root )
#==========================================================================
def sendXml( xmlCmd ):
sock = socket.create_connection( ( config.host, config.port ) )
try:
sock.send( xmlCmd )
buf = ""
while True:
s = sock.recv( 1024 )
if not s:
break
buf += s
finally:
sock.close()
return buf
#==========================================================================

View File

@@ -0,0 +1,70 @@
#===========================================================================
#
# Base class for messages
#
#===========================================================================
import datetime
from . import convert
#==========================================================================
class Base:
def __init__( self, name, node=None ):
self.name = name
# Copy the attributes.
if node is not None:
for child in node:
setattr( self, child.tag, child.text )
# Convert hex values to int and float.
convert.hexKeys( self, self._numHexKeys, float )
convert.hexKeys( self, self._intHexKeys, int )
#------------------------------------------------------------------------
def msgDict( self ):
assert( self._jsonKeys )
msg = {}
for key in self._jsonKeys:
if hasattr( self, key ):
msg[key] = getattr( self, key )
if hasattr( self, "TimeUnix" ):
msg["Time"] = self.TimeUnix
return msg
#------------------------------------------------------------------------
def _format( self, indent=3 ):
i = " "*indent
s = "%s(\n" % self.name
for key in dir( self ):
if key[0] == "_":
continue
v = getattr( self, key )
if callable( v ):
continue
if hasattr( v, "_format" ):
s += "%s%s : %s,\n" % ( i, key, v._format( indent+3 ) )
elif isinstance( v, str ):
s += "%s%s : '%s',\n" % ( i, key, v )
else:
s += "%s%s : %s,\n" % ( i, key, v )
s += "%s)" % i
return s
#------------------------------------------------------------------------
def __str__( self ):
return self._format()
def __repr__( self ):
return self.__str__()
#------------------------------------------------------------------------
#==========================================================================

View File

@@ -0,0 +1,87 @@
#===========================================================================
#
# BlockPriceDetail Message
#
#===========================================================================
from .Base import Base
from . import convert
#==========================================================================
class BlockPriceDetail ( Base ):
"""Block price detail message
After construction, will have the following attributes:
DeviceMacId int
MeterMacId int
TimeStamp float (UTC sec past 1-JAN-2000 00:00)
CurrentStart
CurrentDuration
BlockPeriodConsumption
BlockPeriodConsumptionMultiplier
BlockPeriodConsumptionDivisor
NumberOfBlocks
Multiplier
Divisor
Currency
TrailingDigits
Time datetime UTC time stamp
TimeUnix float (UTC sec past 1-JAN-1970 00:00)
Consumption
Sample:
<BlockPriceDetail>
<DeviceMacId>0xd8d5b9000000103f</DeviceMacId>
<MeterMacId>0x000781000086d0fe</MeterMacId>
<TimeStamp>0x1c531d6b</TimeStamp>
<CurrentStart>0x00000000</CurrentStart>
<CurrentDuration>0x0000</CurrentDuration>
<BlockPeriodConsumption>0x0000000000231c38</BlockPeriodConsumption>
<BlockPeriodConsumptionMultiplier>0x00000001</BlockPeriodConsumptionMultiplier>
<BlockPeriodConsumptionDivisor>0x000003e8</BlockPeriodConsumptionDivisor>
<NumberOfBlocks>0x00</NumberOfBlocks>
<Multiplier>0x00000001</Multiplier>
<Divisor>0x00000001</Divisor>
<Currency>0x0348</Currency>
<TrailingDigits>0x00</TrailingDigits>
</BlockPriceDetail>
"""
# Hex keys turn into floats or ints. Taken care of automatically
# in Base.__init__().
_intHexKeys = [ "DeviceMacId", "MeterMacId", "NumberOfBlocks", "Currency",
"TrailingDigits", ]
_numHexKeys = [ "TimeStamp", "CurrentStart", "CurrentDuration",
"BlockPeriodConsumption", "BlockPeriodConsumptionMultiplier",
"BlockPeriodConsumptionDivisor", "Multiplier", "Divisor" ]
_jsonKeys = [ "DeviceMacid", "MeterMacId", "Consumption" ]
#------------------------------------------------------------------------
def __init__( self, node ):
assert( node.tag == "BlockPriceDetail" )
Base.__init__( self, "BlockPriceDetail", node )
# Convert a 0 value to 1 (special case).
convert.zeroToOne( self, [ "Multiplier", "Divisor" ] )
convert.zeroToOne( self, [ "BlockPeriodConsumptionMultiplier",
"BlockPeriodConsumptionDivisor" ] )
self.Consumption = convert.toValue( self.BlockPeriodConsumption,
self.BlockPeriodConsumptionMultiplier,
self.BlockPeriodConsumptionDivisor )
convert.time( self, "Time", "TimeUnix", self.TimeStamp )
#------------------------------------------------------------------------
def jsonMsg( self ):
return {
"MacId" : self.DeviceMacId,
"Time" : self.TimeUnix,
"Consumption" : self.Consumption,
}
#==========================================================================

View File

@@ -0,0 +1,78 @@
#===========================================================================
#
# CurrentSummation Message
#
#===========================================================================
from .Base import Base
from . import convert
#==========================================================================
class CurrentSummation ( Base ):
"""Current summation message
After construction, will have the following attributes:
DeviceMacId int
MeterMacId int
TimeStamp float (UTC sec past 1-JAN-2000 00:00)
SummationDelivered float (utility->user)
SummationReceived float (user->utility)
Multiplier float
Divisor float
DigitsRight int
DigitsLeft int
SuppressLeadingZero str
Consumed float in kWh (utility->user)
Produced float in kWh (user->utility)
Time datetime UTC time stamp
TimeUnix float (UTC sec past 1-JAN-1970 00:00)
Sample:
NOTE: Socket API uses CurrentSummation, uploader (cloud) API uses
CurrentSummationDelivered. Both are fine.
<CurrentSummationDelivered>
<DeviceMacId>0xd8d5b9000000103f</DeviceMacId>
<MeterMacId>0x000781000086d0fe</MeterMacId>
<TimeStamp>0x1c531e54</TimeStamp>
<SummationDelivered>0x0000000001321a5f</SummationDelivered>
<SummationReceived>0x00000000003f8240</SummationReceived>
<Multiplier>0x00000001</Multiplier>
<Divisor>0x000003e8</Divisor>
<DigitsRight>0x01</DigitsRight>
<DigitsLeft>0x06</DigitsLeft>
<SuppressLeadingZero>Y</SuppressLeadingZero>
</CurrentSummationDelivered>
"""
# Hex keys turn into floats or ints. Taken care of automatically
# in Base.__init__().
_intHexKeys = [ "DeviceMacId", "MeterMacId", "DigitsRight", "DigitsLeft" ]
_numHexKeys = [ "TimeStamp", "SummationDelivered", "SummationReceived",
"Multiplier", "Divisor" ]
_jsonKeys = [ "DeviceMacid", "MeterMacId", "Consumed", "Produced" ]
#------------------------------------------------------------------------
def __init__( self, node ):
assert( node.tag == "CurrentSummation" or
node.tag == "CurrentSummationDelivered" )
Base.__init__( self, "CurrentSummation", node )
# Convert a 0 value to 1 (special case).
convert.zeroToOne( self, [ "Multiplier", "Divisor" ] )
self.Consumed = convert.toValue( self.SummationDelivered,
self.Multiplier, self.Divisor )
self.Produced = convert.toValue( self.SummationReceived,
self.Multiplier, self.Divisor )
convert.time( self, "Time", "TimeUnix", self.TimeStamp )
#------------------------------------------------------------------------
#==========================================================================

View File

@@ -0,0 +1,59 @@
#===========================================================================
#
# DeviceInfo Message
#
#===========================================================================
from .Base import Base
#==========================================================================
class DeviceInfo ( Base ):
"""Network info message
After construction, will have the following attributes:
DeviceMacId int
InstallCode int
LinkKey int
FWVersion str
HWVersion str
ImageType int
Manufacturer str
ModelId str
DateCode str
Port str
Sample:
<DeviceInfo>
<DeviceMacId>0xd8d5b9000000103f</DeviceMacId>
<InstallCode>0x8ba7f1dee6c4f5cc</InstallCode>
<LinkKey>0x2b26f9124113b1e2b317d402ed789a47</LinkKey>
<FWVersion>1.4.47 (6798)</FWVersion>
<HWVersion>1.2.3</HWVersion>
<ImageType>0x1301</ImageType>
<Manufacturer>Rainforest Automation, Inc.</Manufacturer>
<ModelId>Z109-EAGLE</ModelId>
<DateCode>2013103023220630</DateCode>
<Port>/dev/ttySP0</Port>
</DeviceInfo>
"""
# Hex keys turn into floats or ints. Taken care of automatically
# in Base.__init__().
_numHexKeys = []
_intHexKeys = [ "DeviceMacId", "InstallCode", "LinkKey", "ImageType" ]
_jsonKeys = [ "DeviceMacid", "InstallCode", "LinkKey", "FWVersion",
"HWVersion", "ImageType", "Manufacturer", "ModelId",
"DateCode", "Port" ]
#------------------------------------------------------------------------
def __init__( self, node ):
assert( node.tag == "DeviceInfo" )
Base.__init__( self, "DeviceInfo", node )
#------------------------------------------------------------------------
#==========================================================================

View File

@@ -0,0 +1,53 @@
#===========================================================================
#
# FastPollStatus Message
#
#===========================================================================
from .Base import Base
from . import convert
#==========================================================================
class FastPollStatus ( Base ):
"""Fast polling status message
After construction, will have the following attributes:
DeviceMacId int
CoordMacId int
Frequency float (sec)
EndTime float (UTC sec past 1-JAN-2000 00:00)
End datetime UTC time stamp
EndUnix float (UTC sec past 1-JAN-1970 00:00)
Sample:
<FastPollStatus>
<DeviceMacId>0xd8d5b9000000103f</DeviceMacId>
<MeterMacId>0x000781000086d0fe</MeterMacId>
<Frequency>0x00</Frequency>
<EndTime>0xFFFFFFFF</EndTime>
</FastPollStatus>
"""
# Hex keys turn into floats or ints. Taken care of automatically
# in Base.__init__().
_numHexKeys = [ "Frequency", "EndTime" ]
_intHexKeys = [ "DeviceMacId", "MeterMacId" ]
_jsonKeys = [ "DeviceMacid", "Frequency" ]
#------------------------------------------------------------------------
def __init__( self, node ):
"""node == xml ETree node
"""
assert( node.tag == "FastPollStatus" )
Base.__init__( self, "FastPollStatus", node )
convert.time( self, "End", "EndUnix", self.EndTime )
#------------------------------------------------------------------------
#==========================================================================

View File

@@ -0,0 +1,74 @@
#===========================================================================
#
# InstantaneousDemand Message
#
#===========================================================================
from .Base import Base
from . import convert
#==========================================================================
class InstantaneousDemand ( Base ):
"""Instantaneous demand message
After construction, will have the following attributes:
DeviceMacId int
MeterMacId int
TimeStamp float (UTC sec past 1-JAN-2000 00:00)
Demand float in Watt (may be negative)
Multiplier float
Divisor float
DigitsRight int
DigitsLeft int
SuppressLeadingZero str
Power float in kWatt (may be negative)
Time datetime UTC time stamp
TimeUnix float (UTC sec past 1-JAN-1970 00:00)
Sample:
<InstantaneousDemand>
<DeviceMacId>0xd8d5b9000000103f</DeviceMacId>
<MeterMacId>0x000781000086d0fe</MeterMacId>
<TimeStamp>0x1c531d48</TimeStamp>
<Demand>0x00032d</Demand>
<Multiplier>0x00000001</Multiplier>
<Divisor>0x000003e8</Divisor>
<DigitsRight>0x03</DigitsRight>
<DigitsLeft>0x06</DigitsLeft>
<SuppressLeadingZero>Y</SuppressLeadingZero>
</InstantaneousDemand>
plus:
Time : datetime object
Power : intantaneous power reading
"""
# Hex keys turn into floats or ints. Taken care of automatically
# in Base.__init__().
_intHexKeys = [ "DeviceMacId", "MeterMacId", "DigitsRight", "DigitsLeft" ]
_numHexKeys = [ "Demand", "Multiplier", "Divisor", "TimeStamp" ]
_jsonKeys = [ "DeviceMacid", "MeterMacId", "Power" ]
#------------------------------------------------------------------------
def __init__( self, node ):
assert( node.tag == "InstantaneousDemand" )
Base.__init__( self, "InstantaneousDemand", node )
# Convert a 0 value to 1 (special case).
convert.zeroToOne( self, [ "Multiplier", "Divisor" ] )
# Handle the signed demand field.
self.Demand = convert.toSigned4( self.Demand )
self.Power = convert.toValue( self.Demand, self.Multiplier, self.Divisor )
convert.time( self, "Time", "TimeUnix", self.TimeStamp )
#------------------------------------------------------------------------
#==========================================================================

View File

@@ -0,0 +1,69 @@
#===========================================================================
#
# MessageCluster Message
#
#===========================================================================
from .Base import Base
from . import convert
#==========================================================================
class MessageCluster ( Base ):
"""Message cluster message
After construction, will have the following attributes:
DeviceMacId int
MeterMacId int
TimeStamp float (UTC sec past 1-JAN-2000 00:00)
Id int
Text str
Priority str
StartTime float (UTC sec past 1-JAN-2000 00:00)
Duration float
ConfirmationRequired str
Confirmed str
Queue str
Time datetime UTC time stamp
TimeUnix float (UTC sec past 1-JAN-1970 00:00)
Start datetime UTC time stamp
StartUnix float (UTC sec past 1-JAN-1970 00:00)
Sample:
<MessageCluster>
<DeviceMacId>0xd8d5b9000000103f</DeviceMacId>
<MeterMacId>0x000781000086d0fe</MeterMacId>
<TimeStamp></TimeStamp>
<Id></Id>
<Text></Text>
<Priority></Priority>
<StartTime></StartTime>
<Duration></Duration>
<ConfirmationRequired>N</ConfirmationRequired>
<Confirmed>N</Confirmed>
<Queue>Active</Queue>
</MessageCluster>
"""
# Hex keys turn into floats or ints. Taken care of automatically
# in Base.__init__().
_intHexKeys = [ "DeviceMacId", "MeterMacId", "Id" ]
_numHexKeys = [ "TimeStamp", "StartTime", "Duration" ]
_jsonKeys = [ "DeviceMacid", "MeterMacId", "Id", "Text", "Priority",
"Duration", "StartTime", "Queue" ]
#------------------------------------------------------------------------
def __init__( self, node ):
assert( node.tag == "MessageCluster" )
Base.__init__( self, "MessageCluster", node )
convert.time( self, "Time", "TimeUnix", self.TimeStamp )
convert.time( self, "Start", "StartUnix", self.StartTime )
#------------------------------------------------------------------------
#==========================================================================

View File

@@ -0,0 +1,55 @@
#===========================================================================
#
# MeterInfo Message
#
#===========================================================================
from .Base import Base
#==========================================================================
class MeterInfo ( Base ):
"""Network info message
After construction, will have the following attributes:
DeviceMacId int
CoordMacId int
Type str
Nickname str
Optional:
Account str
Auth str
Host str
Enabled str
Sample:
<MeterInfo>
<DeviceMacId>0xd8d5b9000000103f</DeviceMacId>
<MeterMacId>0x000781000086d0fe</MeterMacId>
<Type>0x0000</Type>
<Nickname></Nickname>
<Account></Account>
<Auth></Auth>
<Host></Host>
<Enabled>Y</Enabled>
</MeterInfo>
"""
# Hex keys turn into floats or ints. Taken care of automatically
# in Base.__init__().
_numHexKeys = []
_intHexKeys = [ "DeviceMacId", "CoordMacId" ]
_jsonKeys = [ "DeviceMacid", "Type", "Enabled" ]
#------------------------------------------------------------------------
def __init__( self, node ):
"""node == xml ETree node
"""
assert( node.tag == "MeterInfo" )
Base.__init__( self, "MeterInfo", node )
#------------------------------------------------------------------------
#==========================================================================

View File

@@ -0,0 +1,63 @@
#===========================================================================
#
# NetworkInfo Message
#
#===========================================================================
from .Base import Base
#==========================================================================
class NetworkInfo ( Base ):
"""Network info message
After construction, will have the following attributes:
DeviceMacId int
CoordMacId int
Status str
LinkStrength int
Optional:
Description str
ExtPanId int
Channel int
ShortAddr int
Sample:
<NetworkInfo>
<DeviceMacId>0xd8d5b9000000103f</DeviceMacId>
<CoordMacId>0x000781000086d0fe</CoordMacId>
<Status>Connected</Status>
<Description>Successfully Joined</Description>
<ExtPanId>0x000781000086d0fe</ExtPanId>
<Channel>20</Channel>
<ShortAddr>0xe1aa</ShortAddr>
<LinkStrength>0x64</LinkStrength>
</NetworkInfo>
"""
# Hex keys turn into floats or ints. Taken care of automatically
# in Base.__init__().
_numHexKeys = []
_intHexKeys = [ "DeviceMacId", "CoordMacId", "ExtPanId", "ShortAddr",
"StatusCode", "LinkStrength" ]
_jsonKeys = [ "DeviceMacid", "Status", "LinkStrength", "Description",
"Channel" ]
#------------------------------------------------------------------------
def __init__( self, node ):
"""node == xml ETree node
"""
assert( node.tag == "NetworkInfo" )
Base.__init__( self, "NetworkInfo", node )
# Convert channel string to integer.
if hasattr( self, "Channel" ):
self.Channel = int( self.Channel )
#------------------------------------------------------------------------
#==========================================================================

View File

@@ -0,0 +1,75 @@
#===========================================================================
#
# Message
#
#===========================================================================
from .Base import Base
from . import convert
#==========================================================================
class PriceCluster ( Base ):
"""Price cluster message
After construction, will have the following attributes:
DeviceMacId int
MeterMacId int
TimeStamp float (UTC sec past 1-JAN-2000 00:00)
Price float
Currency int
TrailingDigits int
Tier int
StartTime float (UTC sec past 1-JAN-2000 00:00)
Duration float
Time datetime UTC time stamp
TimeUnix float (UTC sec past 1-JAN-1970 00:00)
Start datetime UTC time stamp
StartUnix float (UTC sec past 1-JAN-1970 00:00)
Optional:
RateLabel str
TierLabel str
Sample:
<PriceCluster>
<DeviceMacId>0xd8d5b9000000103f</DeviceMacId>
<MeterMacId>0x000781000086d0fe</MeterMacId>
<TimeStamp>0xffffffff</TimeStamp>
<Price>0x0000000e</Price>
<Currency>0x0348</Currency>
<TrailingDigits>0x02</TrailingDigits>
<Tier>0x01</Tier>
<StartTime>0xffffffff</StartTime>
<Duration>0xffff</Duration>
<RateLabel>Tier 1</RateLabel>
</PriceCluster>
"""
# Hex keys turn into floats or ints. Taken care of automatically
# in Base.__init__().
_intHexKeys = [ "DeviceMacId", "MeterMacId", "Currency", "TrailingDigits",
"Tier" ]
_numHexKeys = [ "TimeStamp", "StartTime", "Price", "Duration" ]
_jsonKeys = [ "DeviceMacid", "MeterMacId", "Time", "Price", "Tier" ]
#------------------------------------------------------------------------
def __init__( self, node ):
assert( node.tag == "PriceCluster" )
Base.__init__( self, "PriceCluster", node )
if self.Price == 0xffffffff:
self.Price = 0.0
self.Price = self.Price / 10**self.TrailingDigits
convert.time( self, "Time", "TimeUnix", self.TimeStamp )
convert.time( self, "Start", "StartUnix", self.StartTime )
#------------------------------------------------------------------------
#==========================================================================

View File

@@ -0,0 +1,52 @@
#===========================================================================
#
# Reading Message
#
#===========================================================================
from .Base import Base
from . import convert
#==========================================================================
class Reading ( Base ):
"""Reading message
After construction, will have the following attributes:
Value float
TimeStamp float (UTC sec past 1-JAN-2000 00:00)
Type str
Time datetime UTC time stamp
TimeUnix float (UTC sec past 1-JAN-1970 00:00)
Sample:
<Reading>
<Value>-123.345</Value>
<TimeStamp>0x1c531d48</TimeStamp>
<Type>Summation</Type>
</Reading>
"""
# Hex keys turn into floats or ints. Taken care of automatically
# in Base.__init__().
_intHexKeys = []
_numHexKeys = [ "TimeStamp" ]
_jsonKeys = [ "Value", "Type" ]
#------------------------------------------------------------------------
def __init__( self, node ):
"""node == xml ETree node
"""
assert( node.tag == "Reading" )
Base.__init__( self, "Reading", node )
convert.time( self, "Time", "TimeUnix", self.TimeStamp )
self.Value = float( self.Value )
#------------------------------------------------------------------------
#==========================================================================

View File

@@ -0,0 +1,52 @@
#===========================================================================
#
# ScheduleInfo Message
#
#===========================================================================
from .Base import Base
#==========================================================================
class ScheduleInfo ( Base ):
"""Schedule info message
After construction, will have the following attributes:
DeviceMacId int
MeterMacId int
Mode str
Event str
Frequency float (sec)
Enabled str
Sample:
<ScheduleInfo>
<DeviceMacId>0xd8d5b9000000103f</DeviceMacId>
<MeterMacId>0x000781000086d0fe</MeterMacId>
<Mode>default</Mode>
<Event>message</Event>
<Frequency>0x00000078</Frequency>
<Enabled>Y</Enabled>
</ScheduleInfo>
"""
# Hex keys turn into floats or ints. Taken care of automatically
# in Base.__init__().
_numHexKeys = [ "Frequency" ]
_intHexKeys = [ "DeviceMacId", "MeterMacId" ]
_jsonKeys = [ "DeviceMacid", "MeterMacId", "Mode", "Event", "Frequency",
"Enabled" ]
#------------------------------------------------------------------------
def __init__( self, node ):
"""node == xml ETree node
"""
assert( node.tag == "ScheduleInfo" )
Base.__init__( self, "ScheduleInfo", node )
#------------------------------------------------------------------------
#==========================================================================

View File

@@ -0,0 +1,54 @@
#===========================================================================
#
# TimeCluster Message
#
#===========================================================================
from .Base import Base
from . import convert
#==========================================================================
class TimeCluster ( Base ):
"""Time cluster message
After construction, will have the following attributes:
DeviceMacId int
MeterMacId int
UTCTime float (UTC sec past 1-JAN-2000 00:00)
LocalTime float (Local sec past 1-JAN-2000 00:00)
Time datetime UTC time stamp
TimeUnix float (UTC sec past 1-JAN-1970 00:00)
Local datetime local time stamp
LocalUnix float (local sec past 1-JAN-1970 00:00)
Sample:
<TimeCluster>
<DeviceMacId>0xd8d5b9000000103f</DeviceMacId>
<MeterMacId>0x000781000086d0fe</MeterMacId>
<UTCTime>0x1c531da7</UTCTime>
<LocalTime>0x1c52ad27</LocalTime>
</TimeCluster>
"""
# Hex keys turn into floats or ints. Taken care of automatically
# in Base.__init__().
_intHexKeys = [ "DeviceMacId", "MeterMacId" ]
_numHexKeys = [ "UTCTime", "LocalTime" ]
_jsonKeys = [ "DeviceMacid", "MeterMacId", "LocalUnix" ]
#------------------------------------------------------------------------
def __init__( self, node ):
assert( node.tag == "TimeCluster" )
Base.__init__( self, "TimeCluster", node )
convert.time( self, "Time", "TimeUnix", self.UTCTime )
convert.time( self, "Local", "LocalUnix", self.LocalTime )
#------------------------------------------------------------------------
#==========================================================================

View File

@@ -0,0 +1,49 @@
#===========================================================================
#
# RainForest Eagle XML messages
#
#===========================================================================
__doc__ = """Decodes Eagle XML messages.
One class per message type. Use the eagle.parse() function to do the
conversions.
"""
#===========================================================================
from . import convert
from .Base import Base
from .BlockPriceDetail import BlockPriceDetail
from .CurrentSummation import CurrentSummation
from .DeviceInfo import DeviceInfo
from .FastPollStatus import FastPollStatus
from .InstantaneousDemand import InstantaneousDemand
from .MessageCluster import MessageCluster
from .MeterInfo import MeterInfo
from .NetworkInfo import NetworkInfo
from .PriceCluster import PriceCluster
from .Reading import Reading
from .ScheduleInfo import ScheduleInfo
from .TimeCluster import TimeCluster
#===========================================================================
# Map XML names to class names.
tagMap = {
"BlockPriceDetail" : BlockPriceDetail,
"CurrentSummation" : CurrentSummation, # socket API
"CurrentSummationDelivered" : CurrentSummation, # cloud API
"DeviceInfo" : DeviceInfo,
"FastPollStatus" : FastPollStatus,
"InstantaneousDemand" : InstantaneousDemand,
"MessageCluster" : MessageCluster,
"MeterInfo" : MeterInfo,
"NetworkInfo" : NetworkInfo,
"PriceCluster" : PriceCluster,
"Reading" : Reading,
"ScheduleInfo" : ScheduleInfo,
"TimeCluster" : TimeCluster,
}
#===========================================================================

View File

@@ -0,0 +1,80 @@
#===========================================================================
#
# Field conversion utilities.
#
#===========================================================================
import datetime
#==========================================================================
# Eagle reference date as a datetime object.
eagleT0 = datetime.datetime( 2000, 1, 1 )
# Delta in seconds between Eagle ref and UNIX ref time.
eagleT0_unixT0 = ( eagleT0 - datetime.datetime( 1970, 1, 1 ) ).total_seconds()
#==========================================================================
def hexKeys( obj, keyList, cvtFunc ):
for key in keyList:
strVal = getattr( obj, key, None )
if strVal is None:
continue
intVal = int( strVal, 16 )
setattr( obj, key, cvtFunc( intVal ) )
#==========================================================================
def time( obj, timeKey, unixKey, eagleSec ):
timeValue = None
unixValue = None
if eagleSec:
timeValue = toDateTime( eagleSec )
unixValue = toUnixTime( eagleSec )
setattr( obj, timeKey, timeValue )
setattr( obj, unixKey, unixValue )
#==========================================================================
def zeroToOne( obj, keyList ):
for key in keyList:
val = getattr( obj, key )
if not val:
setattr( obj, key, 1.0 )
#==========================================================================
def toValue( value, multiplier, divisor ):
return float( value ) * multiplier / divisor
#==========================================================================
def toSigned4( value ):
if value > 0x7FFFFFFF:
return value - 0xFFFFFFFF
return value
#==========================================================================
def toUnixTime( eagleSec ):
"""Input is EAGLE UTC seconds past 00:00:00 1-jan-2000
Returns a float of UTC seconds past UTC 1-jan-1970.
"""
return eagleSec + eagleT0_unixT0
#==========================================================================
def toDateTime( eagleSec ):
"""Input is EAGLE UTC seconds past 00:00:00 1-jan-2000
Returns a datetime object
"""
return eagleT0 + datetime.timedelta( 0, float( eagleSec ) )
#==========================================================================
def fromTime( dateTime ):
"datetime object MUST be utc"
dt = dateTime - eagleT0
isec = int( dt.total_seconds() )
return hex( isec )
#==========================================================================

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env python
import xml.etree.ElementTree as ET
import tHome.eagle as E
s="""
<BlockPriceDetail>
<DeviceMacId>0xd8d5b9000000103f</DeviceMacId>
<MeterMacId>0x000781000086d0fe</MeterMacId>
<TimeStamp>0x1c531d6b</TimeStamp>
<CurrentStart>0x00000000</CurrentStart>
<CurrentDuration>0x0000</CurrentDuration>
<BlockPeriodConsumption>0x0000000000231c38</BlockPeriodConsumption>
<BlockPeriodConsumptionMultiplier>0x00000001</BlockPeriodConsumptionMultiplier>
<BlockPeriodConsumptionDivisor>0x000003e8</BlockPeriodConsumptionDivisor>
<NumberOfBlocks>0x00</NumberOfBlocks>
<Multiplier>0x00000001</Multiplier>
<Divisor>0x00000001</Divisor>
<Currency>0x0348</Currency>
<TrailingDigits>0x00</TrailingDigits>
</BlockPriceDetail>
"""
root = ET.fromstring( s )
n = E.messages.BlockPriceDetail( root )
print n

View File

@@ -0,0 +1,26 @@
#!/usr/bin/env python
import xml.etree.ElementTree as ET
import tHome.eagle as E
s="""
<CurrentSummationDelivered>
<DeviceMacId>0xd8d5b9000000103f</DeviceMacId>
<MeterMacId>0x000781000086d0fe</MeterMacId>
<TimeStamp>0x1c531e54</TimeStamp>
<SummationDelivered>0x0000000001321a5f</SummationDelivered>
<SummationReceived>0x00000000003f8240</SummationReceived>
<Multiplier>0x00000001</Multiplier>
<Divisor>0x000003e8</Divisor>
<DigitsRight>0x01</DigitsRight>
<DigitsLeft>0x06</DigitsLeft>
<SuppressLeadingZero>Y</SuppressLeadingZero>
</CurrentSummationDelivered>
"""
root = ET.fromstring( s )
n = E.messages.CurrentSummation( root )
print n

View File

@@ -0,0 +1,26 @@
#!/usr/bin/env python
import xml.etree.ElementTree as ET
import tHome.eagle as E
s="""
<DeviceInfo>
<DeviceMacId>0xd8d5b9000000103f</DeviceMacId>
<InstallCode>0x8ba7f1dee6c4f5cc</InstallCode>
<LinkKey>0x2b26f9124113b1e2b317d402ed789a47</LinkKey>
<FWVersion>1.4.47 (6798)</FWVersion>
<HWVersion>1.2.3</HWVersion>
<ImageType>0x1301</ImageType>
<Manufacturer>Rainforest Automation, Inc.</Manufacturer>
<ModelId>Z109-EAGLE</ModelId>
<DateCode>2013103023220630</DateCode>
<Port>/dev/ttySP0</Port>
</DeviceInfo>
"""
root = ET.fromstring( s )
n = E.messages.DeviceInfo( root )
print n

View File

@@ -0,0 +1,20 @@
#!/usr/bin/env python
import xml.etree.ElementTree as ET
import tHome.eagle as E
s="""
<FastPollStatus>
<DeviceMacId>0xd8d5b9000000103f</DeviceMacId>
<MeterMacId>0x000781000086d0fe</MeterMacId>
<Frequency>0x00</Frequency>
<EndTime>0xFFFFFFFF</EndTime>
</FastPollStatus>
"""
root = ET.fromstring( s )
n = E.messages.FastPollStatus( root )
print n

View File

@@ -0,0 +1,25 @@
#!/usr/bin/env python
import xml.etree.ElementTree as ET
import tHome.eagle as E
s="""
<InstantaneousDemand>
<DeviceMacId>0xd8d5b9000000103f</DeviceMacId>
<MeterMacId>0x000781000086d0fe</MeterMacId>
<TimeStamp>0x1c531d48</TimeStamp>
<Demand>0x00032d</Demand>
<Multiplier>0x00000001</Multiplier>
<Divisor>0x000003e8</Divisor>
<DigitsRight>0x03</DigitsRight>
<DigitsLeft>0x06</DigitsLeft>
<SuppressLeadingZero>Y</SuppressLeadingZero>
</InstantaneousDemand>
"""
root = ET.fromstring( s )
n = E.messages.InstantaneousDemand( root )
print n

View File

@@ -0,0 +1,27 @@
#!/usr/bin/env python
import xml.etree.ElementTree as ET
import tHome.eagle as E
s="""
<MessageCluster>
<DeviceMacId>0xd8d5b9000000103f</DeviceMacId>
<MeterMacId>0x000781000086d0fe</MeterMacId>
<TimeStamp></TimeStamp>
<Id></Id>
<Text></Text>
<Priority></Priority>
<StartTime></StartTime>
<Duration></Duration>
<ConfirmationRequired>N</ConfirmationRequired>
<Confirmed>N</Confirmed>
<Queue>Active</Queue>
</MessageCluster>
"""
root = ET.fromstring( s )
n = E.messages.MessageCluster( root )
print n

View File

@@ -0,0 +1,24 @@
#!/usr/bin/env python
import xml.etree.ElementTree as ET
import tHome.eagle as E
s="""
<MeterInfo>
<DeviceMacId>0xd8d5b9000000103f</DeviceMacId>
<MeterMacId>0x000781000086d0fe</MeterMacId>
<Type>0x0000</Type>
<Nickname></Nickname>
<Account></Account>
<Auth></Auth>
<Host></Host>
<Enabled>Y</Enabled>
</MeterInfo>
"""
root = ET.fromstring( s )
n = E.messages.MeterInfo( root )
print n

View File

@@ -0,0 +1,24 @@
#!/usr/bin/env python
import xml.etree.ElementTree as ET
import tHome.eagle as E
s="""
<NetworkInfo>
<DeviceMacId>0xd8d5b9000000103f</DeviceMacId>
<CoordMacId>0x000781000086d0fe</CoordMacId>
<Status>Connected</Status>
<Description>Successfully Joined</Description>
<ExtPanId>0x000781000086d0fe</ExtPanId>
<Channel>20</Channel>
<ShortAddr>0xe1aa</ShortAddr>
<LinkStrength>0x64</LinkStrength>
</NetworkInfo>
"""
root = ET.fromstring( s )
n = E.messages.NetworkInfo( root )
print n

View File

@@ -0,0 +1,26 @@
#!/usr/bin/env python
import xml.etree.ElementTree as ET
import tHome.eagle as E
s="""
<PriceCluster>
<DeviceMacId>0xd8d5b9000000103f</DeviceMacId>
<MeterMacId>0x000781000086d0fe</MeterMacId>
<TimeStamp>0xffffffff</TimeStamp>
<Price>0x0000000e</Price>
<Currency>0x0348</Currency>
<TrailingDigits>0x02</TrailingDigits>
<Tier>0x01</Tier>
<StartTime>0xffffffff</StartTime>
<Duration>0xffff</Duration>
<RateLabel>Tier 1</RateLabel>
</PriceCluster>
"""
root = ET.fromstring( s )
n = E.messages.PriceCluster( root )
print n

View File

@@ -0,0 +1,19 @@
#!/usr/bin/env python
import xml.etree.ElementTree as ET
import tHome.eagle as E
s="""
<Reading>
<Value>-123.345</Value>
<TimeStamp>0x1c531d48</TimeStamp>
<Type>Summation</Type>
</Reading>
"""
root = ET.fromstring( s )
n = E.messages.Reading( root )
print n

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env python
import xml.etree.ElementTree as ET
import tHome.eagle as E
s="""
<ScheduleInfo>
<DeviceMacId>0xd8d5b9000000103f</DeviceMacId>
<MeterMacId>0x000781000086d0fe</MeterMacId>
<Mode>default</Mode>
<Event>message</Event>
<Frequency>0x00000078</Frequency>
<Enabled>Y</Enabled>
</ScheduleInfo>
"""
root = ET.fromstring( s )
n = E.messages.ScheduleInfo( root )
print n

View File

@@ -0,0 +1,20 @@
#!/usr/bin/env python
import xml.etree.ElementTree as ET
import tHome.eagle as E
s="""
<TimeCluster>
<DeviceMacId>0xd8d5b9000000103f</DeviceMacId>
<MeterMacId>0x000781000086d0fe</MeterMacId>
<UTCTime>0x1c531da7</UTCTime>
<LocalTime>0x1c52ad27</LocalTime>
</TimeCluster>
"""
root = ET.fromstring( s )
n = E.messages.TimeCluster( root )
print n

View File

@@ -0,0 +1,26 @@
#===========================================================================
#
# Parse XML messages into an object.
#
#===========================================================================
import xml.etree.ElementTree as ET
from . import messages
#==========================================================================
# <rainForest ...>
# <[Message]>...</[Message]>
# </rainForest>
def parse( xmlText ):
root = ET.fromstring( xmlText )
assert( root.tag == "rainforest" )
child = root[0]
msgClass = messages.tagMap.get( child.tag, None )
if not msgClass:
return None
return msgClass( child )
#==========================================================================