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:
tar 2023-07-16 12:37:39 +00:00
commit 4607dcc8ca
15 changed files with 420 additions and 120 deletions

View File

@ -29,7 +29,7 @@ Setup & debug
+ ``pip list`` (pip show abibas, pip uninstall abibas, etc) + ``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: At this stage the package is installed in editable mode, to run it in debug mode:
,
+ ``abibas-debug`` + ``abibas-debug``
or in normal mode: 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. for the changes in simulator though the tool has to be restarted.
**main** module can be manipulated for debugging purposes as well **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/*``

View File

@ -25,6 +25,7 @@ dependencies = [
"Flask", "Flask",
"pyModbusTCP", "pyModbusTCP",
"prettytable", "prettytable",
"plotext",
] ]
version = "0.0.1" version = "0.0.1"
urls = {homepage = "https://gittar.crabdance.com/e-corp/abibas"} urls = {homepage = "https://gittar.crabdance.com/e-corp/abibas"}

View File

@ -34,8 +34,11 @@ def _get_controller_models_fname() -> str:
def _get_reg_value(controller: dict, reg: dict) -> int: def _get_reg_value(controller: dict, reg: dict) -> int:
# get first value from type definition (as default) # get first value from type definition (as default)
value = None value = None
k, v = next(iter(reg['PossibleValues'][0].items()), None) if 'PossibleValues' not in reg or not reg['PossibleValues']:
value = v value = 0
else:
k, v = next(iter(reg['PossibleValues'][0].items()), None)
value = v
# if there is a file with overriden value, take that # if there is a file with overriden value, take that
overrides = _get_overrides_fname(controller['Type'], controller['Port']) overrides = _get_overrides_fname(controller['Type'], controller['Port'])
if os.path.isfile(overrides): 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']] entry = [x for x in overriden if x['Name'] == reg['Name']]
if entry: if entry:
value = entry[0]['Value'] value = entry[0]['Value']
# logging.debug( logging.debug(
# f'{reg["Name"]} value gotten: {value} from {overrides}') f'{reg["Name"]} value gotten: {value} from {overrides}')
return value return value
@ -61,11 +64,6 @@ def _save_overrides(fname: str, overrides: list):
json.dump(overrides, file, indent=2) 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: def get_controller(port: int) -> dict:
controllers = get_controllers() controllers = get_controllers()
models = get_controller_types() models = get_controller_types()
@ -164,3 +162,10 @@ def remove_controller(port: int) -> bool:
logging.debug(f'Overrides file {overrides} not found, skip removing') logging.debug(f'Overrides file {overrides} not found, skip removing')
return True return True
def get_info() -> dict:
distribution = pkg_resources.get_distribution('abibas')
return dict(version=distribution.version,
author='Taryel Hlontsi',
license='copyleft (GPLv3)')

View File

@ -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", "Name": "CIP300",
"RunAs": "Server", "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", "Name": "CIP400",
"RunAs": "Server", "RunAs": "Server",

View File

@ -11,12 +11,6 @@
"Ip": "127.0.0.1", "Ip": "127.0.0.1",
"Port": 11507 "Port": 11507
}, },
{
"Type": "Quantec",
"Enabled": true,
"Ip": "127.0.0.1",
"Port": 11701
},
{ {
"Type": "Quantec", "Type": "Quantec",
"Enabled": true, "Enabled": true,
@ -28,5 +22,11 @@
"Enabled": true, "Enabled": true,
"Ip": "127.0.0.1", "Ip": "127.0.0.1",
"Port": 11508 "Port": 11508
},
{
"Type": "CIP400",
"Enabled": true,
"Ip": "127.0.0.1",
"Port": 11506
} }
] ]

View File

@ -11,8 +11,7 @@ def run():
def run_debug(): def run_debug():
ui = _run_flask_debug() ui = _run_flask_debug()
print(f'UI started: PID {ui.pid}') print(f'UI started: PID {ui.pid}')
#ui.wait() _run_simulator('debug')
_run_simulator('info')
def _run_flask_debug() -> subprocess.Popen: def _run_flask_debug() -> subprocess.Popen:

View File

@ -11,6 +11,7 @@ import signal
import data import data
import sys import sys
import traceback import traceback
import plotext as plt
_RUN_FOR_SEC = 10 _RUN_FOR_SEC = 10
_LOG_LEVEL = 20 # INFO _LOG_LEVEL = 20 # INFO
@ -18,6 +19,8 @@ _LOG_LEVEL = 20 # INFO
_statistic = {} _statistic = {}
_start_time = None _start_time = None
_active = {} _active = {}
_inbound_read_freq = {}
_inbound_write_freq = {}
def get_run_options(): 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, filter(lambda x: x['Type'] == 'Holding' and x['CanOverride'] is True,
registers), None) registers), None)
inputRegs = filter(lambda x: x['Type'] == 'Input', 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}
if holdingRead: if holdingRead:
result = instance.read_holding_registers(holdingRead['Address'], 1) 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') logging.debug(f'{ct["Name"]} client@{instance.port} no connection')
else: else:
gather('read', 'holding', holdingRead['Address'], instance.port, gather('read', 'holding', holdingRead['Address'], instance.port,
result[0], info, 'outbound') result[0], info, 'outbound')
data.set_controller_register(info['Model'], info['Port'],
holdingRead['Name'], result[0])
if holdingWrite: if holdingWrite:
v = holdingWrite['CurrentValue'] v = holdingWrite['CurrentValue']
instance.write_single_register(holdingWrite['Address'], v) instance.write_single_register(holdingWrite['Address'], v)
gather('write', 'holding', holdingWrite['Address'], gather('write', 'holding', holdingWrite['Address'],
instance.port, v, info, 'outbound') 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: else:
logging.warning( logging.warning(
f'{ct["Name"]} client@{instance.port} is not fully configured') 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 Returns
------- -------
void None
""" """
global _active global _active
global _statistic global _statistic
global _inbound_read_freq
global _inbound_write_freq
controller = f'{info["Model"]}@{info["Port"]}' controller = f'{info["Model"]}@{info["Port"]}'
try: try:
# client port here is a port of incoming connection # client port here is a port of incoming connection
@ -216,6 +237,104 @@ def gather(op, registry, address, port, value, info, tag='inbound'):
else: else:
logging.debug(msg) 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(): def report():
global _statistic global _statistic
@ -300,12 +419,12 @@ class MaBank(DataBank):
def on_coils_change(self, address, from_value, to_value, srv_info): def on_coils_change(self, address, from_value, to_value, srv_info):
gather('change event', 'coils', address, srv_info.client.port, 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, def on_holding_registers_change(self, address, from_value, to_value,
srv_info): srv_info):
gather('change event', 'holding', address, srv_info.client.port, 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__': if __name__ == '__main__':
@ -316,7 +435,7 @@ if __name__ == '__main__':
level=_LOG_LEVEL) level=_LOG_LEVEL)
_start_time = datetime.datetime.now() _start_time = datetime.datetime.now()
version = data.get_version() version = data.get_info()['version']
logging.info(f'abibas v{version}. Light controllers MODBUS simulator') logging.info(f'abibas v{version}. Light controllers MODBUS simulator')
while run(): while run():
@ -339,12 +458,14 @@ if __name__ == '__main__':
else: else:
handle_client(instance, c['Model']) handle_client(instance, c['Model'])
sleep(5) sleep(5)
calculate_freq(5, datetime.datetime.now())
report() report()
report_freq()
dispose() dispose()
except Exception: except Exception as ex:
exc = sys.exception() exc = sys.exception()
traceback.print_tb(exc.__traceback__, limit=3, file=sys.stdout) 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() dispose()

View File

@ -15,7 +15,9 @@ bp = Blueprint('controller_types', __name__)
@bp.route('/types') @bp.route('/types')
def list(): def list():
models = data.get_controller_types() 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")) @bp.route('/types/add', methods=("GET", "POST"))
@ -34,7 +36,8 @@ def add():
return redirect( return redirect(
url_for('controller_types.edit', name=request.form['name'])) 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")) @bp.route('/types/<name>/edit', methods=("GET", "POST"))
@ -65,7 +68,9 @@ def edit(name: str):
existing = _prepare_for_editing(existing['Name']) existing = _prepare_for_editing(existing['Name'])
break 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", )) @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): def _prepare_for_editing(name: str):
types = ['Input', 'Holding', 'Coil', 'Discrete'] types = ['Input', 'Holding', 'Coil', 'Discrete']
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
@ -104,6 +110,7 @@ def _prepare_for_editing(name: str):
processed.append(f'{k}={v}') processed.append(f'{k}={v}')
reg['PossibleValues'] = '\n'.join(processed) reg['PossibleValues'] = '\n'.join(processed)
exists['RegTypes'] = types exists['RegTypes'] = types
exists['DisplayVariants'] = variants
return exists return exists
@ -113,7 +120,10 @@ def _save_model(model: dict, current_name: str):
del model['RegTypes'] del model['RegTypes']
_remove_reg(model, '') _remove_reg(model, '')
for r in model['Registers']: 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) models.insert(0, model)
data.set_controller_types(models) data.set_controller_types(models)
@ -146,6 +156,8 @@ 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}-display-as':
updated['DisplayAs'] = v
elif k == f'{reg_name}-possible': elif k == f'{reg_name}-possible':
updated['PossibleValues'] = _str_to_possible(v) updated['PossibleValues'] = _str_to_possible(v)
_remove_reg(model, reg_name) _remove_reg(model, reg_name)

View File

@ -1,5 +1,6 @@
"""Setting up light controllers and monitoring their state""" """Setting up light controllers and monitoring their state"""
import re
import data import data
from flask import (Blueprint, flash, g, redirect, render_template, request, from flask import (Blueprint, flash, g, redirect, render_template, request,
url_for) url_for)
@ -10,8 +11,10 @@ bp = Blueprint('controllers', __name__)
@bp.route('/', methods=('GET', )) @bp.route('/', methods=('GET', ))
def list(): def list():
data = get_list() lst = get_list()
return render_template('controllers/index.html', config=data) return render_template('controllers/index.html',
config=lst,
footer=data.get_info())
@bp.route('/override', methods=('POST', )) @bp.route('/override', methods=('POST', ))
@ -27,13 +30,22 @@ def update():
if k == 'enabled': if k == 'enabled':
enabled = v enabled = v
continue continue
registers.append({'Name': k, 'Value': int(v)}) registers.append({'Name': k, 'Value': v})
controllers = data.get_controllers() controllers = data.get_controllers()
existing = _find(port, controllers) existing = _find(port, controllers)
if existing is None: if existing is None:
abort(404) 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'], data.set_controller_registers(existing['Type'], existing['Port'],
registers) registers)
enabled = enabled == 'True' enabled = enabled == 'True'
@ -89,15 +101,31 @@ def add():
data.set_controllers(controllers) data.set_controllers(controllers)
return redirect(url_for('controllers.list')) 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: def _get_current_value(reg: dict) -> str:
for possible in possibleValues: if 'DisplayAs' in reg and reg['DisplayAs'] == 'RawBinary':
for k, v in possible.items(): return f'{reg["CurrentValue"]:b}'.zfill(16)
if v == value: else:
return k return reg['CurrentValue']
return 'Unknown'
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: def get_list() -> dict:
@ -113,9 +141,9 @@ def get_list() -> dict:
'Name': 'Name':
reg['Name'], reg['Name'],
'Value': 'Value':
reg['CurrentValue'], _get_current_value(reg),
'ValueStr': 'ValueStr':
_get_value_name(reg['CurrentValue'], reg['PossibleValues']), _get_value_name(reg),
'Address': 'Address':
reg['Address'], reg['Address'],
'Type': 'Type':
@ -123,7 +151,9 @@ def get_list() -> dict:
'PossibleValues': 'PossibleValues':
reg['PossibleValues'], reg['PossibleValues'],
'CanOverride': 'CanOverride':
reg['CanOverride'] reg['CanOverride'],
'DisplayAs':
reg.get('DisplayAs', 'DropdownVariants')
}) })
return controllers return controllers
@ -134,3 +164,8 @@ def _to_status(enabled: bool) -> str:
return 'Yes' return 'Yes'
else: else:
return 'No' return 'No'
def _is_binary_str(s: str) -> bool:
pattern = r'^[01]+$'
return re.match(pattern, s) is not None

View File

@ -1,8 +1,12 @@
html { html {
font-family: sans-serif; font-family: sans-serif;
background: #eee; background: #eee;
padding: 1rem; }
margin: 0.5rem;
body {
min-height: 100vh;
display: flex;
flex-direction: column;
} }
nav { nav {
@ -186,3 +190,25 @@ label {
flex-basis: 100%; /* Take full width of the container */ 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
}

