Compare commits

..

No commits in common. "f5505203d140c8a51bad3cc522988ec423616f36" and "4607dcc8ca106f8378ed85cbb63941fc06de8f36" have entirely different histories.

10 changed files with 225 additions and 465 deletions

View File

@ -64,12 +64,10 @@ def _save_overrides(fname: str, overrides: list):
json.dump(overrides, file, indent=2) json.dump(overrides, file, indent=2)
def get_controller(port: int, model: str) -> dict: def get_controller(port: int) -> dict:
controllers = get_controllers() controllers = get_controllers()
models = get_controller_types() models = get_controller_types()
controller = next( controller = next(filter(lambda x: x['Port'] == port, controllers), None)
filter(lambda x: x['Port'] == port and x['Type'] == model,
controllers), None)
if not controller: if not controller:
return None return None
model = next(filter(lambda x: x['Name'] == controller['Type'], models), model = next(filter(lambda x: x['Name'] == controller['Type'], models),
@ -78,13 +76,12 @@ def get_controller(port: int, model: str) -> dict:
return controller return controller
def get_controller_registers(port: int, model_name: str) -> list: def get_controller_registers(port: int) -> list:
result = [] result = []
controller = get_controller(port, model_name) controller = get_controller(port)
for reg in controller['Model']['Registers']: for reg in controller['Model']['Registers']:
reg['CurrentValue'] = _get_reg_value(controller, reg) reg['CurrentValue'] = _get_reg_value(controller, reg)
result.append(reg) result.append(reg)
result = sorted(result, key=lambda x: x['Address'])
return result return result
@ -115,7 +112,6 @@ def set_controller_register(ct: str, port: int, reg: str, val):
def get_controllers() -> dict: def get_controllers() -> dict:
"""Returns controllers along with their type represented as 'Model'."""
with open(_get_controlers_fname(), 'r') as file: with open(_get_controlers_fname(), 'r') as file:
try: try:
controllers = json.load(file) controllers = json.load(file)
@ -137,15 +133,9 @@ def set_controllers(data: list):
def get_controller_types() -> dict: def get_controller_types() -> dict:
"""Returns controller types as they are in the json file."""
with open(_get_controller_models_fname(), 'r') as file: with open(_get_controller_models_fname(), 'r') as file:
try: try:
models = json.load(file) 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): except (json.JSONDecodeError):
models = [] models = []
return models return models

View File

@ -1,53 +1,91 @@
[ [
{
"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", "Name": "Ads",
"RunAs": "Client", "RunAs": "Client",
"Registers": [ "Registers": [
{ {
"CanOverride": false, "CanOverride": false,
"Name": "Status", "Name": "Status2",
"Type": "Holding", "Type": "Input",
"Function": "F3",
"Address": 0, "Address": 0,
"DisplayAs": "DropdownVariants", "DisplayAs": "DropdownVariants",
"PossibleValues": [ "PossibleValues": [
{ {
"Oki": 1 "NotOperting": 0
}, },
{ {
"Error": 0 "Operating": 1
} }
] ]
}, },
{ {
"CanOverride": false, "CanOverride": false,
"Name": "LastExposedTurbine", "Name": "Sim160",
"Type": "Input", "Type": "Input",
"Function": "F4",
"Address": 928, "Address": 928,
"DisplayAs": "RawBinary", "DisplayAs": "RawBinary",
"PossibleValues": [] "PossibleValues": []
}, },
{ {
"CanOverride": false, "CanOverride": false,
"Name": "SecondExposedTurbine", "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",
"Type": "Input", "Type": "Input",
"Function": "F4",
"Address": 770, "Address": 770,
"DisplayAs": "RawBinary", "DisplayAs": "RawBinary",
"PossibleValues": [] "PossibleValues": []
}, },
{ {
"CanOverride": false, "CanOverride": false,
"Name": "FirstExposedTurbine", "Name": "Sim1",
"Type": "Input", "Type": "Input",
"Function": "F4",
"Address": 769, "Address": 769,
"DisplayAs": "RawBinary", "DisplayAs": "RawBinary",
"PossibleValues": [] "PossibleValues": []
@ -56,72 +94,32 @@
"CanOverride": false, "CanOverride": false,
"Name": "SystemStatusCode", "Name": "SystemStatusCode",
"Type": "Input", "Type": "Input",
"Function": "F4",
"Address": 1, "Address": 1,
"DisplayAs": "RawBinary", "DisplayAs": "RawBinary",
"PossibleValues": [] "PossibleValues": []
}, },
{ {
"CanOverride": false, "CanOverride": true,
"Name": "InterfaceVersion", "Name": "Interface",
"Type": "Input", "Type": "Holding",
"Function": "F4", "Address": 512,
"Address": 260, "PossibleValues": [
"DisplayAs": "RawBinary", {
"PossibleValues": [] "Deactivated": 0
},
{
"AircraftDetected": 1
},
{
"AircraftNotDetected": 3
}
]
}, },
{ {
"CanOverride": false, "CanOverride": false,
"Name": "TurbinesConfigured", "Name": "Status",
"Type": "Input", "Type": "Holding",
"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, "Address": 0,
"DisplayAs": "DropdownVariants",
"PossibleValues": [ "PossibleValues": [
{ {
"NotOperting": 0 "NotOperting": 0
@ -136,16 +134,6 @@
"DropdownVariants", "DropdownVariants",
"RawBinary", "RawBinary",
"RawInteger" "RawInteger"
],
"Functions": [
"F1",
"F2",
"F3",
"F4",
"F5",
"F6",
"F15",
"F16"
] ]
}, },
{ {
@ -154,33 +142,40 @@
"Registers": [ "Registers": [
{ {
"CanOverride": true, "CanOverride": true,
"Name": "PhotecellSensing", "Name": "RemoteReset",
"Type": "Input", "Type": "Coil",
"Function": "F4", "Address": 20001,
"Address": 20202,
"DisplayAs": "DropdownVariants",
"PossibleValues": [ "PossibleValues": [
{ {
"Night": 1 "NoReset": 0
}, },
{ {
"Twilight": 2 "Reset": 1
}
]
},
{
"CanOverride": false,
"Name": "ForcedOffAdls",
"Type": "Holding",
"Address": 20210,
"PossibleValues": [
{
"Auto": 0
}, },
{ {
"Day": 3 "AircraftNotDetected": 128
}, },
{ {
"FailSafeIntensity": 255 "AircraftDetected": 129
} }
] ]
}, },
{ {
"CanOverride": true, "CanOverride": true,
"Name": "FullSystemStatus", "Name": "Status",
"Type": "Input", "Type": "Input",
"Function": "F4",
"Address": 19000, "Address": 19000,
"DisplayAs": "DropdownVariants",
"PossibleValues": [ "PossibleValues": [
{ {
"Normal": 1 "Normal": 1
@ -193,9 +188,6 @@
}, },
{ {
"FailAndAlarm": 4 "FailAndAlarm": 4
},
{
"BAD": 666
} }
] ]
}, },
@ -203,9 +195,7 @@
"CanOverride": true, "CanOverride": true,
"Name": "TimeOfDay", "Name": "TimeOfDay",
"Type": "Input", "Type": "Input",
"Function": "F4",
"Address": 20200, "Address": 20200,
"DisplayAs": "DropdownVariants",
"PossibleValues": [ "PossibleValues": [
{ {
"ControllerOff": 0 "ControllerOff": 0
@ -233,53 +223,11 @@
} }
] ]
}, },
{
"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, "CanOverride": true,
"Name": "SelfStatus", "Name": "OverallSystemStatus",
"Type": "Input", "Type": "Input",
"Function": "F4",
"Address": 19005, "Address": 19005,
"DisplayAs": "DropdownVariants",
"PossibleValues": [ "PossibleValues": [
{ {
"Normal": 1 "Normal": 1
@ -294,14 +242,18 @@
"FailAndAlarm": 4 "FailAndAlarm": 4
} }
] ]
}, }
]
},
{
"Name": "CIP400",
"RunAs": "Server",
"Registers": [
{ {
"CanOverride": true, "CanOverride": true,
"Name": "RemoteReset", "Name": "RemoteReset",
"Type": "Coil", "Type": "Coil",
"Function": "F5",
"Address": 20001, "Address": 20001,
"DisplayAs": "DropdownVariants",
"PossibleValues": [ "PossibleValues": [
{ {
"NoReset": 0 "NoReset": 0
@ -311,13 +263,28 @@
} }
] ]
}, },
{
"CanOverride": false,
"Name": "ForcedOffAdls",
"Type": "Holding",
"Address": 20210,
"PossibleValues": [
{
"Auto": 0
},
{
"AircraftNotDetected": 128
},
{
"AircraftDetected": 129
}
]
},
{ {
"CanOverride": true, "CanOverride": true,
"Name": "Status", "Name": "Status",
"Type": "Input", "Type": "Input",
"Function": "F4",
"Address": 19000, "Address": 19000,
"DisplayAs": "DropdownVariants",
"PossibleValues": [ "PossibleValues": [
{ {
"Normal": 1 "Normal": 1
@ -337,9 +304,7 @@
"CanOverride": true, "CanOverride": true,
"Name": "TimeOfDay", "Name": "TimeOfDay",
"Type": "Input", "Type": "Input",
"Function": "F4",
"Address": 20200, "Address": 20200,
"DisplayAs": "DropdownVariants",
"PossibleValues": [ "PossibleValues": [
{ {
"ControllerOff": 0 "ControllerOff": 0
@ -371,9 +336,7 @@
"CanOverride": true, "CanOverride": true,
"Name": "PhotocellSensing", "Name": "PhotocellSensing",
"Type": "Input", "Type": "Input",
"Function": "F4",
"Address": 20202, "Address": 20202,
"DisplayAs": "DropdownVariants",
"PossibleValues": [ "PossibleValues": [
{ {
"Night": 1 "Night": 1
@ -390,39 +353,25 @@
] ]
}, },
{ {
"CanOverride": false, "CanOverride": true,
"Name": "ForcedOffAdls", "Name": "OverallSystemStatus",
"Type": "Holding", "Type": "Input",
"Function": "F6", "Address": 19005,
"Address": 20210,
"DisplayAs": "DropdownVariants",
"PossibleValues": [ "PossibleValues": [
{ {
"Auto": 0 "Normal": 1
}, },
{ {
"AircraftNotDetected": 128 "Fail": 2
}, },
{ {
"AircraftDetected": 129 "Alarm": 3
},
{
"FailAndAlarm": 4
} }
] ]
} }
],
"DisplayVariants": [
"DropdownVariants",
"RawBinary",
"RawInteger"
],
"Functions": [
"F1",
"F2",
"F3",
"F4",
"F5",
"F6",
"F15",
"F16"
] ]
}, },
{ {
@ -431,30 +380,9 @@
"Registers": [ "Registers": [
{ {
"CanOverride": false, "CanOverride": false,
"Name": "ForcedOffAdlsWrite", "Name": "ForcedOffAdls",
"Type": "Holding", "Type": "Holding",
"Function": "F6",
"Address": 512, "Address": 512,
"DisplayAs": "DropdownVariants",
"PossibleValues": [
{
"Auto": 0
},
{
"AircraftDetected": 1
},
{
"AircraftNotDetected": 3
}
]
},
{
"CanOverride": true,
"Name": "ForcedOffAdlsRead",
"Type": "Input",
"Function": "F4",
"Address": 512,
"DisplayAs": "DropdownVariants",
"PossibleValues": [ "PossibleValues": [
{ {
"Auto": 0 "Auto": 0
@ -471,9 +399,7 @@
"CanOverride": true, "CanOverride": true,
"Name": "OverallSystemStatus", "Name": "OverallSystemStatus",
"Type": "Input", "Type": "Input",
"Function": "F4",
"Address": 0, "Address": 0,
"DisplayAs": "DropdownVariants",
"PossibleValues": [ "PossibleValues": [
{ {
"NotOperting": 0 "NotOperting": 0
@ -483,21 +409,6 @@
} }
] ]
} }
],
"DisplayVariants": [
"DropdownVariants",
"RawBinary",
"RawInteger"
],
"Functions": [
"F1",
"F2",
"F3",
"F4",
"F5",
"F6",
"F15",
"F16"
] ]
} }
] ]

View File

@ -1,4 +1,10 @@
[ [
{
"Type": "Ads",
"Enabled": true,
"Ip": "127.0.0.1",
"Port": 2502
},
{ {
"Type": "CIP400", "Type": "CIP400",
"Enabled": true, "Enabled": true,
@ -9,19 +15,7 @@
"Type": "Quantec", "Type": "Quantec",
"Enabled": true, "Enabled": true,
"Ip": "127.0.0.1", "Ip": "127.0.0.1",
"Port": 11704 "Port": 11700
},
{
"Type": "ThirdPartyInput",
"Enabled": true,
"Ip": "127.0.0.1",
"Port": 2502
},
{
"Type": "Ads",
"Enabled": true,
"Ip": "127.0.0.1",
"Port": 2502
}, },
{ {
"Type": "CIP300", "Type": "CIP300",
@ -30,9 +24,9 @@
"Port": 11508 "Port": 11508
}, },
{ {
"Type": "Quantec", "Type": "CIP400",
"Enabled": true, "Enabled": true,
"Ip": "127.0.0.1", "Ip": "127.0.0.1",
"Port": 11705 "Port": 11506
} }
] ]

View File

@ -62,120 +62,61 @@ def remove_instance(instance, key: str):
del _active[key] del _active[key]
def handle_server(instance, ct: dict): def handle_server(instance):
for reg in data.get_controller_registers(instance.port, ct.get('Name', for reg in data.get_controller_registers(instance.port):
'')):
if not reg['CanOverride']: if not reg['CanOverride']:
continue continue
tp = reg['Type'].lower() tp = reg['Type'].lower()
v = reg['CurrentValue'] v = reg['CurrentValue']
if tp == 'input' and v is not None: if tp == 'input' and v is not None:
instance.data_bank.set_input_registers(int(reg['Address']), [v]) 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): def handle_client(instance, ct: dict):
""" Acts as a Modbus client. Function codes matter. registers = data.get_controller_registers(instance.port)
holdingRead = next(
filter(lambda x: x['Type'] == 'Holding' and x['CanOverride'] is False,
registers), None)
FC1 - read coils holdingWrite = next(
FC4 - read input filter(lambda x: x['Type'] == 'Holding' and x['CanOverride'] is True,
FC3 - read holding registers), None)
FC5 - write coils
FC6 - write holding """
registers = data.get_controller_registers(instance.port, ct['Name']) inputRegs = filter(lambda x: x['Type'] == 'Input', registers)
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 = {} regs = {}
for r in registers: for r in registers:
regs[r['Address']] = r['Name'] regs[r['Address']] = r['Name']
info = {'Model': ct['Name'], 'Port': instance.port, 'Registers': regs} info = {'Model': ct['Name'], 'Port': instance.port, 'Registers': regs}
warn = '{0} register {1} on server {2} is misconfigured. {3} call result is {4}' if holdingRead:
connected = False result = instance.read_holding_registers(holdingRead['Address'], 1)
if result is None:
# Input read logging.debug(f'{ct["Name"]} client@{instance.port} no connection')
for r in list(inputRead): else:
result = instance.read_input_registers(r['Address']) gather('read', 'holding', holdingRead['Address'], instance.port,
if result is not None: result[0], info, 'outbound')
gather('read', 'input', r['Address'], instance.port, result[0],
info, 'outbound')
data.set_controller_register(info['Model'], info['Port'], data.set_controller_register(info['Model'], info['Port'],
r['Name'], result[0]) holdingRead['Name'], result[0])
connected = True if holdingWrite:
else: v = holdingWrite['CurrentValue']
logging.debug( instance.write_single_register(holdingWrite['Address'], v)
warn.format('Input', r['Name'], instance.port, 'FC4', result)) gather('write', 'holding', holdingWrite['Address'],
break instance.port, v, info, 'outbound')
for r in list(inputRegs):
# Holding read result = instance.read_input_registers(r['Address'])
for r in list(holdingRead): if result is not None:
result = instance.read_holding_registers(r['Address']) gather('read', 'input', r['Address'], instance.port,
if result is not None: result[0], info, 'outbound')
gather('read', 'holding', r['Address'], instance.port, result[0], data.set_controller_register(info['Model'], info['Port'],
info, 'outbound') r['Name'], result[0])
data.set_controller_register(info['Model'], info['Port'], else:
r['Name'], result[0]) logging.warning(
connected = True f'Input register {r["Name"]} on server {instance.port} is misconfigured. Read result is None'
else: )
logging.debug( else:
warn.format('Holding', r['Name'], instance.port, 'FC3', logging.warning(
result)) f'{ct["Name"]} client@{instance.port} is not fully configured')
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): def add_new(controllers: list):
@ -356,6 +297,9 @@ def merge_dict(base: dict, incoming: dict) -> dict:
def aggregate_freq(collection: dict): def aggregate_freq(collection: dict):
for k, v in collection.items(): for k, v in collection.items():
del v[0] del v[0]
# print(f'{k}:')
# for timestamp, freq in v.items():
# print(f'{timestamp} {freq} ')
merged = {} merged = {}
for k, v in collection.items(): for k, v in collection.items():
merged = merge_dict(merged, v) merged = merge_dict(merged, v)
@ -502,7 +446,7 @@ if __name__ == '__main__':
for k in list(_active.keys()): for k in list(_active.keys()):
instance = _active[k]['instance'] instance = _active[k]['instance']
c = next( c = next(
filter(lambda x: f"{x['Type']}@{x['Port']}" == k, filter(lambda c: instance.port == int(c['Port']),
controllers), None) controllers), None)
if not c or not c['Enabled']: if not c or not c['Enabled']:
@ -510,12 +454,9 @@ if __name__ == '__main__':
continue continue
if type(instance) is ModbusServer: if type(instance) is ModbusServer:
handle_server(instance, c['Model']) handle_server(instance)
else: else:
if c['Model']['RunAs'] == 'Client-script': handle_client(instance, c['Model'])
handle_client_script(instance, c['Model']['Script'])
else:
handle_client(instance, c['Model'])
sleep(5) sleep(5)
calculate_freq(5, datetime.datetime.now()) calculate_freq(5, datetime.datetime.now())

View File

@ -61,11 +61,6 @@ def edit(name: str):
_save_model(existing, name) _save_model(existing, name)
existing = _prepare_for_editing(name) existing = _prepare_for_editing(name)
break break
elif key.startswith('script_'):
existing['Script'] = request.form['script']
_save_model(existing, name)
existing = _prepare_for_editing(name)
break
elif key.startswith('rename_'): elif key.startswith('rename_'):
_abort_if_cannot_delete(name) _abort_if_cannot_delete(name)
existing['Name'] = request.form['name'] existing['Name'] = request.form['name']
@ -103,14 +98,11 @@ def _find_model_by_name(name: str, models: dict = None) -> dict:
def _prepare_for_editing(name: str): def _prepare_for_editing(name: str):
functions = ['F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F15', 'F16']
types = ['Input', 'Holding', 'Coil', 'Discrete'] types = ['Input', 'Holding', 'Coil', 'Discrete']
variants = ['DropdownVariants', 'RawBinary', 'RawInteger'] variants = ['DropdownVariants', 'RawBinary', 'RawInteger']
exists = _find_model_by_name(name) exists = _find_model_by_name(name)
if not exists: if not exists:
return None return None
if exists['RunAs'] == 'Client-script':
return exists
for reg in exists['Registers']: for reg in exists['Registers']:
processed = [] processed = []
for possible in reg['PossibleValues']: for possible in reg['PossibleValues']:
@ -119,21 +111,19 @@ def _prepare_for_editing(name: str):
reg['PossibleValues'] = '\n'.join(processed) reg['PossibleValues'] = '\n'.join(processed)
exists['RegTypes'] = types exists['RegTypes'] = types
exists['DisplayVariants'] = variants exists['DisplayVariants'] = variants
exists['Functions'] = functions
return exists return exists
def _save_model(model: dict, current_name: str): def _save_model(model: dict, current_name: str):
models = data.get_controller_types() models = data.get_controller_types()
models = [x for x in models if x['Name'] != current_name] models = [x for x in models if x['Name'] != current_name]
if model['RunAs'] != 'Client-script': del model['RegTypes']
del model['RegTypes'] _remove_reg(model, '')
_remove_reg(model, '') for r in model['Registers']:
for r in model['Registers']: if 'PossibleValues' in r:
if 'PossibleValues' in r: r['PossibleValues'] = _str_to_possible(r['PossibleValues'])
r['PossibleValues'] = _str_to_possible(r['PossibleValues']) else:
else: r['PossibleValues'] = []
r['PossibleValues'] = []
models.insert(0, model) models.insert(0, model)
data.set_controller_types(models) data.set_controller_types(models)
@ -166,8 +156,6 @@ def _update_reg(model: dict, reg_name: str, form: dict):
updated['CanOverride'] = v == 'True' updated['CanOverride'] = v == 'True'
elif k == f'{reg_name}-type': elif k == f'{reg_name}-type':
updated['Type'] = v updated['Type'] = v
elif k == f'{reg_name}-function':
updated['Function'] = v
elif k == f'{reg_name}-display-as': elif k == f'{reg_name}-display-as':
updated['DisplayAs'] = v updated['DisplayAs'] = v
elif k == f'{reg_name}-possible': elif k == f'{reg_name}-possible':

View File

@ -37,21 +37,17 @@ def update():
if existing is None: if existing is None:
abort(404) abort(404)
if existing['Model']['RunAs'] != 'Client-script': 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'])
current_regs = data.get_controller_registers(existing['Port'], data.set_controller_registers(existing['Type'], existing['Port'],
existing['Type']) registers)
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' enabled = enabled == 'True'
if existing['Enabled'] != enabled: if existing['Enabled'] != enabled:
@ -91,7 +87,7 @@ def add():
controller = _find(p, controllers) controller = _find(p, controllers)
if p < 1024 or p > 65536: if p < 1024 or p > 65536:
flash('Port should be in range 1025-65536') flash('Port should be in range 1025-65536')
elif controller and controller['Model']['RunAs'] == 'Server': elif controller:
flash('This port is already assigned to another controller!') flash('This port is already assigned to another controller!')
elif t not in types: elif t not in types:
flash(f'Type {t} was not found') flash(f'Type {t} was not found')
@ -138,31 +134,27 @@ def get_list() -> dict:
for controller in controllers: for controller in controllers:
controller['Enabled'] = _to_status(controller['Enabled']) controller['Enabled'] = _to_status(controller['Enabled'])
controller['Registers'] = [] controller['Registers'] = []
if controller['Model']['RunAs'] != 'Client-script': registers = data.get_controller_registers(controller['Port'])
registers = data.get_controller_registers(controller['Port'],
controller['Type'])
for reg in registers: for reg in registers:
controller['Registers'].append({ controller['Registers'].append({
'Name': 'Name':
reg['Name'], reg['Name'],
'Value': 'Value':
_get_current_value(reg), _get_current_value(reg),
'ValueStr': 'ValueStr':
_get_value_name(reg), _get_value_name(reg),
'Address': 'Address':
reg['Address'], reg['Address'],
'Type': 'Type':
reg['Type'], reg['Type'],
'PossibleValues': 'PossibleValues':
reg['PossibleValues'], reg['PossibleValues'],
'CanOverride': 'CanOverride':
reg['CanOverride'], reg['CanOverride'],
'DisplayAs': 'DisplayAs':
reg.get('DisplayAs', 'DropdownVariants'), reg.get('DisplayAs', 'DropdownVariants')
'Function': })
reg['Function']
})
return controllers return controllers

View File

@ -16,7 +16,6 @@
<div><select id="runAs" name="runAs"> <div><select id="runAs" name="runAs">
<option value="Server">Server</option> <option value="Server">Server</option>
<option value="Client">Client</option> <option value="Client">Client</option>
<option value="Client-script">Client-script</option>
</select></div> </select></div>
</div> </div>
<br/> <br/>

View File

@ -14,16 +14,7 @@
<a class="button" href="{{ url_for('controller_types.copy', name=type['Name']) }}">Copy</a> <a class="button" href="{{ url_for('controller_types.copy', name=type['Name']) }}">Copy</a>
<a class="delete" href="{{ url_for('controller_types.delete', name=type['Name']) }}">Delete</a> <a class="delete" href="{{ url_for('controller_types.delete', name=type['Name']) }}">Delete</a>
{% for reg in type['Registers'] %}
{% if type['RunAs']== 'Client-script' %}
<p>
<div><label>Script to be executed:</label></div>
<div><textarea rows="16" cols="64" disabled> {{ type['Script'] }} </textarea></div>
</p>
{% endif %}
{% for reg in type['Registers'] %}
<div class="setting-wide"> <div class="setting-wide">
<div><label for="reg-{{ reg['Name'] }}">{{ reg['Type'] }} {{ reg['Address'] }} ({{ reg['Name'] }}):</label></div> <div><label for="reg-{{ reg['Name'] }}">{{ reg['Type'] }} {{ reg['Address'] }} ({{ reg['Name'] }}):</label></div>
@ -39,12 +30,10 @@
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}
</select> </select>
<div class="grey-box">{{ reg['Function'] }}</div>
{% else %} {% else %}
<div class="grey-box">{{ reg['DisplayAs'] }}</div> <div class="grey-box">{{ reg['DisplayAs'] }}</div>
<div class="grey-box">{{ reg['Function'] }}</div>
{% endif %} {% endif %}
</div> </div>

View File

@ -18,33 +18,7 @@
<button class="button rename" type="submit" name="rename_{{ data['Name'] }}">Rename</button> <button class="button rename" type="submit" name="rename_{{ data['Name'] }}">Rename</button>
</section> </section>
</div> </div>
<br/> <br/>
{% if data['RunAs'] == 'Client-script' %}
<section>
<section class="row">
<div><button class="button" type="submit" name="script_update">Save</button></div>
</section>
<section class="row">
<p>Example of a script. `instance` object is available and represents MODBUS client that can be used for reading or writing registers</p>
</section>
<section class="row">
<div>
<textarea name="script" rows="16" cols="64" disabled>{{ data['ScriptExample'] }}</textarea>
</div>
</section>
<section>
<div><label for="script">Script to be run:</label></div>
<div><textarea name="script" rows="16" cols="64" placeholder="ValueNameOne=14&#13;ValueNameTwo=3" required>{{ request.form['script'] or data['Script'] }}</textarea></div>
</section>
</section>
{% else %}
<div><button class="button" type="submit" name="add_register">Add Register</button></div> <div><button class="button" type="submit" name="add_register">Add Register</button></div>
<section class="container"> <section class="container">
@ -81,15 +55,6 @@
<option value="{{ option }}" {% if option == reg['Type'] %}selected{% endif %}>{{ option }}</option> <option value="{{ option }}" {% if option == reg['Type'] %}selected{% endif %}>{{ option }}</option>
{% endfor %} {% endfor %}
</select></div> </select></div>
</div>
<div>
<div><label for="{{ reg['Name'] }}-function">Function:</label></div>
<div><select id="{{ reg['Name'] }}-function" name="{{ reg['Name'] }}-function">
{% for option in data['Functions'] %}
<option value="{{ option }}" {% if option == reg['Function'] %}selected{% endif %}>{{ option }}</option>
{% endfor %}
</select></div>
</div> </div>
<div> <div>
@ -111,6 +76,5 @@
</article> </article>
{% endfor %} {% endfor %}
</section> </section>
{% endif %}
</form> </form>
{% endblock %} {% endblock %}

View File

@ -21,9 +21,6 @@
<div>{{ controller['Enabled'] }}</div> <div>{{ controller['Enabled'] }}</div>
</div> </div>
{% if controller['Type'] == 'Client-script' %}
<div>Code is going to be here</div>
{% else %}
{% for reg in controller['Registers'] %} {% for reg in controller['Registers'] %}
<div class="setting"> <div class="setting">
@ -33,7 +30,6 @@
</div> </div>
{% endfor %} {% endfor %}
{% endif %}
<div class="override"> <div class="override">
<h4>Manual override</h4> <h4>Manual override</h4>
@ -54,7 +50,6 @@
</div> </div>
{% for reg in controller['Registers'] %} {% for reg in controller['Registers'] %}
{% if reg['CanOverride'] %} {% if reg['CanOverride'] %}
<div class="setting-wide"> <div class="setting-wide">
@ -68,17 +63,14 @@
<option value="{{ value }}" {% if value == reg['Value'] %}selected{% endif %}>{{ key }} ({{ value }})</option> <option value="{{ value }}" {% if value == reg['Value'] %}selected{% endif %}>{{ key }} ({{ value }})</option>
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}
</select> </select></div>
<div class="grey-box">{{ reg['Function'] }}</div>
</div>
{% else %} {% else %}
<div><input id="reg-{{ reg['Name'] }}" name="{{ reg['Name'] }}" value="{{ reg['Value'] }}" required><div class="grey-box">{{ reg['Function'] }}</div></div> <div><input id="reg-{{ reg['Name'] }}" name="{{ reg['Name'] }}" value="{{ reg['Value'] }}" required></div>
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<input class="button apply" type="submit" value="Apply"> <input class="button apply" type="submit" value="Apply">