diff --git a/src/Logger.py b/src/Logger.py new file mode 100644 index 0000000..46c5129 --- /dev/null +++ b/src/Logger.py @@ -0,0 +1,85 @@ +# Logger.py 14/05/2016 D.J.Whale +# +# A simple logger - logs to file. + + +from energenie import OpenThings +import os, time + +LOG_FILENAME = "energenie.csv" +HEADINGS = 'timestamp,mfrid,prodid,sensorid,flags,switch,voltage,freq,reactive,real' + + +log_file = None + +def trace(msg): + print(str(msg)) + + +def logMessage(msg): + 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 == OpenThings.PARAM_SWITCH_STATE: + switch = value + flags[0] = 1 + elif paramid == OpenThings.PARAM_VOLTAGE: + flags[1] = 1 + voltage = value + elif paramid == OpenThings.PARAM_FREQUENCY: + flags[2] = 1 + freq = value + elif paramid == OpenThings.PARAM_REACTIVE_POWER: + flags[3] = 1 + reactive = value + elif paramid == OpenThings.PARAM_REAL_POWER: + flags[4] = 1 + real = value + elif paramid == OpenThings.PARAM_APPARENT_POWER: + flags[5] = 1 + apparent = value + elif paramid == OpenThings.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 + +# END diff --git a/src/energenie/Messages.py b/src/energenie/Messages.py index 7dddd3f..aaac787 100644 --- a/src/energenie/Messages.py +++ b/src/energenie/Messages.py @@ -42,4 +42,16 @@ } +def send_join_ack(radio, mfrid, productid, sensorid): + # send back a JOIN ACK, so that join light stops flashing + response = OpenThings.alterMessage(JOIN_ACK, + header_mfrid=mfrid, + header_productid=productid, + header_sensorid=sensorid) + p = OpenThings.encode(response) + radio.transmitter() + radio.transmit(p) + radio.receiver() + + # END diff --git a/src/energenie/Registry.py b/src/energenie/Registry.py new file mode 100644 index 0000000..eba308b --- /dev/null +++ b/src/energenie/Registry.py @@ -0,0 +1,50 @@ +# Registry.py 14/05/2016 D.J.Whale +# +# A simple registry of connected devices. +# +# NOTE: This is an initial, non persisted implementation only + +import time +import Devices + +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. + + +def size(): + return len(directory) + + +def get_sensorids(): + return directory.keys() + + +def get_info(sensor_id): + return directory[sensor_id] + +# END diff --git a/src/energenie/drv/radio.c b/src/energenie/drv/radio.c index b8ea7ec..495a556 100644 --- a/src/energenie/drv/radio.c +++ b/src/energenie/drv/radio.c @@ -5,6 +5,22 @@ * https://energenie4u.co.uk/index.phpcatalogue/product/ENER314-RT */ +/* TODO + +push the FSK configuration into radio.c +remove radio.py (the old version of the radio interface) +implement FSK transmit in radio.c and test with switch.py and hard coded ID number +radio_modulation fix for FSK +move radio.py modulation to radio2.py modulation as it is better? + +contrive a switch.py that only transmits +hard code device address into the dictionary +disable the call to the receive check +run the code, it should turn the switch on and off repeatedly. + + +*/ + /***** INCLUDES *****/ diff --git a/src/legacy.py b/src/legacy.py index 0e7b7b2..f92e06c 100644 --- a/src/legacy.py +++ b/src/legacy.py @@ -145,6 +145,5 @@ finally: radio.finished() - # END diff --git a/src/monitor.py b/src/monitor.py index 43ed0ee..8ac2945 100644 --- a/src/monitor.py +++ b/src/monitor.py @@ -9,133 +9,21 @@ # However, it will log all messages from MiHome monitor, adaptor plus and house monitor # to a CSV log file, so could be the basis for a non-controlling energy logging app. -import time +from energenie import Registry, Devices, Messages, OpenThings +from energenie import radio -from energenie import OpenThings -from energenie import Devices, Messages, radio -import os - -LOG_FILENAME = "energenie.csv" +import Logger 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 == OpenThings.PARAM_SWITCH_STATE: - switch = value - flags[0] = 1 - elif paramid == OpenThings.PARAM_VOLTAGE: - flags[1] = 1 - voltage = value - elif paramid == OpenThings.PARAM_FREQUENCY: - flags[2] = 1 - freq = value - elif paramid == OpenThings.PARAM_REACTIVE_POWER: - flags[3] = 1 - reactive = value - elif paramid == OpenThings.PARAM_REAL_POWER: - flags[4] = 1 - real = value - elif paramid == OpenThings.PARAM_APPARENT_POWER: - flags[5] = 1 - apparent = value - elif paramid == OpenThings.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. - - -def send_join_ack(mfrid, productid, sensorid): - # send back a JOIN ACK, so that join light stops flashing - response = OpenThings.alterMessage(Messages.JOIN_ACK, - header_mfrid=mfrid, - header_productid=productid, - header_sensorid=sensorid) - p = OpenThings.encode(response) - radio.transmitter() - radio.transmit(p) - radio.receiver() - - def monitor_loop(): """Capture any incoming messages and log to CSV file""" @@ -154,9 +42,9 @@ OpenThings.showMessage(decoded) # Any device that reports will be added to the non-persistent directory - updateDirectory(decoded) + Registry.update(decoded) #trace(decoded) - logMessage(decoded) + Logger.logMessage(decoded) # Process any JOIN messages by sending back a JOIN-ACK to turn the LED off if len(decoded["recs"]) == 0: @@ -170,8 +58,7 @@ mfrid = header["mfrid"] productid = header["productid"] sensorid = header["sensorid"] - send_join_ack(mfrid, productid, sensorid) - + Messages.send_join_ack(radio, mfrid, productid, sensorid) if __name__ == "__main__": diff --git a/src/switch.py b/src/switch.py index f283936..9a19a39 100644 --- a/src/switch.py +++ b/src/switch.py @@ -10,130 +10,88 @@ # Don't expect this to be a good starting point for an application. # Consider waiting for me to finish developing the device object interface first. -import time - -from energenie import OpenThings -from energenie import Devices, Messages, radio +from energenie import Devices, Messages, Registry, OpenThings +from energenie import radio from Timer import Timer + # Increase this if you have lots of switches, so that the receiver has enough # time to receive update messages, otherwise your devices won't make it into # the device directory. TX_RATE = 10 # seconds between each switch change cycle + def warning(msg): print("warning:%s" % str(msg)) + def trace(msg): print("monitor:%s" % str(msg)) #----- TEST APPLICATION ------------------------------------------------------- -directory = {} +def switch_sniff_loop(): + """Listen to sensor messages and add them to the Registry""" -def allkeys(d): - result = "" - for k in d: - if len(result) != 0: - result += ',' - result += str(k) - return result + # See if there is a payload, and if there is, process it + if radio.isReceiveWaiting(): + #trace("receiving payload") + payload = radio.receive() + try: + decoded = OpenThings.decode(payload) + except OpenThings.OpenThingsException as e: + warning("Can't decode payload:" + str(e)) + return - -def updateDirectory(message): - """Update the local directory with information about this device""" - now = time.time() - header = message["header"] - sensorId = header["sensorid"] + OpenThings.showMessage(decoded) + # Any device that reports will be added to the non-persistent directory + Registry.update(decoded) + #trace(decoded) - 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. - - -def send_join_ack(mfrid, productid, sensorid): - # send back a JOIN ACK, so that join light stops flashing - response = OpenThings.alterMessage(Messages.JOIN_ACK, - header_mfrid=mfrid, - header_productid=productid, - header_sensorid=sensorid) - p = OpenThings.encode(response) - radio.transmitter() - radio.transmit(p) - radio.receiver() - - -def switch_loop(): - """Listen to sensor messages, and turn switches on and off every few seconds""" - - # Define the schedule of message polling - sendSwitchTimer = Timer(TX_RATE, 1) # every n seconds offset by initial 1 - switch_state = 0 # OFF - radio.receiver() - - 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 = OpenThings.decode(payload) - except OpenThings.OpenThingsException as e: - warning("Can't decode payload:" + str(e)) - continue - - OpenThings.showMessage(decoded) - # Any device that reports will be added to the non-persistent directory - updateDirectory(decoded) - #trace(decoded) - - # Process any JOIN messages by sending back a JOIN-ACK to turn the LED off - 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: use OpenThings.getFromMessage("header_mfrid") - if decoded["recs"][0]["paramid"] == OpenThings.PARAM_JOIN: - header = decoded["header"] - mfrid = header["mfrid"] - productid = header["productid"] - sensorid = header["sensorid"] - send_join_ack(mfrid, productid, sensorid) - - - # Toggle the switch on all devices in the directory - if len(directory) > 0 and sendSwitchTimer.check(): - print("transmit") - radio.transmitter() - - for sensorid in directory.keys(): - # Only try to toggle the switch for devices that actually have a switch - header = directory[sensorid]["header"] - mfrid = header["mfrid"] + # Process any JOIN messages by sending back a JOIN-ACK to turn the LED off + 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: use OpenThings.getFromMessage("header_mfrid") + if decoded["recs"][0]["paramid"] == OpenThings.PARAM_JOIN: + header = decoded["header"] + mfrid = header["mfrid"] productid = header["productid"] + sensorid = header["sensorid"] + Messages.send_join_ack(radio, mfrid, productid, sensorid) - if Devices.hasSwitch(mfrid, productid): - request = OpenThings.alterMessage(Messages.SWITCH, - header_sensorid=sensorid, - recs_0_value=switch_state) - p = OpenThings.encode(request) - print("Sending switch message to %s %s" % (hex(productid), hex(sensorid))) - # Transmit multiple times, hope one of them gets through - for i in range(4): - radio.transmit(p) - radio.receiver() - print("receive") - switch_state = (switch_state+1) % 2 # toggle +def switch_toggle_loop(): + """Toggle the switch on all devices in the directory""" + + global switch_state + + if len(Registry.size()) > 0 and sendSwitchTimer.check(): + print("transmit") + radio.transmitter() + + for sensorid in Registry.get_sensorids(): + # Only try to toggle the switch for devices that actually have a switch + header = Registry.get_info(sensorid)["header"] + mfrid = header["mfrid"] + productid = header["productid"] + + if Devices.hasSwitch(mfrid, productid): + request = OpenThings.alterMessage(Messages.SWITCH, + header_sensorid=sensorid, + recs_0_value=switch_state) + p = OpenThings.encode(request) + print("Sending switch message to %s %s" % (hex(productid), hex(sensorid))) + # Transmit multiple times, hope one of them gets through + for i in range(4): + radio.transmit(p) + + radio.receiver() + print("receive") + switch_state = (switch_state+1) % 2 # toggle if __name__ == "__main__": @@ -142,8 +100,14 @@ radio.init() OpenThings.init(Devices.CRYPT_PID) + sendSwitchTimer = Timer(TX_RATE, 1) # every n seconds offset by initial 1 + switch_state = 0 # OFF + radio.receiver() + try: - switch_loop() + while True: + switch_sniff_loop() + switch_toggle_loop() finally: radio.finished()