diff --git a/src/energenie/OpenThings.py b/src/energenie/OpenThings.py index 08a64da..08acf97 100644 --- a/src/energenie/OpenThings.py +++ b/src/energenie/OpenThings.py @@ -650,14 +650,17 @@ "mfrid" : None, "productid": None, "sensorid": None - } + }, + "recs":[] } - def __init__(self, pydict=None): + def __init__(self, pydict=None, **kwargs): if pydict == None: pydict = copy.deepcopy(Message.BLANK) self.pydict = pydict + self.set(**kwargs) + @untested def __getitem__(self, key): try: @@ -682,6 +685,79 @@ import copy return Message(copy.deepcopy(self.pydict)) + def set(self, **kwargs): + """Set fields in the message from key value pairs""" + for key in kwargs: + value = kwargs[key] + pathed_key = key.split('_') + m = self.pydict + # walk to the parent + for pkey in pathed_key[:-1]: + # If the key parseable as an integer, use as a list index instead + try: + pkey = int(pkey) + except: + pass + m = m[pkey] + # set the parent to have a key that points to the new value + pkey = pathed_key[-1] + # If the key parseable as an integer, use as a list index instead + try: + pkey = int(pkey) + except: + pass + + # if index exists, change it, else create it + try: + m[pkey] = value + except IndexError: + # expand recs up to pkey + l = len(m) # length of list + gap = (l - pkey)+1 + for i in range(gap): + m.append({}) + m[pkey] = value + + def append_rec(self, *args, **kwargs): + """Add a rec""" + if type(args[0]) == dict: + # This is ({}) + self.pydict["recs"].append(args[0]) + return len(self.pydict["recs"])-1 # index of rec just added + + elif type(args[0]) == int: + if len(kwargs) == 0: + # This is (PARAM_x, pydict) + paramid = args[0] + pydict = args[1] + pydict["paramid"] = paramid + return self.append_rec(pydict) + else: + # This is (PARAM_x, key1=value1, key2=value2) + paramid = args[0] + # build a pydict + pydict = {"paramid":paramid} + for key in kwargs: + value = kwargs[key] + pydict[key] = value + self.append_rec(pydict) + + else: + raise ValueError("Not sure how to parse arguments to append_rec") + + def get(self, keypath): + """READ(GET) from a single keypathed entry""" + path = keypath.split("_") + m = self.pydict + # walk to the final item + for pkey in path: + try: + pkey = int(pkey) + except: + pass + m = m[pkey] + return m + def __str__(self): # -> str return "Message.STR" @@ -699,18 +775,17 @@ # HEADER if "header" in msg: - header = msg["header"] - mfrid = header["mfrid"] - if mfrid == None: mfrid = "" - else: mfrid = str(hex(mfrid)) + header = msg["header"] - productid = header["productid"] - if productid == None: productid = "" - else: productid = str(hex(productid)) + def gethex(key): + if key in header: + value = header[key] + if value != None: return str(hex(value)) + return "" - sensorid = header["sensorid"] - if sensorid == None: sensorid = "" - else: sensorid = str(hex(sensorid)) + mfrid = gethex("mfrid") + productid = gethex("productid") + sensorid = gethex("sensorid") print("mfrid:%s prodid:%s sensorid:%s" % (mfrid, productid, sensorid)) @@ -724,19 +799,19 @@ write = "read " try: - paramname = rec["paramname"] # This only come out from decoded messages + paramname = rec["paramname"] #NOTE: This only comes out from decoded messages except: paramname = "" try: - paramid = rec["paramid"] #This is only present on a input message (e.g SWITCH) + paramid = rec["paramid"] #NOTE: This is only present on a input message (e.g SWITCH) paramname = paramid_to_paramname(paramid) paramid = str(hex(paramid)) except: paramid = "" try: - paramunit = rec["paramunit"] # This only come out from decoded messages + paramunit = rec["paramunit"] #NOTE: This only comes out from decoded messages except: paramunit = "" @@ -748,7 +823,6 @@ print("%s %s %s %s = %s" % (write, paramid, paramname, paramunit, str(value))) - @deprecated def showMessage(msg, timestamp=None): """Show the message in a friendly format""" diff --git a/src/energenie/OpenThings_test.py b/src/energenie/OpenThings_test.py index 05153d6..76f1d5d 100644 --- a/src/energenie/OpenThings_test.py +++ b/src/energenie/OpenThings_test.py @@ -154,83 +154,125 @@ class TestMessage(unittest.TestCase): - def test_blank(self): - # create a blank message + def XXXtest_blank(self): + """CREATE a completely blank message""" msg = Message() + print(msg.pydict) msg.dump() - def test_blank_create_dict(self): #1 {pydict} (CREATE) - msg = Message({"header":{}, "recs":[{"parmid":PARAM_SWITCH_STATE, "value":1}]}) + def XXXtest_blank_template(self): + """CREATE a message from a simple pydict template""" + # This is useful to simplify all other tests + msg = Message(Message.BLANK) + print(msg.pydict) msg.dump() - def XXXtest_add_rec_dict(self): #1 {pydict} (ADD) - # add rec fields from a dict parameter - msg = Message() - msg.add_rec({"paramid":PARAM_SWITCH_STATE, "value":1}) + def XXXtest_blank_create_dict(self): #1 {pydict} + """CREATE a blank message with a dict parameter""" + msg = Message({"header":{}, "recs":[{"wr":False, "parmid":PARAM_SWITCH_STATE, "value":1}]}) + print(msg.pydict) + msg.dump() - def XXXtest_create_template(self): #1 {pydict} (CREATE) + def XXXtest_blank_create_header_dict(self): #2 header={pydict} + """CREATE a blank message and add a header at creation time from a dict""" + msg = Message(header={"mfrid":123, "productid":456, "sensorid":789}) + print(msg.pydict) + msg.dump() + + def XXXtest_create_big_template(self): #1 {pydict} + """CREATE from a large template message""" # create a message from a template msg = Message(Devices.MIHO005_REPORT) + print(msg.pydict) msg.dump() - - def XXXtest_blank_create_header_dict(self): #2 header={pydict} (CREATE) - # create a blank message and add a header at creation from a dict - msg = Message(header={"mfrid":123, "productid":456, "sensorid":789}) + def XXXtest_add_rec_dict(self): #1 {pydict} + """UPDATE(APPEND) rec fields from a dict parameter""" + msg = Message(Message.BLANK) + i = msg.append_rec({"paramid":PARAM_SWITCH_STATE, "wr":True, "value":1}) + print("added index:%d" % i) + print(msg.pydict) msg.dump() - def XXXtest_add_header_dict(self): #2 header={pydict} (ADD) + def XXXtest_add_header_dict(self): #2 header={pydict} + """UPDATE(SET) a new header to an existing message""" msg = Message() - msg.add(header={"mfrid":123, "productid":456, "sensorid":789}) + msg.set(header={"mfrid":123, "productid":456, "sensorid":789}) + print(msg.pydict) msg.dump() + def XXXtest_add_recs_dict(self): + """UPDATE(SET) recs to an existing message""" + msg = Message() + msg.set(recs=[{"paramid":PARAM_SWITCH_STATE, "wr":True, "value":1}]) + print(msg.pydict) + msg.dump() - def XXXtest_alter_rec_template(self): #3 header_mfrid=123 (CHANGE) - # alter rec fields in a template + def XXXtest_add_path(self): + """UPDATE(SET) a pathed key to an existing message""" + msg = Message() + msg.set(header_productid=1234) + print(msg.pydict) + msg.dump() + + def XXXtest_alter_template(self): #3 header_mfrid=123 + """UPDATE(SET) an existing key with a path""" msg = Message(Devices.MIHO005_REPORT) - msg.alter(header_productid=123) + msg.set(header_productid=123) + msg.dump() + + def XXXtest_alter_template_multiple(self): + """UPDATE(SET) multiple keys with paths""" + msg = Message(Devices.MIHO005_REPORT) + msg.set(header_productid=123, header_sensorid=99) + print(msg.pydict) msg.dump() def XXXtest_blank_create_header_paths(self): #3 header_mfrid=123 (CREATE) - # create a blank message and add some header fields at creation time + """CREATE message with pathed keys in constructor""" msg = Message(header_mfrid=123, header_productid=456, header_sensorid=789) + print(msg.pydict) msg.dump() - def XXXtest_add_header_path(self): #3 header_mfrid=123 (ADD) - # add header fields to a message after creation via pathed keys + def XXXtest_blank_create_recs_paths(self): + """CREATE message with pathed keys in constructor""" + # uses integer path component to mean array index + msg = Message(recs_0={"paramid":PARAM_SWITCH_STATE, "wr":True, "value":1}, + recs_1={"paramid":PARAM_AIR_PRESSURE, "wr":True, "value":2}) + print(msg.pydict) + msg.dump() + + def XXXtest_add_rec_path(self): #5 recs_0_paramid=PARAM_SWITCH_STATE + """UPDATE(SET) records in a message""" + msg = Message(recs_0={}) # must create rec before you can change it + print(msg.pydict) + msg.set(recs_0_paramid=PARAM_SWITCH_STATE, recs_0_value=1, recs_0_wr=True) + print(msg.pydict) + msg.dump() + + def XXXtest_add_rec_fn_pydict(self): #6 SWITCH_STATE={pydict} + """UPDATE(ADD) a rec to message using PARAM constants as keys""" + #always creates a new rec at the end and then populates msg = Message() - msg.add(header_mfrid=123, header_productid=456) + msg.append_rec(PARAM_SWITCH_STATE, {"wr": True, "value":1}) + print(msg.pydict) msg.dump() - ###4 recs_0={pydict} - - def XXXtest_add_rec_path(self): #5 recs_0_paramid=PARAM_SWITCH_STATE (ADD) - # add rec fields to a message after creation via pathed indexed keys + def XXXtest_add_rec_fn_keyed(self): #7 SWITCH_STATE,value=1 (ADD) + """UPDATE(ADD) a rec to message using PARAM const and keyed values""" msg = Message() - msg.add(recs_0_paramid=PARAM_SWITCH_STATE, recs_0_value=1) + msg.append_rec(PARAM_SWITCH_STATE, wr=True, value=1) + print(msg.pydict) msg.dump() - - def XXXtest_add_rec_fn_pydict(self): #6 SWITCH_STATE={pydict} (ADD) - # add rec fields to a message after creation via pathed PARAM name keys - msg = Message() - msg.add_rec(PARAM_SWITCH_STATE, {"value":1}) - msg.dump() - - - def XXXtest_add_rec_fn(self): #7 SWITCH_STATE,value=1 (ADD) - # add rec fields to a message after creation via pathed PARAM name keys - msg = Message() - msg.add_rec(PARAM_SWITCH_STATE, value=1) - msg.dump() - - - def XXXtest_alter_rec_template_paramname(self): #8 SWITCH_STATE_value=1 (CHANGE) - # alter rec fields in a template + def XXXtest_get_pathed(self): + """READ from the message with a path key""" msg = Message(Devices.MIHO005_REPORT) - msg.alter(recs_SWITCH_STATE_value=1) - msg.dump() + print(msg.get("header_mfrid")) + #----- HERE ----- + + #### This is the attribute set/get abstraction def XXXtest_pydict_read(self): #9 msg["header"] msg["recs"][0] (READ) ## access a specific keyed entry like a normal pydict, for read @@ -256,6 +298,9 @@ msg["recs"][0] = {"paramid": PARAM_SWITCH_STATE, "value": 1} msg.dump() + + #### This is the PARAMID indexer + def XXXtest_paramid_read_struct(self): #10 msg[PARAM_SWITCH_STATE] (READ) # access a paramid entry for read of the whole structure msg = Message(Devices.MIHO005_REPORT) @@ -273,6 +318,18 @@ msg.dump() + + ####TODO: This is where we need an intelligent key parser + + def XXXtest_alter_rec_template_paramname(self): #8 SWITCH_STATE_value=1 (CHANGE) + # alter rec fields in a template + msg = Message(Devices.MIHO005_REPORT) + msg.set(recs_SWITCH_STATE_value=1) + msg.dump() + + + ####TODO: This is where dump() might need to dump to a strbuf and then output + #some of these might just print the inner pydict though def XXXtest_repr(self): ## dump a message in printable format msg = Message(Devices.MIHO005_REPORT)