View File

@ -5,6 +5,7 @@
<title>{% block title %}{% endblock %}</title> <title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head> </head>
<nav> <nav>
<h1>Abibas (Automated Beacon Interface for Budget-friendly Aviation Safety)</h1> <h1>Abibas (Automated Beacon Interface for Budget-friendly Aviation Safety)</h1>
<ul> <ul>
@ -22,4 +23,7 @@
{% block content %} {% block content %}
{% endblock %} {% endblock %}
</section> </section>
<footer>
<p>Version: {{ footer['version'] }} ⊚ Author: {{ footer['author'] }} ⊚ License: {{ footer['license'] }} </p>
</footer>
</html> </html>

View File

@ -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 %}

View File

@ -19,7 +19,9 @@
<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>
<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 option in reg['PossibleValues'] %}
{% for key, value in option.items() %} {% for key, value in option.items() %}
@ -27,7 +29,14 @@
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}
</select></div> </select>
{% else %}
<div class="grey-box">{{ reg['DisplayAs'] }}</div>
{% endif %}
</div>
</div> </div>
{% endfor %} {% endfor %}

View File

@ -61,10 +61,17 @@
<div><label for="{{ reg['Name'] }}-address">Address:</label></div> <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><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>
<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>
<div><label for="{{ reg['Name'] }}-possible">Possible values:</label></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&#13;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&#13;ValueNameTwo=3" {% if reg['DisplayAs'] == 'DropdownVariants' %} required {% else %} disabled {% endif %} >{{ request.form[reg['Name'] ~ '-possible'] or reg['PossibleValues'] }}</textarea></div>
</div> </div>
</article> </article>
{% endfor %} {% endfor %}

