Merge pull request 'Add frequency plotting and representation values in binary format' (#1) from dev into main
Reviewed-on: #1
This commit is contained in:
commit
4607dcc8ca
|
@ -29,7 +29,7 @@ Setup & debug
|
|||
+ ``pip list`` (pip show abibas, pip uninstall abibas, etc)
|
||||
|
||||
At this stage the package is installed in editable mode, to run it in debug mode:
|
||||
|
||||
,
|
||||
+ ``abibas-debug``
|
||||
|
||||
or in normal mode:
|
||||
|
@ -40,3 +40,9 @@ If in debug mode changes made to the Flask app will be applied automatically at
|
|||
for the changes in simulator though the tool has to be restarted.
|
||||
|
||||
**main** module can be manipulated for debugging purposes as well
|
||||
|
||||
Before releasing
|
||||
================
|
||||
+ ``pip install twine``
|
||||
+ ``twine check dist/*``
|
||||
+ ``twine upload --repository-url https://test.pypi.org/legacy/ dist/*``
|
||||
|
|
|
@ -25,6 +25,7 @@ dependencies = [
|
|||
"Flask",
|
||||
"pyModbusTCP",
|
||||
"prettytable",
|
||||
"plotext",
|
||||
]
|
||||
version = "0.0.1"
|
||||
urls = {homepage = "https://gittar.crabdance.com/e-corp/abibas"}
|
||||
|
|
|
@ -34,8 +34,11 @@ def _get_controller_models_fname() -> str:
|
|||
def _get_reg_value(controller: dict, reg: dict) -> int:
|
||||
# get first value from type definition (as default)
|
||||
value = None
|
||||
k, v = next(iter(reg['PossibleValues'][0].items()), None)
|
||||
value = v
|
||||
if 'PossibleValues' not in reg or not reg['PossibleValues']:
|
||||
value = 0
|
||||
else:
|
||||
k, v = next(iter(reg['PossibleValues'][0].items()), None)
|
||||
value = v
|
||||
# if there is a file with overriden value, take that
|
||||
overrides = _get_overrides_fname(controller['Type'], controller['Port'])
|
||||
if os.path.isfile(overrides):
|
||||
|
@ -51,8 +54,8 @@ def _get_reg_value(controller: dict, reg: dict) -> int:
|
|||
entry = [x for x in overriden if x['Name'] == reg['Name']]
|
||||
if entry:
|
||||
value = entry[0]['Value']
|
||||
# logging.debug(
|
||||
# f'{reg["Name"]} value gotten: {value} from {overrides}')
|
||||
logging.debug(
|
||||
f'{reg["Name"]} value gotten: {value} from {overrides}')
|
||||
return value
|
||||
|
||||
|
||||
|
@ -61,11 +64,6 @@ def _save_overrides(fname: str, overrides: list):
|
|||
json.dump(overrides, file, indent=2)
|
||||
|
||||
|
||||
def get_version() -> str:
|
||||
version = pkg_resources.get_distribution('abibas').version
|
||||
return version
|
||||
|
||||
|
||||
def get_controller(port: int) -> dict:
|
||||
controllers = get_controllers()
|
||||
models = get_controller_types()
|
||||
|
@ -164,3 +162,10 @@ def remove_controller(port: int) -> bool:
|
|||
logging.debug(f'Overrides file {overrides} not found, skip removing')
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def get_info() -> dict:
|
||||
distribution = pkg_resources.get_distribution('abibas')
|
||||
return dict(version=distribution.version,
|
||||
author='Taryel Hlontsi',
|
||||
license='copyleft (GPLv3)')
|
||||
|
|
|
@ -1,4 +1,141 @@
|
|||
[
|
||||
{
|
||||
"Name": "Ads",
|
||||
"RunAs": "Client",
|
||||
"Registers": [
|
||||
{
|
||||
"CanOverride": false,
|
||||
"Name": "Status2",
|
||||
"Type": "Input",
|
||||
"Address": 0,
|
||||
"DisplayAs": "DropdownVariants",
|
||||
"PossibleValues": [
|
||||
{
|
||||
"NotOperting": 0
|
||||
},
|
||||
{
|
||||
"Operating": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"CanOverride": false,
|
||||
"Name": "Sim160",
|
||||
"Type": "Input",
|
||||
"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",
|
||||
"Type": "Input",
|
||||
"Address": 770,
|
||||
"DisplayAs": "RawBinary",
|
||||
"PossibleValues": []
|
||||
},
|
||||
{
|
||||
"CanOverride": false,
|
||||
"Name": "Sim1",
|
||||
"Type": "Input",
|
||||
"Address": 769,
|
||||
"DisplayAs": "RawBinary",
|
||||
"PossibleValues": []
|
||||
},
|
||||
{
|
||||
"CanOverride": false,
|
||||
"Name": "SystemStatusCode",
|
||||
"Type": "Input",
|
||||
"Address": 1,
|
||||
"DisplayAs": "RawBinary",
|
||||
"PossibleValues": []
|
||||
},
|
||||
{
|
||||
"CanOverride": true,
|
||||
"Name": "Interface",
|
||||
"Type": "Holding",
|
||||
"Address": 512,
|
||||
"PossibleValues": [
|
||||
{
|
||||
"Deactivated": 0
|
||||
},
|
||||
{
|
||||
"AircraftDetected": 1
|
||||
},
|
||||
{
|
||||
"AircraftNotDetected": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"CanOverride": false,
|
||||
"Name": "Status",
|
||||
"Type": "Holding",
|
||||
"Address": 0,
|
||||
"PossibleValues": [
|
||||
{
|
||||
"NotOperting": 0
|
||||
},
|
||||
{
|
||||
"Operating": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"DisplayVariants": [
|
||||
"DropdownVariants",
|
||||
"RawBinary",
|
||||
"RawInteger"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "CIP300",
|
||||
"RunAs": "Server",
|
||||
|
@ -108,43 +245,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Ads",
|
||||
"RunAs": "Client",
|
||||
"Registers": [
|
||||
{
|
||||
"CanOverride": true,
|
||||
"Name": "Interface",
|
||||
"Type": "Holding",
|
||||
"Address": 512,
|
||||
"PossibleValues": [
|
||||
{
|
||||
"Deactivated": 0
|
||||
},
|
||||
{
|
||||
"AircraftDetected": 1
|
||||
},
|
||||
{
|
||||
"AircraftNotDetected": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"CanOverride": false,
|
||||
"Name": "Status",
|
||||
"Type": "Holding",
|
||||
"Address": 0,
|
||||
"PossibleValues": [
|
||||
{
|
||||
"NotOperting": 0
|
||||
},
|
||||
{
|
||||
"Operating": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "CIP400",
|
||||
"RunAs": "Server",
|
||||
|
|
|
@ -11,12 +11,6 @@
|
|||
"Ip": "127.0.0.1",
|
||||
"Port": 11507
|
||||
},
|
||||
{
|
||||
"Type": "Quantec",
|
||||
"Enabled": true,
|
||||
"Ip": "127.0.0.1",
|
||||
"Port": 11701
|
||||
},
|
||||
{
|
||||
"Type": "Quantec",
|
||||
"Enabled": true,
|
||||
|
@ -28,5 +22,11 @@
|
|||
"Enabled": true,
|
||||
"Ip": "127.0.0.1",
|
||||
"Port": 11508
|
||||
},
|
||||
{
|
||||
"Type": "CIP400",
|
||||
"Enabled": true,
|
||||
"Ip": "127.0.0.1",
|
||||
"Port": 11506
|
||||
}
|
||||
]
|
|
@ -11,8 +11,7 @@ def run():
|
|||
def run_debug():
|
||||
ui = _run_flask_debug()
|
||||
print(f'UI started: PID {ui.pid}')
|
||||
#ui.wait()
|
||||
_run_simulator('info')
|
||||
_run_simulator('debug')
|
||||
|
||||
|
||||
def _run_flask_debug() -> subprocess.Popen:
|
||||
|
|
|
@ -11,6 +11,7 @@ import signal
|
|||
import data
|
||||
import sys
|
||||
import traceback
|
||||
import plotext as plt
|
||||
|
||||
_RUN_FOR_SEC = 10
|
||||
_LOG_LEVEL = 20 # INFO
|
||||
|
@ -18,6 +19,8 @@ _LOG_LEVEL = 20 # INFO
|
|||
_statistic = {}
|
||||
_start_time = None
|
||||
_active = {}
|
||||
_inbound_read_freq = {}
|
||||
_inbound_write_freq = {}
|
||||
|
||||
|
||||
def get_run_options():
|
||||
|
@ -79,22 +82,38 @@ def handle_client(instance, ct: dict):
|
|||
filter(lambda x: x['Type'] == 'Holding' and x['CanOverride'] is True,
|
||||
registers), None)
|
||||
|
||||
inputRegs = filter(lambda x: x['Type'] == 'Input', 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 not result:
|
||||
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')
|
||||
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')
|
||||
|
@ -186,11 +205,13 @@ def gather(op, registry, address, port, value, info, tag='inbound'):
|
|||
|
||||
Returns
|
||||
-------
|
||||
void
|
||||
None
|
||||
"""
|
||||
|
||||
global _active
|
||||
global _statistic
|
||||
global _inbound_read_freq
|
||||
global _inbound_write_freq
|
||||
controller = f'{info["Model"]}@{info["Port"]}'
|
||||
try:
|
||||
# client port here is a port of incoming connection
|
||||
|
@ -216,6 +237,104 @@ def gather(op, registry, address, port, value, info, tag='inbound'):
|
|||
else:
|
||||
logging.debug(msg)
|
||||
|
||||
freq_key = f'{op};{registry} {address};{controller}'
|
||||
if tag == 'inbound' and (op == 'read' or op == 'write'):
|
||||
collection = _inbound_read_freq if op == 'read' else _inbound_write_freq
|
||||
entry = collection.get(freq_key)
|
||||
if entry:
|
||||
entry[0] += 1
|
||||
else:
|
||||
collection[freq_key] = {0: 0}
|
||||
|
||||
|
||||
def calculate_freq(interval_sec: int, dt: datetime.datetime):
|
||||
global _inbound_read_freq
|
||||
global _inbound_write_freq
|
||||
complete_freq_period(_inbound_read_freq, interval_sec, dt)
|
||||
complete_freq_period(_inbound_write_freq, interval_sec, dt)
|
||||
|
||||
|
||||
def complete_freq_period(collection: dict, interval_sec: int,
|
||||
dt: datetime.datetime):
|
||||
|
||||
format = '%Y-%d-%m %H:%M:%S'
|
||||
dt_str = dt.strftime(format)
|
||||
hour_ago_str = (dt - datetime.timedelta(hours=1)).strftime(format)
|
||||
for k, v in collection.items():
|
||||
v[dt_str] = v[0] / interval_sec
|
||||
v[0] = 0
|
||||
keys_to_del = []
|
||||
# starting from 3.7 insertion order in dict is preserved
|
||||
for entry, _ in v.items():
|
||||
if entry == 0:
|
||||
continue
|
||||
if entry < hour_ago_str:
|
||||
keys_to_del.append(entry)
|
||||
else:
|
||||
break
|
||||
for entry in keys_to_del:
|
||||
del v[entry]
|
||||
|
||||
|
||||
def merge_dict(base: dict, incoming: dict) -> dict:
|
||||
result = {**base, **incoming}
|
||||
for key in result.keys():
|
||||
if key in base and key in incoming:
|
||||
if isinstance(base[key], list) and isinstance(incoming[key], list):
|
||||
result[key] = base[key] + incoming[key]
|
||||
elif isinstance(base[key], list):
|
||||
result[key] = [*base[key], incoming[key]]
|
||||
elif isinstance(incoming[key], list):
|
||||
result[key] = [base[key], *incoming[key]]
|
||||
else:
|
||||
result[key] = [base[key], incoming[key]]
|
||||
else:
|
||||
if not isinstance(result[key], list):
|
||||
result[key] = [result[key]]
|
||||
return result
|
||||
|
||||
|
||||
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)
|
||||
aggregated = {}
|
||||
for k, v in merged.items():
|
||||
v = round(sum(v) / len(v), 2)
|
||||
aggregated[k] = v
|
||||
return aggregated
|
||||
|
||||
|
||||
def report_freq():
|
||||
global _inbound_read_freq
|
||||
global _inbound_write_freq
|
||||
|
||||
reads = aggregate_freq(_inbound_read_freq)
|
||||
writes = aggregate_freq(_inbound_write_freq)
|
||||
if not reads and not writes:
|
||||
return
|
||||
|
||||
plt.date_form('Y-d-m H:M:S', 'M:S')
|
||||
if reads:
|
||||
plt.plot(reads.keys(),
|
||||
reads.values(),
|
||||
label='inbound read frequency',
|
||||
color='blue')
|
||||
if writes:
|
||||
plt.plot(writes.keys(),
|
||||
writes.values(),
|
||||
label='inbound write frequency',
|
||||
color='magenta')
|
||||
plt.title('Inbound Read/Write Frequency')
|
||||
plt.xlabel('Time (max 1 hour long)')
|
||||
plt.ylabel('Operations per second')
|
||||
plt.show()
|
||||
|
||||
|
||||
def report():
|
||||
global _statistic
|
||||
|
@ -300,12 +419,12 @@ class MaBank(DataBank):
|
|||
|
||||
def on_coils_change(self, address, from_value, to_value, srv_info):
|
||||
gather('change event', 'coils', address, srv_info.client.port,
|
||||
f'{from_value}⟶{to_value}', self.info)
|
||||
f'{from_value}→{to_value}', self.info)
|
||||
|
||||
def on_holding_registers_change(self, address, from_value, to_value,
|
||||
srv_info):
|
||||
gather('change event', 'holding', address, srv_info.client.port,
|
||||
f'{from_value}⟶{to_value}', self.info)
|
||||
f'{from_value}→{to_value}', self.info)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -316,7 +435,7 @@ if __name__ == '__main__':
|
|||
level=_LOG_LEVEL)
|
||||
_start_time = datetime.datetime.now()
|
||||
|
||||
version = data.get_version()
|
||||
version = data.get_info()['version']
|
||||
logging.info(f'abibas v{version}. Light controllers MODBUS simulator')
|
||||
|
||||
while run():
|
||||
|
@ -339,12 +458,14 @@ if __name__ == '__main__':
|
|||
else:
|
||||
handle_client(instance, c['Model'])
|
||||
sleep(5)
|
||||
calculate_freq(5, datetime.datetime.now())
|
||||
|
||||
report()
|
||||
report_freq()
|
||||
dispose()
|
||||
|
||||
except Exception:
|
||||
except Exception as ex:
|
||||
exc = sys.exception()
|
||||
traceback.print_tb(exc.__traceback__, limit=3, file=sys.stdout)
|
||||
logging.critical('An epic failure just has happened!')
|
||||
logging.critical(f'An epic failure just has happened! ({ex})')
|
||||
dispose()
|
||||
|
|
|
@ -15,7 +15,9 @@ bp = Blueprint('controller_types', __name__)
|
|||
@bp.route('/types')
|
||||
def list():
|
||||
models = data.get_controller_types()
|
||||
return render_template('controller_types/list.html', config=models)
|
||||
return render_template('controller_types/list.html',
|
||||
config=models,
|
||||
footer=data.get_info())
|
||||
|
||||
|
||||
@bp.route('/types/add', methods=("GET", "POST"))
|
||||
|
@ -34,7 +36,8 @@ def add():
|
|||
return redirect(
|
||||
url_for('controller_types.edit', name=request.form['name']))
|
||||
|
||||
return render_template('controller_types/create.html')
|
||||
return render_template('controller_types/create.html',
|
||||
footer=data.get_info())
|
||||
|
||||
|
||||
@bp.route('/types/<name>/edit', methods=("GET", "POST"))
|
||||
|
@ -65,7 +68,9 @@ def edit(name: str):
|
|||
existing = _prepare_for_editing(existing['Name'])
|
||||
break
|
||||
|
||||
return render_template('controller_types/update.html', data=existing)
|
||||
return render_template('controller_types/update.html',
|
||||
data=existing,
|
||||
footer=data.get_info())
|
||||
|
||||
|
||||
@bp.route('/types/<name>/copy', methods=("GET", ))
|
||||
|
@ -94,6 +99,7 @@ def _find_model_by_name(name: str, models: dict = None) -> dict:
|
|||
|
||||
def _prepare_for_editing(name: str):
|
||||
types = ['Input', 'Holding', 'Coil', 'Discrete']
|
||||
variants = ['DropdownVariants', 'RawBinary', 'RawInteger']
|
||||
exists = _find_model_by_name(name)
|
||||
if not exists:
|
||||
return None
|
||||
|
@ -104,6 +110,7 @@ def _prepare_for_editing(name: str):
|
|||
processed.append(f'{k}={v}')
|
||||
reg['PossibleValues'] = '\n'.join(processed)
|
||||
exists['RegTypes'] = types
|
||||
exists['DisplayVariants'] = variants
|
||||
return exists
|
||||
|
||||
|
||||
|
@ -113,7 +120,10 @@ def _save_model(model: dict, current_name: str):
|
|||
del model['RegTypes']
|
||||
_remove_reg(model, '')
|
||||
for r in model['Registers']:
|
||||
r['PossibleValues'] = _str_to_possible(r['PossibleValues'])
|
||||
if 'PossibleValues' in r:
|
||||
r['PossibleValues'] = _str_to_possible(r['PossibleValues'])
|
||||
else:
|
||||
r['PossibleValues'] = []
|
||||
models.insert(0, model)
|
||||
data.set_controller_types(models)
|
||||
|
||||
|
@ -146,6 +156,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}-display-as':
|
||||
updated['DisplayAs'] = v
|
||||
elif k == f'{reg_name}-possible':
|
||||
updated['PossibleValues'] = _str_to_possible(v)
|
||||
_remove_reg(model, reg_name)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
"""Setting up light controllers and monitoring their state"""
|
||||
|
||||
import re
|
||||
import data
|
||||
from flask import (Blueprint, flash, g, redirect, render_template, request,
|
||||
url_for)
|
||||
|
@ -10,8 +11,10 @@ bp = Blueprint('controllers', __name__)
|
|||
|
||||
@bp.route('/', methods=('GET', ))
|
||||
def list():
|
||||
data = get_list()
|
||||
return render_template('controllers/index.html', config=data)
|
||||
lst = get_list()
|
||||
return render_template('controllers/index.html',
|
||||
config=lst,
|
||||
footer=data.get_info())
|
||||
|
||||
|
||||
@bp.route('/override', methods=('POST', ))
|
||||
|
@ -27,13 +30,22 @@ def update():
|
|||
if k == 'enabled':
|
||||
enabled = v
|
||||
continue
|
||||
registers.append({'Name': k, 'Value': int(v)})
|
||||
registers.append({'Name': k, 'Value': v})
|
||||
|
||||
controllers = data.get_controllers()
|
||||
existing = _find(port, controllers)
|
||||
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'])
|
||||
|
||||
data.set_controller_registers(existing['Type'], existing['Port'],
|
||||
registers)
|
||||
enabled = enabled == 'True'
|
||||
|
@ -89,15 +101,31 @@ def add():
|
|||
data.set_controllers(controllers)
|
||||
return redirect(url_for('controllers.list'))
|
||||
|
||||
return render_template('controllers/create.html', types=types)
|
||||
return render_template('controllers/create.html',
|
||||
types=types,
|
||||
footer=data.get_info())
|
||||
|
||||
|
||||
def _get_value_name(value: int, possibleValues: list) -> str:
|
||||
for possible in possibleValues:
|
||||
for k, v in possible.items():
|
||||
if v == value:
|
||||
return k
|
||||
return 'Unknown'
|
||||
def _get_current_value(reg: dict) -> str:
|
||||
if 'DisplayAs' in reg and reg['DisplayAs'] == 'RawBinary':
|
||||
return f'{reg["CurrentValue"]:b}'.zfill(16)
|
||||
else:
|
||||
return reg['CurrentValue']
|
||||
|
||||
|
||||
def _get_value_name(reg: dict) -> str:
|
||||
value = reg['CurrentValue']
|
||||
display_as = reg.get('DisplayAs', 'DropdownVariants')
|
||||
if display_as == 'RawBinary':
|
||||
return value
|
||||
elif display_as == 'RawInteger':
|
||||
return ''
|
||||
else:
|
||||
for possible in reg['PossibleValues']:
|
||||
for k, v in possible.items():
|
||||
if v == value:
|
||||
return k
|
||||
return 'Undefined'
|
||||
|
||||
|
||||
def get_list() -> dict:
|
||||
|
@ -113,9 +141,9 @@ def get_list() -> dict:
|
|||
'Name':
|
||||
reg['Name'],
|
||||
'Value':
|
||||
reg['CurrentValue'],
|
||||
_get_current_value(reg),
|
||||
'ValueStr':
|
||||
_get_value_name(reg['CurrentValue'], reg['PossibleValues']),
|
||||
_get_value_name(reg),
|
||||
'Address':
|
||||
reg['Address'],
|
||||
'Type':
|
||||
|
@ -123,7 +151,9 @@ def get_list() -> dict:
|
|||
'PossibleValues':
|
||||
reg['PossibleValues'],
|
||||
'CanOverride':
|
||||
reg['CanOverride']
|
||||
reg['CanOverride'],
|
||||
'DisplayAs':
|
||||
reg.get('DisplayAs', 'DropdownVariants')
|
||||
})
|
||||
|
||||
return controllers
|
||||
|
@ -134,3 +164,8 @@ def _to_status(enabled: bool) -> str:
|
|||
return 'Yes'
|
||||
else:
|
||||
return 'No'
|
||||
|
||||
|
||||
def _is_binary_str(s: str) -> bool:
|
||||
pattern = r'^[01]+$'
|
||||
return re.match(pattern, s) is not None
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
html {
|
||||
font-family: sans-serif;
|
||||
background: #eee;
|
||||
padding: 1rem;
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
nav {
|
||||
|
@ -186,3 +190,25 @@ label {
|
|||
flex-basis: 100%; /* Take full width of the container */
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: auto;
|
||||
height: 2rem;
|
||||
text-align: center;
|
||||
background-color: #e5bdf1;
|
||||
padding: 0.5rem 0.5rem;
|
||||
opacity: 0.50;
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.grey-box {
|
||||
background-color: #d0d0d7;
|
||||
padding: 0.2rem;
|
||||
border: 1px solid #ccc;
|
||||
display: inline-block;
|
||||
font-size: 0.7rem;
|
||||
border-radius: 0.3rem
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
<title>{% block title %}{% endblock %}</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
</head>
|
||||
|
||||
<nav>
|
||||
<h1>Abibas (Automated Beacon Interface for Budget-friendly Aviation Safety)</h1>
|
||||
<ul>
|
||||
|
@ -22,4 +23,7 @@
|
|||
{% block content %}
|
||||
{% endblock %}
|
||||
</section>
|
||||
<footer>
|
||||
<p>Version: {{ footer['version'] }} ⊚ Author: {{ footer['author'] }} ⊚ License: {{ footer['license'] }} </p>
|
||||
</footer>
|
||||
</html>
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block header %}
|
||||
<h1>{% block title %}New Controller Type{% endblock %}</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
<div>
|
||||
<div><label for="name">Name:</label></div>
|
||||
<div><input name="name" id="name" value="{{ request.form['name'] }}" required></div>
|
||||
</div>
|
||||
<br/>
|
||||
<div>
|
||||
<div><label for="runAs">Run As:</label></div>
|
||||
<div><select id="runAs" name="runAs">
|
||||
<option value="Server">Server</option>
|
||||
<option value="Client">Client</option>
|
||||
</select></div>
|
||||
</div>
|
||||
<br/>
|
||||
<div>
|
||||
<input type="submit" value="Add">
|
||||
</div>
|
||||
</form>
|
||||
<p>Note: once new type is created, you can Edit the type to add necessary registers. Changing "Run As" option will not be possible</p>
|
||||
{% endblock %}
|
|
@ -19,7 +19,9 @@
|
|||
<div class="setting-wide">
|
||||
<div><label for="reg-{{ reg['Name'] }}">{{ reg['Type'] }} {{ reg['Address'] }} ({{ reg['Name'] }}):</label></div>
|
||||
|
||||
<div><select id="reg-{{ reg['Name'] }}" name="{{ reg['Name'] }}">
|
||||
<div>
|
||||
{% if reg['PossibleValues'] %}
|
||||
<select id="reg-{{ reg['Name'] }}" name="{{ reg['Name'] }}">
|
||||
{% for option in reg['PossibleValues'] %}
|
||||
{% for key, value in option.items() %}
|
||||
|
||||
|
@ -27,7 +29,14 @@
|
|||
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</select></div>
|
||||
</select>
|
||||
|
||||
{% else %}
|
||||
|
||||
<div class="grey-box">{{ reg['DisplayAs'] }}</div>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endfor %}
|
||||
|
|
|
@ -61,10 +61,17 @@
|
|||
<div><label for="{{ reg['Name'] }}-address">Address:</label></div>
|
||||
<div><input name="{{ reg['Name'] }}-address" id="{{ reg['Name'] }}-address" value="{{ request.form[reg['Name'] ~ '-address'] or reg['Address'] }}" placeholder="12345" required></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div><label for="{{ reg['Name'] }}-display-as">Display value as:</label></div>
|
||||
<div><select id="{{ reg['Name'] }}-display-as" name="{{ reg['Name'] }}-display-as">
|
||||
{% for variant in data['DisplayVariants'] %}
|
||||
<option value={{ variant }} {% if variant == reg['DisplayAs'] %}selected="selected"{% endif %}>{{ variant }}</option>
|
||||
{% endfor %}
|
||||
</select></div>
|
||||
</div>
|
||||
<div>
|
||||
<div><label for="{{ reg['Name'] }}-possible">Possible values:</label></div>
|
||||
<div><textarea name="{{ reg['Name'] }}-possible" id="{{ reg['Name'] }}-possible" rows="6" cols="28" placeholder="ValueNameOne=14 ValueNameTwo=3" required>{{ request.form[reg['Name'] ~ '-possible'] or reg['PossibleValues'] }}</textarea></div>
|
||||
<div><textarea name="{{ reg['Name'] }}-possible" id="{{ reg['Name'] }}-possible" rows="6" cols="28" placeholder="ValueNameOne=14 ValueNameTwo=3" {% if reg['DisplayAs'] == 'DropdownVariants' %} required {% else %} disabled {% endif %} >{{ request.form[reg['Name'] ~ '-possible'] or reg['PossibleValues'] }}</textarea></div>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
<div class="setting">
|
||||
<label {% if not reg['CanOverride'] %} class="red" {% endif %}>{{ reg['Name'] }} ({{ reg['Type'] }}):</label>
|
||||
<div>[{{ '%05d' % reg['Address'] }}] ➡ {{ reg['Value'] }} ({{ reg['ValueStr'] }})
|
||||
<div>[{{ '%05d' % reg['Address'] }}] ➡ {{ reg['Value'] }} {% if reg['ValueStr'] != '' %}({{ reg['ValueStr'] }}){% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -49,23 +49,25 @@
|
|||
</select></div>
|
||||
</div>
|
||||
|
||||
|
||||
{% for reg in controller['Registers'] %}
|
||||
{% if reg['CanOverride'] %}
|
||||
|
||||
<div class="setting-wide">
|
||||
<div><label for="reg-{{ reg['Name'] }}">{{ reg['Name'] }} value:</label></div>
|
||||
|
||||
{% if not reg['DisplayAs'] or reg['DisplayAs'] == 'DropdownVariants' %}
|
||||
|
||||
<div><select id="reg-{{ reg['Name'] }}" name="{{ reg['Name'] }}">
|
||||
{% for option in reg['PossibleValues'] %}
|
||||
{% for key, value in option.items() %}
|
||||
{% if value == reg['Value'] %}
|
||||
<option value="{{ value }}" selected>{{ key }} ({{ value }})</option>
|
||||
{% else %}
|
||||
<option value="{{ value }}">{{ key }} ({{ value }})</option>
|
||||
{% endif %}
|
||||
<option value="{{ value }}" {% if value == reg['Value'] %}selected{% endif %}>{{ key }} ({{ value }})</option>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</select></div>
|
||||
</select></div>
|
||||
|
||||
{% else %}
|
||||
<div><input id="reg-{{ reg['Name'] }}" name="{{ reg['Name'] }}" value="{{ reg['Value'] }}" required></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
|
Loading…
Reference in New Issue