diff --git a/src/data/__init__.py b/src/data/__init__.py index 02eb0e3..d09a2ed 100644 --- a/src/data/__init__.py +++ b/src/data/__init__.py @@ -64,10 +64,12 @@ def _save_overrides(fname: str, overrides: list): json.dump(overrides, file, indent=2) -def get_controller(port: int) -> dict: +def get_controller(port: int, model: str) -> dict: controllers = get_controllers() models = get_controller_types() - controller = next(filter(lambda x: x['Port'] == port, controllers), None) + controller = next( + filter(lambda x: x['Port'] == port and x['Type'] == model, + controllers), None) if not controller: return None model = next(filter(lambda x: x['Name'] == controller['Type'], models), @@ -76,12 +78,13 @@ def get_controller(port: int) -> dict: return controller -def get_controller_registers(port: int) -> list: +def get_controller_registers(port: int, model_name: str) -> list: result = [] - controller = get_controller(port) + controller = get_controller(port, model_name) for reg in controller['Model']['Registers']: reg['CurrentValue'] = _get_reg_value(controller, reg) result.append(reg) + result = sorted(result, key=lambda x: x['Address']) return result @@ -112,6 +115,7 @@ def set_controller_register(ct: str, port: int, reg: str, val): def get_controllers() -> dict: + """Returns controllers along with their type represented as 'Model'.""" with open(_get_controlers_fname(), 'r') as file: try: controllers = json.load(file) @@ -133,9 +137,15 @@ def set_controllers(data: list): def get_controller_types() -> dict: + """Returns controller types as they are in the json file.""" with open(_get_controller_models_fname(), 'r') as file: try: models = json.load(file) + for m in models: + registers = m.get('Registers', []) + for r in registers: + if 'Function' not in r: + r['Function'] = 'F16' except (json.JSONDecodeError): models = [] return models diff --git a/src/data/controller-models.json b/src/data/controller-models.json index 08b7f22..08b3f59 100644 --- a/src/data/controller-models.json +++ b/src/data/controller-models.json @@ -1,91 +1,53 @@ [ + { + "Name": "ThirdPartyInput", + "RunAs": "Client-script", + "Script": "import time\r\nt = int(time.time() * 1000)\r\ncounter = 0\r\nchunks = []\r\nchunks.append(2)\r\nchunks.append(counter)\r\nchunks.append((t >> 48))\r\nchunks.append((t >> 32) & 0xFFFF)\r\nchunks.append((t >> 16) & 0xFFFF)\r\nchunks.append(t & 0xFFFF)\r\nchunks.append(128)\r\nchunks.append(5)\r\nchunks.append(65)\r\ninstance.write_multiple_registers(505, chunks)", + "ScriptExample": "import time\r\nt = int(time.time() * 1000)\r\ncounter = 0\r\nchunks = []\r\nchunks.append(2)\r\nchunks.append(counter)\r\nchunks.append((t >> 48))\r\nchunks.append((t >> 32) & 0xFFFF)\r\nchunks.append((t >> 16) & 0xFFFF)\r\nchunks.append(t & 0xFFFF)\r\nchunks.append(129)\r\nchunks.append(1)\r\nchunks.append(65)\r\ninstance.write_multiple_registers(505, chunks)" + }, { "Name": "Ads", "RunAs": "Client", "Registers": [ { "CanOverride": false, - "Name": "Status2", - "Type": "Input", + "Name": "Status", + "Type": "Holding", + "Function": "F3", "Address": 0, "DisplayAs": "DropdownVariants", "PossibleValues": [ { - "NotOperting": 0 + "Oki": 1 }, { - "Operating": 1 + "Error": 0 } ] }, { "CanOverride": false, - "Name": "Sim160", + "Name": "LastExposedTurbine", "Type": "Input", + "Function": "F4", "Address": 928, "DisplayAs": "RawBinary", "PossibleValues": [] }, { "CanOverride": false, - "Name": "ConfigUnixTime4", - "Type": "Input", - "Address": 259, - "DisplayAs": "RawBinary", - "PossibleValues": [] - }, - { - "CanOverride": false, - "Name": "ConfigUnixTime3", - "Type": "Input", - "Address": 258, - "DisplayAs": "RawBinary", - "PossibleValues": [] - }, - { - "CanOverride": false, - "Name": "ConfigUnixTime2", - "Type": "Input", - "Address": 257, - "DisplayAs": "RawBinary", - "PossibleValues": [] - }, - { - "CanOverride": false, - "Name": "ConfigUnixTime1", - "Type": "Input", - "Address": 256, - "DisplayAs": "RawBinary", - "PossibleValues": [] - }, - { - "CanOverride": false, - "Name": "TurbinesConfigured", - "Type": "Input", - "Address": 261, - "DisplayAs": "RawBinary", - "PossibleValues": [] - }, - { - "CanOverride": false, - "Name": "InterfaceVersion", - "Type": "Input", - "Address": 260, - "DisplayAs": "RawBinary", - "PossibleValues": [] - }, - { - "CanOverride": false, - "Name": "Sim2", + "Name": "SecondExposedTurbine", "Type": "Input", + "Function": "F4", "Address": 770, "DisplayAs": "RawBinary", "PossibleValues": [] }, { "CanOverride": false, - "Name": "Sim1", + "Name": "FirstExposedTurbine", "Type": "Input", + "Function": "F4", "Address": 769, "DisplayAs": "RawBinary", "PossibleValues": [] @@ -94,32 +56,72 @@ "CanOverride": false, "Name": "SystemStatusCode", "Type": "Input", + "Function": "F4", "Address": 1, "DisplayAs": "RawBinary", "PossibleValues": [] }, { - "CanOverride": true, - "Name": "Interface", - "Type": "Holding", - "Address": 512, - "PossibleValues": [ - { - "Deactivated": 0 - }, - { - "AircraftDetected": 1 - }, - { - "AircraftNotDetected": 3 - } - ] + "CanOverride": false, + "Name": "InterfaceVersion", + "Type": "Input", + "Function": "F4", + "Address": 260, + "DisplayAs": "RawBinary", + "PossibleValues": [] }, { "CanOverride": false, - "Name": "Status", - "Type": "Holding", + "Name": "TurbinesConfigured", + "Type": "Input", + "Function": "F4", + "Address": 261, + "DisplayAs": "RawBinary", + "PossibleValues": [] + }, + { + "CanOverride": false, + "Name": "ConfigUnixTime1", + "Type": "Input", + "Function": "F4", + "Address": 256, + "DisplayAs": "RawBinary", + "PossibleValues": [] + }, + { + "CanOverride": false, + "Name": "ConfigUnixTime2", + "Type": "Input", + "Function": "F4", + "Address": 257, + "DisplayAs": "RawBinary", + "PossibleValues": [] + }, + { + "CanOverride": false, + "Name": "ConfigUnixTime3", + "Type": "Input", + "Function": "F4", + "Address": 258, + "DisplayAs": "RawBinary", + "PossibleValues": [] + }, + { + "CanOverride": false, + "Name": "ConfigUnixTime4", + "Type": "Input", + "Function": "F4", + "Address": 259, + "DisplayAs": "RawBinary", + "PossibleValues": [] + }, + { + "CanOverride": false, + "Name": "Status2", + "Type": "Input", + "Function": "F4", "Address": 0, + "DisplayAs": "DropdownVariants", "PossibleValues": [ { "NotOperting": 0 @@ -134,6 +136,16 @@ "DropdownVariants", "RawBinary", "RawInteger" + ], + "Functions": [ + "F1", + "F2", + "F3", + "F4", + "F5", + "F6", + "F15", + "F16" ] }, { @@ -142,40 +154,33 @@ "Registers": [ { "CanOverride": true, - "Name": "RemoteReset", - "Type": "Coil", - "Address": 20001, + "Name": "PhotecellSensing", + "Type": "Input", + "Function": "F4", + "Address": 20202, + "DisplayAs": "DropdownVariants", "PossibleValues": [ { - "NoReset": 0 + "Night": 1 }, { - "Reset": 1 - } - ] - }, - { - "CanOverride": false, - "Name": "ForcedOffAdls", - "Type": "Holding", - "Address": 20210, - "PossibleValues": [ - { - "Auto": 0 + "Twilight": 2 }, { - "AircraftNotDetected": 128 + "Day": 3 }, { - "AircraftDetected": 129 + "FailSafeIntensity": 255 } ] }, { "CanOverride": true, - "Name": "Status", + "Name": "FullSystemStatus", "Type": "Input", + "Function": "F4", "Address": 19000, + "DisplayAs": "DropdownVariants", "PossibleValues": [ { "Normal": 1 @@ -188,6 +193,9 @@ }, { "FailAndAlarm": 4 + }, + { + "BAD": 666 } ] }, @@ -195,7 +203,9 @@ "CanOverride": true, "Name": "TimeOfDay", "Type": "Input", + "Function": "F4", "Address": 20200, + "DisplayAs": "DropdownVariants", "PossibleValues": [ { "ControllerOff": 0 @@ -223,11 +233,53 @@ } ] }, + { + "CanOverride": false, + "Name": "ForcedOffAdls", + "Type": "Holding", + "Function": "F3", + "Address": 20210, + "DisplayAs": "DropdownVariants", + "PossibleValues": [ + { + "Auto": 0 + }, + { + "AircraftNotDetected": 128 + }, + { + "AircraftDetected": 129 + } + ] + } + ], + "DisplayVariants": [ + "DropdownVariants", + "RawBinary", + "RawInteger" + ], + "Functions": [ + "F1", + "F2", + "F3", + "F4", + "F5", + "F6", + "F15", + "F16" + ] + }, + { + "Name": "CIP400", + "RunAs": "Server", + "Registers": [ { "CanOverride": true, - "Name": "OverallSystemStatus", + "Name": "SelfStatus", "Type": "Input", + "Function": "F4", "Address": 19005, + "DisplayAs": "DropdownVariants", "PossibleValues": [ { "Normal": 1 @@ -242,18 +294,14 @@ "FailAndAlarm": 4 } ] - } - ] - }, - { - "Name": "CIP400", - "RunAs": "Server", - "Registers": [ + }, { "CanOverride": true, "Name": "RemoteReset", "Type": "Coil", + "Function": "F5", "Address": 20001, + "DisplayAs": "DropdownVariants", "PossibleValues": [ { "NoReset": 0 @@ -263,28 +311,13 @@ } ] }, - { - "CanOverride": false, - "Name": "ForcedOffAdls", - "Type": "Holding", - "Address": 20210, - "PossibleValues": [ - { - "Auto": 0 - }, - { - "AircraftNotDetected": 128 - }, - { - "AircraftDetected": 129 - } - ] - }, { "CanOverride": true, "Name": "Status", "Type": "Input", + "Function": "F4", "Address": 19000, + "DisplayAs": "DropdownVariants", "PossibleValues": [ { "Normal": 1 @@ -304,7 +337,9 @@ "CanOverride": true, "Name": "TimeOfDay", "Type": "Input", + "Function": "F4", "Address": 20200, + "DisplayAs": "DropdownVariants", "PossibleValues": [ { "ControllerOff": 0 @@ -336,7 +371,9 @@ "CanOverride": true, "Name": "PhotocellSensing", "Type": "Input", + "Function": "F4", "Address": 20202, + "DisplayAs": "DropdownVariants", "PossibleValues": [ { "Night": 1 @@ -353,25 +390,39 @@ ] }, { - "CanOverride": true, - "Name": "OverallSystemStatus", - "Type": "Input", - "Address": 19005, + "CanOverride": false, + "Name": "ForcedOffAdls", + "Type": "Holding", + "Function": "F6", + "Address": 20210, + "DisplayAs": "DropdownVariants", "PossibleValues": [ { - "Normal": 1 + "Auto": 0 }, { - "Fail": 2 + "AircraftNotDetected": 128 }, { - "Alarm": 3 - }, - { - "FailAndAlarm": 4 + "AircraftDetected": 129 } ] } + ], + "DisplayVariants": [ + "DropdownVariants", + "RawBinary", + "RawInteger" + ], + "Functions": [ + "F1", + "F2", + "F3", + "F4", + "F5", + "F6", + "F15", + "F16" ] }, { @@ -380,9 +431,30 @@ "Registers": [ { "CanOverride": false, - "Name": "ForcedOffAdls", + "Name": "ForcedOffAdlsWrite", "Type": "Holding", + "Function": "F6", "Address": 512, + "DisplayAs": "DropdownVariants", + "PossibleValues": [ + { + "Auto": 0 + }, + { + "AircraftDetected": 1 + }, + { + "AircraftNotDetected": 3 + } + ] + }, + { + "CanOverride": true, + "Name": "ForcedOffAdlsRead", + "Type": "Input", + "Function": "F4", + "Address": 512, + "DisplayAs": "DropdownVariants", "PossibleValues": [ { "Auto": 0 @@ -399,7 +471,9 @@ "CanOverride": true, "Name": "OverallSystemStatus", "Type": "Input", + "Function": "F4", "Address": 0, + "DisplayAs": "DropdownVariants", "PossibleValues": [ { "NotOperting": 0 @@ -409,6 +483,21 @@ } ] } + ], + "DisplayVariants": [ + "DropdownVariants", + "RawBinary", + "RawInteger" + ], + "Functions": [ + "F1", + "F2", + "F3", + "F4", + "F5", + "F6", + "F15", + "F16" ] } ] \ No newline at end of file diff --git a/src/data/controllers.json b/src/data/controllers.json index 34c8463..c2f83c5 100644 --- a/src/data/controllers.json +++ b/src/data/controllers.json @@ -1,10 +1,4 @@ [ - { - "Type": "Ads", - "Enabled": true, - "Ip": "127.0.0.1", - "Port": 2502 - }, { "Type": "CIP400", "Enabled": true, @@ -15,7 +9,19 @@ "Type": "Quantec", "Enabled": true, "Ip": "127.0.0.1", - "Port": 11700 + "Port": 11704 + }, + { + "Type": "ThirdPartyInput", + "Enabled": true, + "Ip": "127.0.0.1", + "Port": 2502 + }, + { + "Type": "Ads", + "Enabled": true, + "Ip": "127.0.0.1", + "Port": 2502 }, { "Type": "CIP300", @@ -24,9 +30,9 @@ "Port": 11508 }, { - "Type": "CIP400", + "Type": "Quantec", "Enabled": true, "Ip": "127.0.0.1", - "Port": 11506 + "Port": 11705 } ] \ No newline at end of file diff --git a/src/modbus/simulator.py b/src/modbus/simulator.py index 09f65dd..0ce7813 100755 --- a/src/modbus/simulator.py +++ b/src/modbus/simulator.py @@ -62,61 +62,120 @@ def remove_instance(instance, key: str): del _active[key] -def handle_server(instance): - for reg in data.get_controller_registers(instance.port): +def handle_server(instance, ct: dict): + for reg in data.get_controller_registers(instance.port, ct.get('Name', + '')): if not reg['CanOverride']: continue tp = reg['Type'].lower() v = reg['CurrentValue'] if tp == 'input' and v is not None: instance.data_bank.set_input_registers(int(reg['Address']), [v]) + if tp == 'holding' and v is not None: + instance.data_bank.set_holding_registers(int(reg['Address']), [v]) + + +def handle_client_script(instance, script: str): + exec(script, {}, {'instance': instance}) def handle_client(instance, ct: dict): - registers = data.get_controller_registers(instance.port) - holdingRead = next( - filter(lambda x: x['Type'] == 'Holding' and x['CanOverride'] is False, - registers), None) + """ Acts as a Modbus client. Function codes matter. - holdingWrite = next( - filter(lambda x: x['Type'] == 'Holding' and x['CanOverride'] is True, - registers), None) + FC1 - read coils + FC4 - read input + FC3 - read holding + FC5 - write coils + FC6 - write holding """ - inputRegs = filter(lambda x: x['Type'] == 'Input', registers) + registers = data.get_controller_registers(instance.port, ct['Name']) + + inputRead = filter(lambda x: x['Function'] == 'F4', registers) + holdingRead = filter(lambda x: x['Function'] == 'F3', registers) + coilsRead = filter(lambda x: x['Function'] == 'F1', registers) + coilsWrite = filter(lambda x: x['Function'] == 'F5', registers) + holdingWrite = filter(lambda x: x['Function'] == 'F6', registers) regs = {} for r in registers: regs[r['Address']] = r['Name'] info = {'Model': ct['Name'], 'Port': instance.port, 'Registers': regs} - if holdingRead: - result = instance.read_holding_registers(holdingRead['Address'], 1) - if result is None: - logging.debug(f'{ct["Name"]} client@{instance.port} no connection') - else: - gather('read', 'holding', holdingRead['Address'], instance.port, - result[0], info, 'outbound') + warn = '{0} register {1} on server {2} is misconfigured. {3} call result is {4}' + connected = False + + # Input read + for r in list(inputRead): + result = instance.read_input_registers(r['Address']) + if result is not None: + gather('read', 'input', r['Address'], instance.port, result[0], + info, 'outbound') data.set_controller_register(info['Model'], info['Port'], - holdingRead['Name'], result[0]) - if holdingWrite: - v = holdingWrite['CurrentValue'] - instance.write_single_register(holdingWrite['Address'], v) - gather('write', 'holding', holdingWrite['Address'], - instance.port, v, info, 'outbound') - for r in list(inputRegs): - result = instance.read_input_registers(r['Address']) - if result is not None: - gather('read', 'input', r['Address'], instance.port, - result[0], info, 'outbound') - data.set_controller_register(info['Model'], info['Port'], - r['Name'], result[0]) - else: - logging.warning( - f'Input register {r["Name"]} on server {instance.port} is misconfigured. Read result is None' - ) - else: - logging.warning( - f'{ct["Name"]} client@{instance.port} is not fully configured') + r['Name'], result[0]) + connected = True + else: + logging.debug( + warn.format('Input', r['Name'], instance.port, 'FC4', result)) + break + + # Holding read + for r in list(holdingRead): + result = instance.read_holding_registers(r['Address']) + if result is not None: + gather('read', 'holding', r['Address'], instance.port, result[0], + info, 'outbound') + data.set_controller_register(info['Model'], info['Port'], + r['Name'], result[0]) + connected = True + else: + logging.debug( + warn.format('Holding', r['Name'], instance.port, 'FC3', + result)) + break + + # Coils read + for r in list(coilsRead): + result = instance.read_coils(r['Address']) + if result is not None: + gather('read', 'coils', r['Address'], instance.port, result[0], + info, 'outbound') + data.set_controller_register(info['Model'], info['Port'], + r['Name'], result[0]) + connected = True + else: + logging.debug( + warn.format('Coils', r['Name'], instance.port, 'FC1', result)) + break + + # Coils write + for r in list(coilsWrite): + result = instance.write_single_coil(r['Address'], + r.get('CurrentValue', False)) + if result is True: + gather('write', 'coils', r['Address'], instance.port, + r.get('CurrentValue', False), info, 'outbound') + connected = True + else: + logging.debug( + warn.format('Coils', r['Name'], instance.port, 'FC5', result)) + break + + # Holding write + for r in list(holdingWrite): + result = instance.write_single_register(r['Address'], + r.get('CurrentValue', 0)) + if result is True: + gather('write', 'holding', r['Address'], instance.port, + r.get('CurrentValue', 0), info, 'outbound') + connected = True + else: + logging.debug( + warn.format('Holding', r['Name'], instance.port, 'FC6', + result)) + break + + if not connected: + logging.debug(f'{ct["Name"]} client@{instance.port} no connection') def add_new(controllers: list): @@ -297,9 +356,6 @@ def merge_dict(base: dict, incoming: dict) -> dict: def aggregate_freq(collection: dict): for k, v in collection.items(): del v[0] - # print(f'{k}:') - # for timestamp, freq in v.items(): - # print(f'{timestamp} {freq} ') merged = {} for k, v in collection.items(): merged = merge_dict(merged, v) @@ -446,7 +502,7 @@ if __name__ == '__main__': for k in list(_active.keys()): instance = _active[k]['instance'] c = next( - filter(lambda c: instance.port == int(c['Port']), + filter(lambda x: f"{x['Type']}@{x['Port']}" == k, controllers), None) if not c or not c['Enabled']: @@ -454,9 +510,12 @@ if __name__ == '__main__': continue if type(instance) is ModbusServer: - handle_server(instance) + handle_server(instance, c['Model']) else: - handle_client(instance, c['Model']) + if c['Model']['RunAs'] == 'Client-script': + handle_client_script(instance, c['Model']['Script']) + else: + handle_client(instance, c['Model']) sleep(5) calculate_freq(5, datetime.datetime.now()) diff --git a/src/ui/controller_types.py b/src/ui/controller_types.py index 876a0ce..3d424ab 100644 --- a/src/ui/controller_types.py +++ b/src/ui/controller_types.py @@ -61,6 +61,11 @@ def edit(name: str): _save_model(existing, name) existing = _prepare_for_editing(name) break + elif key.startswith('script_'): + existing['Script'] = request.form['script'] + _save_model(existing, name) + existing = _prepare_for_editing(name) + break elif key.startswith('rename_'): _abort_if_cannot_delete(name) existing['Name'] = request.form['name'] @@ -98,11 +103,14 @@ def _find_model_by_name(name: str, models: dict = None) -> dict: def _prepare_for_editing(name: str): + functions = ['F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F15', 'F16'] types = ['Input', 'Holding', 'Coil', 'Discrete'] variants = ['DropdownVariants', 'RawBinary', 'RawInteger'] exists = _find_model_by_name(name) if not exists: return None + if exists['RunAs'] == 'Client-script': + return exists for reg in exists['Registers']: processed = [] for possible in reg['PossibleValues']: @@ -111,19 +119,21 @@ def _prepare_for_editing(name: str): reg['PossibleValues'] = '\n'.join(processed) exists['RegTypes'] = types exists['DisplayVariants'] = variants + exists['Functions'] = functions return exists def _save_model(model: dict, current_name: str): models = data.get_controller_types() models = [x for x in models if x['Name'] != current_name] - del model['RegTypes'] - _remove_reg(model, '') - for r in model['Registers']: - if 'PossibleValues' in r: - r['PossibleValues'] = _str_to_possible(r['PossibleValues']) - else: - r['PossibleValues'] = [] + if model['RunAs'] != 'Client-script': + del model['RegTypes'] + _remove_reg(model, '') + for r in model['Registers']: + if 'PossibleValues' in r: + r['PossibleValues'] = _str_to_possible(r['PossibleValues']) + else: + r['PossibleValues'] = [] models.insert(0, model) data.set_controller_types(models) @@ -156,6 +166,8 @@ def _update_reg(model: dict, reg_name: str, form: dict): updated['CanOverride'] = v == 'True' elif k == f'{reg_name}-type': updated['Type'] = v + elif k == f'{reg_name}-function': + updated['Function'] = v elif k == f'{reg_name}-display-as': updated['DisplayAs'] = v elif k == f'{reg_name}-possible': diff --git a/src/ui/controllers.py b/src/ui/controllers.py index d139266..5a100a8 100644 --- a/src/ui/controllers.py +++ b/src/ui/controllers.py @@ -37,17 +37,21 @@ def update(): if existing is None: abort(404) - current_regs = data.get_controller_registers(existing['Port']) - for r in registers: - reg = next(filter(lambda x: x['Name'] == r['Name'], current_regs), r) - display_as = reg.get('DisplayAs', 'DropdownVariants') - if display_as == 'RawBinary' and _is_binary_str(r['Value']): - r['Value'] = int(r['Value'], 2) - else: - r['Value'] = int(r['Value']) + if existing['Model']['RunAs'] != 'Client-script': - data.set_controller_registers(existing['Type'], existing['Port'], - registers) + current_regs = data.get_controller_registers(existing['Port'], + existing['Type']) + for r in registers: + reg = next(filter(lambda x: x['Name'] == r['Name'], current_regs), + r) + display_as = reg.get('DisplayAs', 'DropdownVariants') + if display_as == 'RawBinary' and _is_binary_str(r['Value']): + r['Value'] = int(r['Value'], 2) + else: + r['Value'] = int(r['Value']) + + data.set_controller_registers(existing['Type'], existing['Port'], + registers) enabled = enabled == 'True' if existing['Enabled'] != enabled: @@ -87,7 +91,7 @@ def add(): controller = _find(p, controllers) if p < 1024 or p > 65536: flash('Port should be in range 1025-65536') - elif controller: + elif controller and controller['Model']['RunAs'] == 'Server': flash('This port is already assigned to another controller!') elif t not in types: flash(f'Type {t} was not found') @@ -134,27 +138,31 @@ def get_list() -> dict: for controller in controllers: controller['Enabled'] = _to_status(controller['Enabled']) controller['Registers'] = [] - registers = data.get_controller_registers(controller['Port']) + if controller['Model']['RunAs'] != 'Client-script': + registers = data.get_controller_registers(controller['Port'], + controller['Type']) - for reg in registers: - controller['Registers'].append({ - 'Name': - reg['Name'], - 'Value': - _get_current_value(reg), - 'ValueStr': - _get_value_name(reg), - 'Address': - reg['Address'], - 'Type': - reg['Type'], - 'PossibleValues': - reg['PossibleValues'], - 'CanOverride': - reg['CanOverride'], - 'DisplayAs': - reg.get('DisplayAs', 'DropdownVariants') - }) + for reg in registers: + controller['Registers'].append({ + 'Name': + reg['Name'], + 'Value': + _get_current_value(reg), + 'ValueStr': + _get_value_name(reg), + 'Address': + reg['Address'], + 'Type': + reg['Type'], + 'PossibleValues': + reg['PossibleValues'], + 'CanOverride': + reg['CanOverride'], + 'DisplayAs': + reg.get('DisplayAs', 'DropdownVariants'), + 'Function': + reg['Function'] + }) return controllers diff --git a/src/ui/templates/controller_types/create.html b/src/ui/templates/controller_types/create.html index ecd3acf..42d4f51 100644 --- a/src/ui/templates/controller_types/create.html +++ b/src/ui/templates/controller_types/create.html @@ -16,6 +16,7 @@
+
+ + + {% endif %} + + {% for reg in type['Registers'] %}Example of a script. `instance` object is available and represents MODBUS client that can be used for reading or writing registers
+