diff --git a/README.md b/README.md index 59b613d..c2b4abe 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,110 @@ See the [pislave](https://github.com/ootjersb/pislave#wiring) project ... ``` +* Retrieve a single setting from the WPU + ``` + # ./itho-wpu.py --action getsetting --id 1 + 1. Hardware Configuratie: 70 (min: 0, max: 65535, step: 1) + ``` + +* Retrieve all settings from the WPU + ``` + # ./itho-wpu.py --action getsettings + 0. Niet Gebruikt: 0 (min: 0, max: 65535, step: 1) + 1. Hardware Configuratie: 70 (min: 0, max: 65535, step: 1) + 2. Jaar Inbedrijfstelling: 2010 (min: 2004, max: 2099, step: 1) + 3. Datum Van Inbedrijfstelling: 101 (min: 0, max: 3112, step: 1) + 4. Max Handbedieningstijd (min): 0 (min: 0, max: 600, step: 1) + 5. Vorsttemp (°C): 2.0 (min: -10.0, max: 10.0, step: 0.1) + 6. Offset Voor Vorst Temp (K): 2.0 (min: 0.0, max: 10.0, step: 0.1) + 7. Differentie Van Vorst Om Elektrisch Element Te Starten (K): 1.5 (min: 0.0, max: 10.0, step: 0.1) + 8. Fout Reset Tijd (min): 120 (min: 1, max: 1440, step: 1) + 9. Loginterval Tijd (sec): 5 (min: 1, max: 300, step: 1) + ... + ``` + +* Change a setting of the WPU + ``` + # ./itho-wpu.py --action setsetting --id 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) + ``` + +* Retrieve counters of the WPU + ``` + ./itho-wpu.py --action getcounters + 0. Bedrijf Cv Pomp (cnt_chpump): 5295 uur + 1. Bedrijf Bron Pomp (cnt_source): 873 uur + 2. Bedrijf Boiler Pomp (cnt_dhw): 56085 uur + 3. Bedrijf Compressor (cnt_comp): 53066 uur + 4. Bedrijf Elektrisch Element (cnt_elek): 45568 uur + 5. Cv Bedrijf (cnt_chmode): 2869 uur + 6. Boiler Bedrijf (cnt_dhwmode): 42518 uur + 7. Vrijkoel Bedrijf (cnt_fcmode): 64098 uur + 8. Bedrijf (cnt_run): 34559 uur + 9. Cv Pomp Starts (cnt_chpstart): 65317 + 10. Bron Pomp Starts (cnt_srcpstart): 40672 + 11. Boiler Pomp Starts (cnt_dhwpstart): 30485 + 12. Compressor Starts (cnt_compstart): 62495 + 13. Elektrisch Element Start (cnt_elekstart): 44800 + 14. Cv Starts (cnt_chmstart): 9229 + 15. Boiler Starts (cnt_dhwmstart): 2069 + 16. Vrijkoel Starts (cnt_fcmstart): 62218 + 17. Systeem Starts (cnt_system): 61952 + 18. Bedrijf Dhw Element (cnt_dhwelement): 15616 uur + 19. Dhw E-Element Starts (cnt_dhwestart): 0 + ``` + +* Retrieve a manual operation setting from the WPU + ``` + # ./itho-wpu.py --loglevel info --action getmanual --id 0 + 0. Buitentemp (°C): 10.0 + ``` + +* Initiate a manual operation + ``` + # ./itho-wpu.py --action setmanual --id 0 --value 29.00 + Current manual operation: + 0. Buitentemp (°C): 10.0 + Manual `0` will be changed to `29.0`? [y/N] y + Updating manual operation 0 to `29.0` + Are you really sure? (Type uppercase yes): YES + ``` + +* Finish a manual operation + ``` + # ./itho-wpu.py --action setmanual --id 0 --value 29.00 --no-check + Current manual operation: + 0. Buitentemp (°C): 29.0 + Manual `0` will be changed to `29.0`? [y/N] y + Updating manual operation 0 to `29.0` + Are you really sure? (Type uppercase yes): YES + ``` + +* Reset all errors: + ``` + # ./itho-wpu.py --action setmanual --id 37 --value 1 + Current manual operation: + 37. Reset Alle Fouten: 0 + Manual `37` will be changed to `1`? [y/N] y + Updating manual operation 37 to `1` + Are you really sure? (Type uppercase yes): YES + ``` + +* Reset timer: + ``` + # ./itho-wpu.py --action setmanual --id 38 --value 1 + Current manual operation: + 38. Reset Timer: 0 + Manual `38` will be changed to `1`? [y/N] y + Updating manual operation 38 to `1` + Are you really sure? (Type uppercase yes): YES + ``` + # Exporting measurements ## InfluxDB diff --git a/convert-itho-db.py b/convert-itho-db.py index 6a44154..6e2753a 100755 --- a/convert-itho-db.py +++ b/convert-itho-db.py @@ -38,7 +38,9 @@ def convert(par_file, sqlite_db): tables = [] for table_info in par_cur.tables(tableType="TABLE"): - if re.match("^(VersieBeheer|Data[Ll]abel|Parameterlijst)", table_info.table_name): + if re.match( + "^(VersieBeheer|Data[Ll]abel|Parameterlijst|Handbed|Counters)", table_info.table_name + ): tables.append(table_info.table_name) for t in sorted(tables): @@ -69,11 +71,46 @@ def convert(par_file, sqlite_db): r.Eenheid_NL, ) ) - if re.match("^VersieBeheer", t): - par_cur.execute(f"select VersieNummer, DataLabel, ParameterLijst from {t}") + if re.match("^Counters", t): + par_cur.execute(f"select Index, Naam, Tekst_NL, Tooltip_NL, Eenheid_NL from {t}") rows = par_cur.fetchall() for r in sorted(rows): - data.append((r.VersieNummer, r.DataLabel, r.ParameterLijst)) + data.append( + ( + r.Index, + r.Naam, + r.Tekst_NL, + r.Tooltip_NL, + r.Eenheid_NL, + ) + ) + if re.match("^Handbed", t): + par_cur.execute( + "select Index, Naam, Naam_fabriek, Min, Max, Default, " + f"Tekst_NL, Tooltip_NL, Eenheid_NL from {t}" + ) + rows = par_cur.fetchall() + for r in sorted(rows): + data.append( + ( + r.Index, + r.Naam, + r.Naam_fabriek, + r.Min, + r.Max, + r.Default, + r.Tekst_NL, + r.Tooltip_NL, + r.Eenheid_NL, + ) + ) + if re.match("^VersieBeheer", t): + par_cur.execute( + f"select VersieNummer, DataLabel, ParameterLijst, Handbed, Counters from {t}" + ) + rows = par_cur.fetchall() + for r in sorted(rows): + data.append((r.VersieNummer, r.DataLabel, r.ParameterLijst, r.Handbed, r.Counters)) sqlite_db.insert(t.lower(), data) diff --git a/db.py b/db.py index ca9d32c..6f8b12e 100644 --- a/db.py +++ b/db.py @@ -44,6 +44,32 @@ class sqlite: );""".format( t ) + elif t.startswith("counters"): + query = """ + CREATE TABLE {} ( + id real, + name text, + title text, + tooltip text, + unit text + );""".format( + t + ) + elif t.startswith("handbed"): + query = """ + CREATE TABLE {} ( + id real, + name text, + name_factory text, + min real, + max real, + def real, + title text, + tooltip text, + unit text + );""".format( + t + ) elif t.startswith("parameterlijst"): query = """ CREATE TABLE {} ( @@ -64,7 +90,9 @@ class sqlite: CREATE TABLE {} ( version integer primary key, datalabel integer, - parameterlist integer + parameterlist integer, + handbed integer, + counters interger );""".format( t ) @@ -86,10 +114,24 @@ class sqlite: """.format( t ) + elif t.startswith("counters"): + query = """ + INSERT INTO {} (id, name, title, tooltip, unit) + VALUES (?, ?, ?, ?, ?); + """.format( + t + ) + elif t.startswith("handbed"): + query = """ + INSERT INTO {} (id, name, name_factory, min, max, def, title, tooltip, unit) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?); + """.format( + t + ) elif t.startswith("versiebeheer"): query = """ - INSERT INTO {} (version, datalabel, parameterlist) - VALUES (?, ?, ?); + INSERT INTO {} (version, datalabel, parameterlist, handbed, counters) + VALUES (?, ?, ?, ?, ?); """.format( t ) diff --git a/itho-wpu.py b/itho-wpu.py index 7c277ef..42eadab 100755 --- a/itho-wpu.py +++ b/itho-wpu.py @@ -23,6 +23,11 @@ actions = { "getserial": [0x90, 0xE1], "getdatatype": [0xA4, 0x00], "getdatalog": [0xA4, 0x01], + "getsetting": [0xA4, 0x10], + "setsetting": [0xA4, 0x10], + "getmanual": [0x40, 0x30], + "setmanual": [0x40, 0x30], + "getcounters": [0x42, 0x10], } @@ -33,9 +38,26 @@ def parse_args(): "--action", nargs="?", required=True, - choices=actions.keys(), + choices=list(actions.keys()) + ["getsettings"], help="Execute an action", ) + parser.add_argument( + "--id", + nargs="?", + type=int, + help="Setting or manual identifier", + ) + parser.add_argument( + "--value", + nargs="?", + help="Setting value", + ) + parser.add_argument( + "--check", + default=True, + action=argparse.BooleanOptionalAction, + help="Enable/disable manual operation (used with --setmanual)", + ) parser.add_argument( "--loglevel", nargs="?", @@ -71,13 +93,13 @@ class IthoWPU: self._q = queue.Queue() self.no_cache = no_cache self.cache = IthoWPUCache() - self.nodeid = self.get("getnodeid") - self.datatype = self.get("getdatatype") + self.nodeid = self.call("getnodeid") + self.datatype = self.call("getdatatype") self.heatpump_db = db.sqlite("heatpump.sqlite") - def get(self, action): + def call(self, action, identifier=None, datatype=None, value=None, check=True): if not self.no_cache: - response = self.cache.get(action.replace("get", "")) + response = self.cache.call(action.replace("get", "")) if response is not None: logger.debug(f"Response (from cache): {response}") return response @@ -93,7 +115,7 @@ class IthoWPU: if not self.slave_only: master = I2CMaster(address=0x41, bus=1, queue=self._q) if action: - response = master.execute_action(action) + response = master.execute_action(action, identifier, datatype, value, check) logger.debug(f"Response: {response}") master.close() @@ -150,6 +172,65 @@ class IthoWPU: return datalog return datalog + def get_counters(self): + listversion = self.get_listversion_from_nodeid() + counters_version = self.heatpump_db.execute( + f"SELECT counters FROM versiebeheer WHERE version = {listversion}" + )[0]["counters"] + if counters_version is None or not type(counters_version) == int: + logger.error(f"Counters not found in database for version {listversion}") + return None + settings = self.heatpump_db.execute( + "SELECT id, name, title, tooltip, unit " + f"FROM counters_v{counters_version}" + ) + return settings + + def get_settings(self): + listversion = self.get_listversion_from_nodeid() + parameterlist_version = self.heatpump_db.execute( + f"SELECT parameterlist FROM versiebeheer WHERE version = {listversion}" + )[0]["parameterlist"] + if parameterlist_version is None or not type(parameterlist_version) == int: + logger.error(f"Parameterlist not found in database for version {listversion}") + return None + settings = self.heatpump_db.execute( + "SELECT id, name, min, max, def, title, description, unit " + + f"FROM parameterlijst_v{parameterlist_version}" + ) + return settings + + def get_setting_by_id(self, settingid): + listversion = self.get_listversion_from_nodeid() + parameterlist_version = self.heatpump_db.execute( + f"SELECT parameterlist FROM versiebeheer WHERE version = {listversion}" + )[0]["parameterlist"] + if parameterlist_version is None or not type(parameterlist_version) == int: + logger.error(f"Parameterlist not found in database for version {listversion}") + return None + setting_details = self.heatpump_db.execute( + "SELECT name, min, max, def, title, description, unit " + + f"FROM parameterlijst_v{parameterlist_version} WHERE id = {settingid}" + ) + if len(setting_details) != 1: + return None + return setting_details[0] + + def get_manual_by_id(self, manualid): + listversion = self.get_listversion_from_nodeid() + handbed_version = self.heatpump_db.execute( + f"SELECT handbed FROM versiebeheer WHERE version = {listversion}" + )[0]["handbed"] + if handbed_version is None or not type(handbed_version) == int: + logger.error(f"Handbed not found in database for version {listversion}") + return None + manual_details = self.heatpump_db.execute( + "SELECT name, min, max, def, title, tooltip, unit " + + f"FROM handbed_v{handbed_version} WHERE id = {manualid}" + ) + if len(manual_details) != 1: + return None + return manual_details[0] + class IthoWPUCache: def __init__(self): @@ -178,7 +259,7 @@ class IthoWPUCache: logger.debug(f"Writing to local cache: {json.dumps(self._cache_data)}") json.dump(self._cache_data, cache_file) - def get(self, action): + def call(self, action): if action not in ["nodeid", "serial", "datatype"]: logger.debug(f"Cache for '{action}' is not supported") return None @@ -219,10 +300,16 @@ def process_response(action, response, args, wpu): from itho_export import export_to_influxdb export_to_influxdb(action, measurements) + elif action == "getsetting": + process_setting(response, wpu) + elif action == "getmanual": + process_manual(response, wpu) elif action == "getnodeid": process_nodeid(response) elif action == "getserial": process_serial(response) + elif action == "getcounters": + process_counters(response, wpu) def process_nodeid(response): @@ -253,53 +340,303 @@ def process_serial(response): logger.info(f"Serial: {serial}") +def process_counters(response, wpu): + counters = wpu.get_counters() + message = response[5:] + for c in counters: + index = int(c["id"]) * 2 + num = format_datatype(c["name"], message[index : index + 2], 0x10) # noqa: E203 + + logger.info( + "{}. {} ({}): {}{}".format( + int(c["id"]), + c["title"].title(), + c["name"].lower(), + num, + " " + c["unit"] if c["unit"] is not None else "", + ) + ) + + def process_datalog(response, wpu): datalog = wpu.get_datalog_structure() message = response[5:] measurements = {} for d in datalog: if d.type == 0x0 or d.type == 0xC: - m = message[d.index : d.index + 1] # noqa: E203 - num = int(m[0], 0) - elif d.type == 0x10: - m = message[d.index : d.index + 2] # noqa: E203 - num = (int(m[0], 0) << 8) + int(m[1], 0) - elif d.type == 0x12: - m = message[d.index : d.index + 2] # noqa: E203 - num = round((int(m[0], 0) << 8) + int(m[1], 0) / 100, 2) - elif d.type == 0x90: - m = message[d.index : d.index + 2] # noqa: E203 - num = (int(m[0], 0) << 8) + int(m[1], 0) - if num >= 32768: - num -= 65536 - elif d.type == 0x92: - m = message[d.index : d.index + 2] # noqa: E203 - num = (int(m[0], 0) << 8) + int(m[1], 0) - if num >= 32768: - num -= 65536 - num = round(num / 100, 2) + length = 1 + elif d.type == 0x10 or d.type == 0x12 or d.type == 0x90 or d.type == 0x92: + length = 2 elif d.type == 0x20: - m = message[d.index : d.index + 4] # noqa: E203 - num = (int(m[0], 0) << 24) + (int(m[1], 0) << 16) + (int(m[2], 0) << 8) + int(m[3], 0) + length = 4 else: - logger.error(f"Unknown message type for datalog {d.name}: {d.type}") + logger.error(f"Unknown message type for datalog {d.label}: {d.type}") + num = format_datatype(d.label, message[d.index : d.index + length], d.type) # noqa: E203 logger.info(f"{d.description}: {num}") measurements[d.label] = num return measurements -if __name__ == "__main__": +def parse_setting(response, wpu): + message = response[5:] + + settingid = int(message[17], 0) + setting = wpu.get_setting_by_id(settingid) + if setting is None: + logger.error(f"Setting '{settingid}' is invalid") + return + + datatype = message[16] + value = format_datatype(setting["name"], message[0:4], datatype) + minimum = format_datatype(setting["name"], message[4:8], datatype) + maximum = format_datatype(setting["name"], message[8:12], datatype) + step = format_datatype(setting["name"], message[12:16], datatype) + + return value, minimum, maximum, step + + +def process_setting(response, wpu): + message = response[5:] + + settingid = int(message[17], 0) + setting = wpu.get_setting_by_id(settingid) + if setting is None: + logger.error(f"Setting '{settingid}' is invalid") + return + + value, minimum, maximum, step = parse_setting(response, wpu) + + logger.info( + "{}. {}{}: {} (min: {}, max: {}, step: {})".format( + settingid, + setting["title"].title(), + f' ({setting["unit"]})' if setting["unit"] is not None else "", + value, + minimum, + maximum, + step, + ) + ) + + +def process_settings(wpu, args): + settings = wpu.get_settings() + for setting in settings: + response = wpu.call("getsetting", int(setting["id"])) + if response is not None: + process_response("getsetting", response, args, wpu) + + +def process_setsetting(wpu, args): + logger.info("Current setting:") + response = wpu.call("getsetting", int(args.id)) + 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.id, 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.id}` will be changed to `{parsed_value}`? [y/N] ") + if sure in ["y", "Y"]: + logger.info(f"Updating setting {args.id} to `{parsed_value}`") + else: + logger.error("Aborted") + return + + response = wpu.call("setsetting", args.id, None, normalized_value) + if response is None: + return + process_response("getsetting", response, args, wpu) + + +def process_manual(response, wpu): + message = response[5:] + + manualid = int(message[2], 0) + manual = wpu.get_manual_by_id(manualid) + if manual is None: + logger.error(f"Manual '{manualid}' is invalid") + return + + datatype = message[3] + value = format_datatype(manual["name"], message[4:6], datatype) + + logger.info( + "{}. {}{}: {}".format( + manualid, + manual["title"].title(), + f' ({manual["unit"]})' if manual["unit"] is not None else "", + value, + ) + ) + + +def process_setmanual(wpu, args): + logger.info("Current manual operation:") + response = wpu.call("getmanual", int(args.id)) + if response is None: + return + process_response("getmanual", response, args, wpu) + message = response[5:] + datatype = message[3] + + # TODO: check if Max Handbedieningstijd > 0 + + 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 manual operation datatype: {datatype}") + logger.debug(f"New manual operation (input): {value}") + normalized_value = int(value.replace(".", "")) + logger.debug(f"New manual operation (normalized): {normalized_value}") + hex_list_value = [hex(v) for v in list(normalized_value.to_bytes(2, byteorder="big"))] + logger.debug(f"New manual operation (hex): {hex_list_value}") + parsed_value = format_datatype(args.id, hex_list_value, datatype) + logger.debug(f"New manual operation (parsed): {parsed_value}") + + sure = input(f"Manual `{args.id}` will be changed to `{parsed_value}`? [y/N] ") + if sure in ["y", "Y"]: + logger.info(f"Updating manual operation {args.id} to `{parsed_value}`") + else: + logger.error("Aborted") + return + + response = wpu.call("setmanual", args.id, int(datatype, 0), normalized_value, args.check) + + +def format_datatype(name, m, dt): + """ + Transform a list of bytes to a readable number based on the datatype. + + :param str name: Name/label of the data + :param list[str] m: List of bytes in hexadecimal string format + :param dt: Datatype + :type dt: str or int + """ + + num = None + if type(dt) is str: + dt = int(dt, 0) + + if dt == 0x0 or dt == 0xC: + num = int(m[-1], 0) + elif dt == 0x1: + num = round(int(m[-1], 0) / 10, 1) + elif dt == 0x2: + num = round(int(m[-1], 0) / 100, 2) + elif dt == 0x10: + num = (int(m[-2], 0) << 8) + int(m[-1], 0) + elif dt == 0x12: + num = round((int(m[-2], 0) << 8) + int(m[-1], 0) / 100, 2) + elif dt == 0x13: + num = round((int(m[-2], 0) << 8) + int(m[-1], 0) / 1000, 3) + elif dt == 0x14: + num = round((int(m[-2], 0) << 8) + int(m[-1], 0) / 10000, 4) + elif dt == 0x80: + num = int(m[-1], 0) + if num >= 128: + num -= 256 + elif dt == 0x81: + num = int(m[-1], 0) + if num >= 128: + num -= 256 + num = round(num / 10, 1) + elif dt == 0x82: + num = int(m[-1], 0) + if num >= 128: + num -= 256 + num = round(num / 100, 2) + elif dt == 0x8F: + num = int(m[-1], 0) + if num >= 128: + num -= 256 + num = round(num / 1000, 3) + elif dt == 0x90: + num = (int(m[-2], 0) << 8) + int(m[-1], 0) + if num >= 32768: + num -= 65536 + elif dt == 0x91: + num = (int(m[-2], 0) << 8) + int(m[-1], 0) + if num >= 32768: + num -= 65536 + num = round(num / 10, 2) + elif dt == 0x92: + num = (int(m[-2], 0) << 8) + int(m[-1], 0) + if num >= 32768: + num -= 65536 + num = round(num / 100, 2) + elif dt == 0x20: + num = (int(m[-4], 0) << 24) + (int(m[-3], 0) << 16) + (int(m[-2], 0) << 8) + int(m[-1], 0) + else: + logger.error(f"Unknown datatype for '{name}': 0x{dt:X}") + return num + + +def main(): args = parse_args() if args.loglevel: logger.setLevel(args.loglevel.upper()) + logging.getLogger("itho_i2c").setLevel(args.loglevel.upper()) if args.timestamp: stdout_log_handler.setFormatter( logging.Formatter("%(asctime)-15s %(levelname)s: %(message)s") ) + if args.action in ["getsetting", "setsetting", "getmanual", "setmanual"] and args.id is None: + logger.error(f"`--id` is required with `--action {args.action}`") + return + wpu = IthoWPU(args.master_only, args.slave_only, args.slave_timeout, args.no_cache) - response = wpu.get(args.action) + + if args.action == "getsettings": + process_settings(wpu, args) + return + + if args.action == "setsetting": + process_setsetting(wpu, args) + return + + if args.action == "setmanual": + process_setmanual(wpu, args) + return + + response = wpu.call(args.action, args.id) if response is not None: process_response(args.action, response, args, wpu) + + +if __name__ == "__main__": + main() diff --git a/itho_i2c.py b/itho_i2c.py index 12eaddb..12a99b1 100644 --- a/itho_i2c.py +++ b/itho_i2c.py @@ -4,14 +4,24 @@ import logging import pigpio import struct import time +import sys logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) +stdout_log_handler = logging.StreamHandler(sys.stdout) +stdout_log_handler.setFormatter(logging.Formatter("%(message)s")) +logger.addHandler(stdout_log_handler) actions = { "getnodeid": [0x90, 0xE0], "getserial": [0x90, 0xE1], "getdatatype": [0xA4, 0x00], "getdatalog": [0xA4, 0x01], + "getsetting": [0xA4, 0x10], + "setsetting": [0xA4, 0x10], + "getmanual": [0x40, 0x30], + "setmanual": [0x40, 0x30], + "getcounters": [0x42, 0x10], } @@ -45,9 +55,57 @@ class I2CMaster: self.i = I2CRaw(address=address, bus=bus) self.queue = queue - def compose_request(self, action): - # 0x80 = source, 0x04 = msg_type, 0x00 = length - request = [0x80] + actions[action] + [0x04, 0x00] + def compose_request(self, action, identifier, datatype, value, check): + if action == "getsetting": + request = ( + [0x80] + + actions[action] + + [0x04, 0x13] # read, length + + [0x00, 0x00, 0x00, 0x00] # current + + [0x00, 0x00, 0x00, 0x00] # min + + [0x00, 0x00, 0x00, 0x00] # max + + [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] + ) + elif action == "getmanual": + byte_identifier = list(identifier.to_bytes(2, byteorder="big")) + request = ( + [0x80] + + actions[action] + + [0x04, 0x04] # read, length + + [0x01] # bank + + byte_identifier + + [0x01] # 1 = manual + ) + elif action == "setmanual": + byte_identifier = list(identifier.to_bytes(2, byteorder="big")) + byte_list_value = list(value.to_bytes(2, byteorder="big")) + byte_check = [0x01] if check else [0x00] + request = ( + [0x80] + + actions[action] + + [0x06, 0x07] # write, length + + [0x01] # bank + + byte_identifier + + [datatype] # datatype + + byte_list_value # new + + byte_check + ) + else: + # 0x80 = source, 0x04 = msg_type, 0x00 = length + request = [0x80] + actions[action] + [0x04, 0x00] request.append(self.calculate_checksum(request)) return request @@ -60,9 +118,16 @@ class I2CMaster: checksum = 0 return checksum - def execute_action(self, action): - request = self.compose_request(action) + def execute_action(self, action, identifier, datatype, value, check): + request = self.compose_request(action, identifier, datatype, value, check) + request_in_hex = [hex(c) for c in request] + logger.debug(f"Request: {request_in_hex}") result = None + if action in ["setsetting", "setmanual"]: + 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) @@ -71,6 +136,8 @@ class I2CMaster: if self.queue.qsize() > 0: result = self.queue.get() break + elif action == "setmanual" and self.queue.qsize() == 0: + return None if result is None: logger.error("No valid result in 20 requests")