Started work on OOK support.
Ported C code into radio.py
Split out monitor and switch. Monitor.py is now just a monitor.
Switch.py needs more work to make it just a switch.
legacy.py will be a demo app for legacy (green button) devices.
1 parent f342fe2 commit 8f6cc1c2e9d84a2f1cd1027894e4e203b80119ba
@David Whale David Whale authored on 17 Mar 2016
Showing 4 changed files
View
203
src/energenie/radio.py
[ADDR_FIFOTHRESH, VAL_FIFOTHRESH1], # Condition to start packet transmission: at least one byte in FIFO
[ADDR_OPMODE, MODE_RECEIVER] # Operating mode to Receiver
]
 
config_OOK = [
[ADDR_REGDATAMODUL, VAL_REGDATAMODUL_OOK], # modulation scheme OOK
[ADDR_FDEVMSB, 0], # frequency deviation -> 0kHz
[ADDR_FDEVLSB, 0], # frequency deviation -> 0kHz
[ADDR_FRMSB, VAL_FRMSB433], # carrier freq -> 433.92MHz 0x6C7AE1
[ADDR_FRMID, VAL_FRMID433], # carrier freq -> 433.92MHz 0x6C7AE1
[ADDR_FRLSB, VAL_FRLSB433], # carrier freq -> 433.92MHz 0x6C7AE1
[ADDR_RXBW, VAL_RXBW120], # channel filter bandwidth 120kHz
[ADDR_BITRATEMSB, 0x40], # 1938b/s
[ADDR_BITRATELSB, 0x80], # 1938b/s
#[ADDR_BITRATEMSB, 0x1A], # 4800b/s
#[ADDR_BITRATELSB, 0x0B], # 4800b/s
[ADDR_PREAMBLELSB, 0], # preamble size LSB 3
[ADDR_SYNCCONFIG, VAL_SYNCCONFIG4], # Size of the Synch word = 4 (SyncSize + 1)
[ADDR_SYNCVALUE1, VAL_SYNCVALUE1OOK], # sync value 1
[ADDR_SYNCVALUE2, 0], # sync value 2
[ADDR_SYNCVALUE3, 0], # sync value 3
[ADDR_SYNCVALUE4, 0], # sync value 4
[ADDR_PACKETCONFIG1, VAL_PACKETCONFIG1OOK], # Fixed length, no Manchester coding, OOK
[ADDR_PAYLOADLEN, VAL_PAYLOADLEN_OOK], # Payload Length
[ADDR_FIFOTHRESH, VAL_FIFOTHRESH30], # Condition to start packet transmission: wait for 30 bytes in FIFO
#[ADDR_OPMODE, MODE_TRANSMITER] # Transmitter mode
]
 
 
def HRF_wait_ready():
"""Wait for HRF to be ready after last command"""
HRF_pollreg(ADDR_IRQFLAGS1, MASK_MODEREADY, MASK_MODEREADY)
 
def HRF_config_FSK():
"""Configure HRF for FSK modulation"""
for cmd in config_FSK:
HRF_writereg(cmd[0], cmd[1])
HRF_wait_ready()
 
 
def HRF_config_OOK():
"""Configure HRF for OOK modulation"""
for cmd in config_OOK:
HRF_writereg(cmd[0], cmd[1])
HRF_wait_ready()
 
 
trace(" irqflags2=%s" % hex(reg))
if ((reg & MASK_FIFONOTEMPTY) != 0) or ((reg & MASK_FIFOOVERRUN) != 0):
warning("Failed to send payload to HRF")
 
 
def send_payload_repeat(payload, times=0):
"""Send a payload multiple times"""
 
# 32 bits enclosed in sync words
HRF_pollreg(ADDR_IRQFLAGS1, MASK_MODEREADY|MASK_TXREADY, MASK_MODEREADY|MASK_TXREADY)
 
#write first payload without sync preamble
HRF_writefifo_burst(payload)
 
# preceed all future payloads with a sync-word preamble
if times > 0:
preamble = [0x00,0x80,0x00,0x00,0x00]
preamble_payload = preamble + payload
for i in range(times): # Repeat the message a number of times
HRF_pollreg(ADDR_IRQFLAGS2, MASK_FIFOLEVEL, 0)
HRF_writefifo_burst(preamble_payload)
 
HRF_pollreg(ADDR_IRQFLAGS2, MASK_PACKETSENT, MASK_PACKETSENT) # wait for Packet sent
 
reg = HRF_readreg(ADDR_IRQFLAGS2)
trace(" irqflags2=%s" % hex(reg))
if (reg & (MASK_FIFONOTEMPTY) != 0) or ((reg & MASK_FIFOOVERRUN) != 0):
warning("Failed to send repeated payload to HRF")
 
 
def dumpPayloadAsHex(payload):
length = payload[0]
print(hex(length))
if length+1 != len(payload):
for i in range(1,length+1):
print("[%d] = %s" % (i, hex(payload[i])))
 
 
 
