diff --git a/README.md b/README.md index d51ba9f..b8cd18f 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,17 @@ See the [pislave](https://github.com/ootjersb/pislave#wiring) project ... ``` +* Change a setting of the WPU + ``` + # ./itho-wpu.py --action setsetting --settingid 139 --value 48 + Current setting: + 139. Blokkade Tijd Van Verwarmen Naar Koelen (uur): 24 (min: 0, max: 168, step: 1) + Setting `139` will be changed to `48`? [y/N] y + Updating setting 139 to `48` + Are you really sure? (Type uppercase yes): YES + 139. Blokkade Tijd Van Verwarmen Naar Koelen (uur): 48 (min: 0, max: 168, step: 1) + ``` + # Exporting measurements ## InfluxDB diff --git a/itho-wpu.py b/itho-wpu.py index 431353d..eab40bd 100755 --- a/itho-wpu.py +++ b/itho-wpu.py @@ -24,6 +24,7 @@ actions = { "getdatatype": [0xA4, 0x00], "getdatalog": [0xA4, 0x01], "getsetting": [0xA4, 0x10], + "setsetting": [0xA4, 0x10], } @@ -43,6 +44,11 @@ def parse_args(): type=int, help="Setting identifier", ) + parser.add_argument( + "--value", + nargs="?", + help="Setting value", + ) parser.add_argument( "--loglevel", nargs="?", @@ -82,7 +88,7 @@ class IthoWPU: self.datatype = self.get("getdatatype") self.heatpump_db = db.sqlite("heatpump.sqlite") - def get(self, action, identifier=None): + def get(self, action, identifier=None, value=None): if not self.no_cache: response = self.cache.get(action.replace("get", "")) if response is not None: @@ -100,7 +106,7 @@ class IthoWPU: if not self.slave_only: master = I2CMaster(address=0x41, bus=1, queue=self._q) if action: - response = master.execute_action(action, identifier) + response = master.execute_action(action, identifier, value) logger.debug(f"Response: {response}") master.close() @@ -361,6 +367,52 @@ def process_settings(wpu, args): process_response("getsetting", response, args, wpu) +def process_setsetting(wpu, args): + logger.info("Current setting:") + response = wpu.get("getsetting", int(args.settingid)) + if response is None: + return + process_response("getsetting", response, args, wpu) + message = response[5:] + datatype = message[16] + + if args.value is None: + value = input("Provide a new value: ") + else: + value = args.value + + # TODO: add support for negative values + if float(value) < 0: + logger.error("Negative values are not supported yet.") + return + + logger.debug(f"New setting datatype: {datatype}") + logger.debug(f"New setting (input): {value}") + normalized_value = int(value.replace(".", "")) + logger.debug(f"New setting (normalized): {normalized_value}") + hex_list_value = [hex(v) for v in list(normalized_value.to_bytes(4, byteorder="big"))] + logger.debug(f"New setting (hex): {hex_list_value}") + parsed_value = format_datatype(args.settingid, hex_list_value, datatype) + logger.debug(f"New setting (parsed): {parsed_value}") + + _, minimum, maximum, _ = parse_setting(response, wpu) + if parsed_value < minimum or parsed_value > maximum: + logger.error(f"New value `{parsed_value}` is not between `{minimum}` and `{maximum}`") + return + + sure = input(f"Setting `{args.settingid}` will be changed to `{parsed_value}`? [y/N] ") + if sure in ["y", "Y"]: + logger.info(f"Updating setting {args.settingid} to `{parsed_value}`") + else: + logger.error("Aborted") + return + + response = wpu.get("setsetting", args.settingid, normalized_value) + if response is None: + return + process_response("getsetting", response, args, wpu) + + def format_datatype(name, m, dt): """ Transform a list of bytes to a readable number based on the datatype. @@ -441,7 +493,7 @@ def main(): logging.Formatter("%(asctime)-15s %(levelname)s: %(message)s") ) - if args.action == "getsetting" and args.settingid is None: + if args.action in ["getsetting", "setsetting"] and args.settingid is None: logger.error("`--settingid` is required with `--action getsetting`") return @@ -451,6 +503,10 @@ def main(): process_settings(wpu, args) return + if args.action == "setsetting": + process_setsetting(wpu, args) + return + response = wpu.get(args.action, args.settingid) if response is not None: process_response(args.action, response, args, wpu) diff --git a/itho_i2c.py b/itho_i2c.py index ca4b660..e0f4310 100644 --- a/itho_i2c.py +++ b/itho_i2c.py @@ -18,6 +18,7 @@ actions = { "getdatatype": [0xA4, 0x00], "getdatalog": [0xA4, 0x01], "getsetting": [0xA4, 0x10], + "setsetting": [0xA4, 0x10], } @@ -51,7 +52,7 @@ class I2CMaster: self.i = I2CRaw(address=address, bus=bus) self.queue = queue - def compose_request(self, action, identifier): + def compose_request(self, action, identifier, value): if action == "getsetting": request = ( [0x80] @@ -63,6 +64,18 @@ class I2CMaster: + [0x00, 0x00, 0x00, 0x00] # step + [0x00, identifier, 0x00] ) + elif action == "setsetting": + byte_list_value = list(value.to_bytes(4, byteorder="big")) + request = ( + [0x80] + + actions[action] + + [0x06, 0x13] # write, length + + byte_list_value # new + + [0x00, 0x00, 0x00, 0x00] # min + + [0x00, 0x00, 0x00, 0x00] # max + + [0x00, 0x00, 0x00, 0x00] # step + + [0x00, identifier, 0x00] + ) else: # 0x80 = source, 0x04 = msg_type, 0x00 = length request = [0x80] + actions[action] + [0x04, 0x00] @@ -78,11 +91,16 @@ class I2CMaster: checksum = 0 return checksum - def execute_action(self, action, identifier): - request = self.compose_request(action, identifier) + def execute_action(self, action, identifier, value): + request = self.compose_request(action, identifier, value) request_in_hex = [hex(c) for c in request] logger.debug(f"Request: {request_in_hex}") result = None + if action == "setsetting": + sure = input("Are you really sure? (Type uppercase yes): ") + if sure != "YES": + logger.error("Aborted") + return for i in range(0, 20): logger.debug(f"Executing action: {action}") self.i.write_i2c_block_data(request)