Newer
Older
pyenergenie / src / monitor.py
# monitor.py  27/09/2015  D.J.Whale
#
# Monitor Energine MiHome plugs

# Note, this is *only* a test program, to exercise the lower level code.
# 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.
#
# 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 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.


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 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_loop():
    """Capture any incoming messages and log to CSV file"""

    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 = OpenHEMS.decode(payload)
            except OpenHEMS.OpenHEMSException as e:
                warning("Can't decode payload:" + str(e))
                continue
                      
            OpenHEMS.showMessage(decoded)
            # Any device that reports will be added to the non-persistent directory
            updateDirectory(decoded)
            #trace(decoded)
            logMessage(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: write OpenHEMS.getFromMessage("header_mfrid")
                if decoded["recs"][0]["paramid"] == OpenHEMS.PARAM_JOIN:
                    header    = decoded["header"]
                    mfrid     = header["mfrid"]
                    productid = header["productid"]
                    sensorid  = header["sensorid"]
                    send_join_ack(mfrid, productid, sensorid)



if __name__ == "__main__":
    
    trace("starting monitor tester")
    radio.init()
    OpenHEMS.init(Devices.CRYPT_PID)

    try:
        monitor_loop()

    finally:
        radio.finished()

# END