def build_OOK_relay_msg(relayState=False):
"""Temporary test code to prove we can turn the relay on or off"""
#This code does not live in this module, it lives in an EnergenieLegacy codec module
#also there are 4 switches, so should pass in up to 4 relayState values
 
# This generates a 20*4 bit address i.e. 10 bytes
# The number generated is always the same
# Presumably this is the 'Energenie address prefix'
# The switch number is encoded in the payload
# This code looks screwy, but it is correct compared to the C code.
# It's probably doing bit encoding on the fly, manchester or sync bits or something.
# Wait until we get the OOK spec from Energenie to better document this.
 
# Looks like: 0000 00BA gets encoded as:
# 128 64 32 16 8 4 2 1
# 1 0 B B 1 A A 0
 
#payload = []
#for i in range(10):
# j = i + 5
# payload.append(8 + (j&1) * 6 + 128 + (j&2) * 48)
#dumpPayloadAsHex(payload)
#
# 5 6 7 8 9 10 11 12 13 14
# 1(01) 1(10) 1(11) 0(00) 0(01) 0(10) 0(11) 1(00) 1(01) 1(10)
payload = [0x8e, 0xe8, 0xee, 0x88, 0x8e, 0xe8, 0xee, 0x88, 0x8e, 0xe8]
 
if relayState: # ON
# D0=high, D1=high, D2-high, D3=high (S1 on)
# 128 64 32 16 8 4 2 1 128 64 32 16 8 4 2 1
# 1 0 B B 1 A A 0 1 0 B B 1 A A 0
# 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 = 10 11 10 11
payload += [0xEE, 0xEE]
else: # OFF
# D0=high, D1=high, D2=high, D3=low (S1 off)
# 128 64 32 16 8 4 2 1 128 64 32 16 8 4 2 1
# 1 0 B B 1 A A 0 1 0 B B 1 A A 0
# 1 1 1 0 1 1 1 0 1 1 1 0 1 0 0 0 = 10 11 10 00
payload += [0xEE, 0xE8]
 
return payload
 
 
#----- USER API ---------------------------------------------------------------
#
# This is only a first-pass at a user API.
# it might change quite a bit in the second pass.
# The HRF functions are intentionally not used by the caller,
# this allows mock testing, and also for that part to be rewritten in C
# for speed later if required.
 
mode = None
modulation_fsk = None
 
def init():
"""Initialise the module ready for use"""
spi.init_defaults()
trace("######## RESET")
trace("RESET")
spi.reset() # send a hardware reset to ensure radio in clean state
trace("######## END RESET")
 
trace("config FSK")
HRF_config_FSK()
HRF_clear_fifo()
receiver()
 
 
def transmitter():
def modulation(fsk=None, ook=None):
"""Switch modulation, if needed"""
global modulation_fsk
 
# Handle sensible module defaults for earlier versions of user code
if fsk == None and ook == None:
# Force FSK mode
fsk = True
 
if fsk != None and fsk:
if modulation_fsk == None or modulation_fsk == False:
trace("switch to FSK")
HRF_config_FSK()
modulation_fsk = True
 
elif ook != None and ook:
if modulation_fsk == None or modulation_fsk == True:
trace("switch to OOK")
HRF_config_OOK()
modulation_fsk = False
 
 
def transmitter(fsk=None, ook=None):
"""Change into transmitter mode"""
global mode
 
trace("transmitter mode")
modulation(fsk, ook)
HRF_change_mode(MODE_TRANSMITER)
mode = "TRANSMITTER"
HRF_wait_txready()
 
 
def transmit(payload):
"""Transmit a single payload using the present modulation scheme"""
HRF_send_payload(payload)
 
 
def receiver():
def receiver(fsk=None, ook=None):
"""Change into receiver mode"""
global mode
 
trace("receiver mode")
modulation(fsk, ook)
HRF_change_mode(MODE_RECEIVER)
HRF_wait_ready()
mode = "RECEIVER"
 
 
def isReceiveWaiting():
"""Check to see if a payload is waiting in the receive buffer"""
return HRF_check_payload()
 
 
def receive():
"""Receive a single payload from the buffer using the present modulation scheme"""
return HRF_receive_payload()
 
 
def finished():
"""Close the library down cleanly when finished"""
spi.finished()
 
 
 
# END
View
0
■■■■■
src/legacy.py 0 → 100644
View
85
src/monitor.py
# monitor.py 27/09/2015 D.J.Whale
#
# Monitor settings of Energine MiHome plugs
# Monitor Energine MiHome plugs
 
import time
 