View File

@ -25,7 +25,7 @@
<div class="setting"> <div class="setting">
<label {% if not reg['CanOverride'] %} class="red" {% endif %}>{{ reg['Name'] }} ({{ reg['Type'] }}):</label> <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>
</div> </div>
@ -49,23 +49,25 @@
</select></div> </select></div>
</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">
<div><label for="reg-{{ reg['Name'] }}">{{ reg['Name'] }} value:</label></div> <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'] }}"> <div><select id="reg-{{ reg['Name'] }}" name="{{ reg['Name'] }}">
{% for option in reg['PossibleValues'] %} {% for option in reg['PossibleValues'] %}
{% for key, value in option.items() %} {% for key, value in option.items() %}
{% if value == reg['Value'] %} <option value="{{ value }}" {% if value == reg['Value'] %}selected{% endif %}>{{ key }} ({{ value }})</option>
<option value="{{ value }}" selected>{{ key }} ({{ value }})</option>
{% else %}
<option value="{{ value }}">{{ key }} ({{ value }})</option>
{% endif %}
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}
</select></div> </select></div>
{% else %}
<div><input id="reg-{{ reg['Name'] }}" name="{{ reg['Name'] }}" value="{{ reg['Value'] }}" required></div>
{% endif %}
</div> </div>
{% endif %} {% endif %}