from energenie import OpenHEMS, Devices
#TODO would be good to keep recs, but need to iterate through all and key by paramid,
#not as a list index, else merging will be hard.
 
 
SWITCH_MESSAGE = {
"header": {
"mfrid": Devices.MFRID,
"productid": Devices.PRODUCTID_R1_MONITOR_AND_CONTROL,
"encryptPIP": Devices.CRYPT_PIP,
"sensorid": 0 # FILL IN
},
"recs": [
{
"wr": True,
"paramid": OpenHEMS.PARAM_SWITCH_STATE,
"typeid": OpenHEMS.Value.UINT,
"length": 1,
"value": 0 # FILL IN
}
]
}
 
 
JOIN_ACK_MESSAGE = {
"header": {
"mfrid": 0, # FILL IN
"productid": 0, # FILL IN
}
]
}
 
def send_join_ack(mfrid, productid, sensorid):
# send back a JOIN ACK, so that join light stops flashing
response = OpenHEMS.alterMessage(JOIN_ACK_MESSAGE,
header_mfrid=mfrid,
header_productid=productid,
header_sensorid=sensorid)
p = OpenHEMS.encode(response)
radio.transmitter()
radio.transmit(p)
radio.receiver()
 
 
def monitor():
"""Send discovery and monitor messages, and capture any responses"""
"""Capture any incoming messages and log to CSV file"""
 
# Define the schedule of message polling
sendSwitchTimer = Timer(5, 1) # every n seconds offset by initial 1
switch_state = 0 # OFF
radio.receiver()
decoded = None
 
while True:
# See if there is a payload, and if there is, process it
if radio.isReceiveWaiting():
OpenHEMS.showMessage(decoded)
updateDirectory(decoded)
logMessage(decoded)
#TODO: Should remember report time of each device,
#and reschedule command messages to avoid their transmit slot
#making it less likely to miss an incoming message due to
#the radio being in transmit mode
 
# handle messages with zero recs in them silently
#trace(decoded)
if len(decoded["recs"]) == 0:
# handle messages with zero recs in them silently
print("Empty record:%s" % decoded)
else:
# assume only 1 rec in a join, for now
#TODO: write OpenHEMS.getFromMessage("header_mfrid")
if decoded["recs"][0]["paramid"] == OpenHEMS.PARAM_JOIN:
#TODO: write OpenHEMS.getFromMessage("header_mfrid")
# send back a JOIN ACK, so that join light stops flashing
response = OpenHEMS.alterMessage(JOIN_ACK_MESSAGE,
header_mfrid=decoded["header"]["mfrid"],
header_productid=decoded["header"]["productid"],
header_sensorid=decoded["header"]["sensorid"])
p = OpenHEMS.encode(response)
radio.transmitter()
radio.transmit(p)
radio.receiver()
header = decoded["header"]
mfrid = header["mfrid"]
productid = header["productid"]
sensorid = header["sensorid"]
send_join_ack(mfrid, productid, sensorid)
 
if sendSwitchTimer.check() and decoded != None:
request = OpenHEMS.alterMessage(SWITCH_MESSAGE,
header_sensorid=decoded["header"]["sensorid"],
recs_0_value=switch_state)
p = OpenHEMS.encode(request)
radio.transmitter()
radio.transmit(p)
radio.receiver()
switch_state = (switch_state+1) % 2 # toggle
 
 
if __name__ == "__main__":
trace("starting monitor")
View
229
src/switch.py 0 → 100644
# monitor.py 27/09/2015 D.J.Whale
#
# Monitor settings of Energine MiHome plugs
 
import time
 
from energenie import OpenHEMS, Devices
from energenie import radio
from Timer import Timer
import os
 
LOG_FILENAME = "energenie.csv"
 
def warning(msg):
print("warning:%s" % str(msg))
def trace(msg):
print("monitor:%s" % str(msg))
 
log_file = None
 
def logMessage (msg):
HEADINGS = 'timestamp,mfrid,prodid,sensorid,flags,switch,voltage,freq,reactive,real'
 
global log_file
if log_file == None:
if not os.path.isfile(LOG_FILENAME):
log_file = open(LOG_FILENAME, 'w')
log_file.write(HEADINGS + '\n')
else:
log_file = open(LOG_FILENAME, 'a') # append
 
# get the header
header = msg['header']
timestamp = time.time()
mfrid = header['mfrid']
productid = header['productid']
sensorid = header['sensorid']
 
# set defaults for any data that doesn't appear in this message
# but build flags so we know which ones this contains
flags = [0 for i in range(7)]
switch = None
voltage = None
freq = None
reactive = None
real = None
apparent = None
current = None
 
# capture any data that we want
#print(msg)
for rec in msg['recs']:
paramid = rec['paramid']
try:
value = rec['value']
except:
value = None
if paramid == OpenHEMS.PARAM_SWITCH_STATE:
switch = value
flags[0] = 1
elif paramid == OpenHEMS.PARAM_VOLTAGE:
flags[1] = 1
voltage = value
elif paramid == OpenHEMS.PARAM_FREQUENCY:
flags[2] = 1
freq = value
elif paramid == OpenHEMS.PARAM_REACTIVE_POWER:
flags[3] = 1
reactive = value
elif paramid == OpenHEMS.PARAM_REAL_POWER:
flags[4] = 1
real = value
elif paramid == OpenHEMS.PARAM_APPARENT_POWER:
flags[5] = 1
apparent = value
elif paramid == OpenHEMS.PARAM_CURRENT:
flags[6] = 1
current = value
 
# generate a line of CSV
flags = "".join([str(a) for a in flags])
csv = "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s" % (timestamp, mfrid, productid, sensorid, flags, switch, voltage, freq, reactive, real, apparent, current)
log_file.write(csv + '\n')
log_file.flush()
trace(csv) # testing
 
 
#----- TEST APPLICATION -------------------------------------------------------
 
directory = {}
 
def allkeys(d):
result = ""
for k in d:
if len(result) != 0:
result += ','
result += str(k)
return result
 
def updateDirectory(message):
"""Update the local directory with information about this device"""
now = time.time()
header = message["header"]
sensorId = header["sensorid"]
 
if not directory.has_key(sensorId):
# new device discovered
desc = Devices.getDescription(header["mfrid"], header["productid"])
print("ADD device:%s %s" % (hex(sensorId), desc))
directory[sensorId] = {"header": message["header"]}
#trace(allkeys(directory))
 
directory[sensorId]["time"] = now
#TODO would be good to keep recs, but need to iterate through all and key by paramid,
#not as a list index, else merging will be hard.
 
 
SWITCH_MESSAGE = {
"header": {
"mfrid": Devices.MFRID,
"productid": Devices.PRODUCTID_R1_MONITOR_AND_CONTROL,
"encryptPIP": Devices.CRYPT_PIP,
"sensorid": 0 # FILL IN
},
"recs": [
{
"wr": True,
"paramid": OpenHEMS.PARAM_SWITCH_STATE,
"typeid": OpenHEMS.Value.UINT,
"length": 1,
"value": 0 # FILL IN
}
]
}
 
 
JOIN_ACK_MESSAGE = {
"header": {
"mfrid": 0, # FILL IN
"productid": 0, # FILL IN
"encryptPIP": Devices.CRYPT_PIP,
"sensorid": 0 # FILL IN
},
"recs": [
{
"wr": False,
"paramid": OpenHEMS.PARAM_JOIN,
"typeid": OpenHEMS.Value.UINT,
"length": 0
}
]
}
 
 
 
def monitor():
"""Send discovery and monitor messages, and capture any responses"""
 
# Define the schedule of message polling
sendSwitchTimer = Timer(5, 1) # every n seconds offset by initial 1
switch_state = 0 # OFF
radio.receiver()
decoded = None
 
while True:
# See if there is a payload, and if there is, process it
if radio.isReceiveWaiting():
#trace("receiving payload")
payload = radio.receive()
try:
decoded = OpenHEMS.decode(payload)
except OpenHEMS.OpenHEMSException as e:
warning("Can't decode payload:" + str(e))
continue
OpenHEMS.showMessage(decoded)
updateDirectory(decoded)
logMessage(decoded)
#TODO: Should remember report time of each device,
#and reschedule command messages to avoid their transmit slot
#making it less likely to miss an incoming message due to
#the radio being in transmit mode
 
# handle messages with zero recs in them silently
#trace(decoded)
if len(decoded["recs"]) == 0:
print("Empty record:%s" % decoded)
else:
# assume only 1 rec in a join, for now
if decoded["recs"][0]["paramid"] == OpenHEMS.PARAM_JOIN:
#TODO: write OpenHEMS.getFromMessage("header_mfrid")
# send back a JOIN ACK, so that join light stops flashing
response = OpenHEMS.alterMessage(JOIN_ACK_MESSAGE,
header_mfrid=decoded["header"]["mfrid"],
header_productid=decoded["header"]["productid"],
header_sensorid=decoded["header"]["sensorid"])
p = OpenHEMS.encode(response)
radio.transmitter()
radio.transmit(p)
radio.receiver()
 
if sendSwitchTimer.check() and decoded != None:
request = OpenHEMS.alterMessage(SWITCH_MESSAGE,
header_sensorid=decoded["header"]["sensorid"],
recs_0_value=switch_state)
p = OpenHEMS.encode(request)
radio.transmitter()
radio.transmit(p)
radio.receiver()
switch_state = (switch_state+1) % 2 # toggle
 
if __name__ == "__main__":
trace("starting monitor")
radio.init()
OpenHEMS.init(Devices.CRYPT_PID)
 
try:
monitor()
 
finally:
radio.finished()
 
# END