benchlab-pytools 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- benchlab/__init__.py +14 -0
- benchlab/__main__.py +21 -0
- benchlab/config/README.md +325 -0
- benchlab/config/__init__.py +9 -0
- benchlab/config/config_client.py +631 -0
- benchlab/config/config_manager.py +495 -0
- benchlab/config/config_tool.py +358 -0
- benchlab/config/diff.py +163 -0
- benchlab/config/examples/complete_config.json +56 -0
- benchlab/config/examples/rgb_lighting.json +24 -0
- benchlab/config/examples/simple_fan.json +32 -0
- benchlab/config/schema.py +138 -0
- benchlab/core/README.md +154 -0
- benchlab/core/__init__.py +78 -0
- benchlab/core/config.py +145 -0
- benchlab/core/datasource.py +1209 -0
- benchlab/core/datasource_manager.py +418 -0
- benchlab/core/device_registry.py +206 -0
- benchlab/core/discovery.py +92 -0
- benchlab/core/infrastructure.py +309 -0
- benchlab/core/process_manager.py +356 -0
- benchlab/core/retry.py +85 -0
- benchlab/core/shared_serial.py +235 -0
- benchlab/core/statistics.py +224 -0
- benchlab/csv_log/README.md +206 -0
- benchlab/csv_log/__init__.py +5 -0
- benchlab/csv_log/csv_logger_enhanced.py +416 -0
- benchlab/csv_log/message_batcher.py +361 -0
- benchlab/csv_log/requirements.txt +3 -0
- benchlab/csv_log/smart_retry.py +466 -0
- benchlab/graph/README.md +117 -0
- benchlab/graph/__init__.py +5 -0
- benchlab/graph/app.py +353 -0
- benchlab/graph/requirements.txt +2 -0
- benchlab/graph/runner.py +42 -0
- benchlab/graph/ui.py +288 -0
- benchlab/hwinfo/README.md +135 -0
- benchlab/hwinfo/__init__.py +5 -0
- benchlab/hwinfo/hwinfo_export.py +386 -0
- benchlab/hwinfo/requirements.txt +1 -0
- benchlab/launcher.py +387 -0
- benchlab/link/.env.EXAMPLE +10 -0
- benchlab/link/README.md +172 -0
- benchlab/link/__init__.py +1 -0
- benchlab/link/link_main.py +479 -0
- benchlab/main.py +462 -0
- benchlab/menu.py +729 -0
- benchlab/menu_classic.py +410 -0
- benchlab/mqtt/README.md +201 -0
- benchlab/mqtt/__init__.py +5 -0
- benchlab/mqtt/mqtt.config_template +2 -0
- benchlab/mqtt/mqtt_publisher.py +540 -0
- benchlab/mqtt/requirements.txt +3 -0
- benchlab/prefs.py +67 -0
- benchlab/restapi/.env.example +17 -0
- benchlab/restapi/__init__.py +5 -0
- benchlab/restapi/favicon.ico +0 -0
- benchlab/restapi/readme.md +202 -0
- benchlab/restapi/requirements.txt +5 -0
- benchlab/restapi/telemetry_api.py +808 -0
- benchlab/sources.py +515 -0
- benchlab/tools.py +147 -0
- benchlab/tui/README.md +266 -0
- benchlab/tui/__init__.py +5 -0
- benchlab/tui/config.py +477 -0
- benchlab/tui/requirements.txt +2 -0
- benchlab/tui/tui_core.py +1298 -0
- benchlab/tui/tui_main.py +418 -0
- benchlab/vu/README.md +64 -0
- benchlab/vu/VU-Server/NOTICE.md +13 -0
- benchlab/vu/VU-Server/README.md +80 -0
- benchlab/vu/VU-Server/Running_from_source_on_linux.md +97 -0
- benchlab/vu/VU-Server/__init__.py +0 -0
- benchlab/vu/VU-Server/assets/vu1_hello_world.png +0 -0
- benchlab/vu/VU-Server/community_applications.md +13 -0
- benchlab/vu/VU-Server/config.yaml +9 -0
- benchlab/vu/VU-Server/database.py +256 -0
- benchlab/vu/VU-Server/dial_driver.py +534 -0
- benchlab/vu/VU-Server/dials/Comms_Hub_Gauge.py +78 -0
- benchlab/vu/VU-Server/dials/Comms_Hub_Server.py +100 -0
- benchlab/vu/VU-Server/dials/__init__.py +0 -0
- benchlab/vu/VU-Server/dials/base_logger.py +100 -0
- benchlab/vu/VU-Server/installer/inc/icon.ico +0 -0
- benchlab/vu/VU-Server/installer/install.nsi +148 -0
- benchlab/vu/VU-Server/make_version.py +40 -0
- benchlab/vu/VU-Server/pylintrc +628 -0
- benchlab/vu/VU-Server/requirements.txt +9 -0
- benchlab/vu/VU-Server/serial_driver.py +244 -0
- benchlab/vu/VU-Server/server.py +670 -0
- benchlab/vu/VU-Server/server_config.py +255 -0
- benchlab/vu/VU-Server/server_dial_handler.py +334 -0
- benchlab/vu/VU-Server/vu_notifications.py +24 -0
- benchlab/vu/VU-Server/www/android-chrome-192x192.png +0 -0
- benchlab/vu/VU-Server/www/android-chrome-512x512.png +0 -0
- benchlab/vu/VU-Server/www/apple-touch-icon.png +0 -0
- benchlab/vu/VU-Server/www/assets/css/demo.css +291 -0
- benchlab/vu/VU-Server/www/assets/css/demo.min.css +9 -0
- benchlab/vu/VU-Server/www/assets/css/demo.rtl.css +276 -0
- benchlab/vu/VU-Server/www/assets/css/demo.rtl.min.css +9 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-flags.css +1084 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-flags.min.css +9 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-flags.rtl.css +1084 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-flags.rtl.min.css +9 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-payments.css +528 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-payments.min.css +9 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-payments.rtl.css +528 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-payments.rtl.min.css +9 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-social.css +10 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-social.min.css +8 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-social.rtl.css +10 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-social.rtl.min.css +8 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-vendors.css +1504 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-vendors.min.css +9 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-vendors.rtl.css +1504 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-vendors.rtl.min.css +9 -0
- benchlab/vu/VU-Server/www/assets/css/tabler.css +25813 -0
- benchlab/vu/VU-Server/www/assets/css/tabler.min.css +13 -0
- benchlab/vu/VU-Server/www/assets/css/tabler.rtl.css +25777 -0
- benchlab/vu/VU-Server/www/assets/css/tabler.rtl.min.css +13 -0
- benchlab/vu/VU-Server/www/assets/css/theme.min.css +261 -0
- benchlab/vu/VU-Server/www/assets/img/header.png +0 -0
- benchlab/vu/VU-Server/www/assets/img/logo.png +0 -0
- benchlab/vu/VU-Server/www/assets/js/dark-theme.js +35 -0
- benchlab/vu/VU-Server/www/assets/js/dark-theme.min.js +9 -0
- benchlab/vu/VU-Server/www/assets/js/demo.js +132 -0
- benchlab/vu/VU-Server/www/assets/js/demo.min.js +9 -0
- benchlab/vu/VU-Server/www/assets/js/jquery.min.js +2 -0
- benchlab/vu/VU-Server/www/assets/js/tabler.esm.js +7482 -0
- benchlab/vu/VU-Server/www/assets/js/tabler.esm.min.js +15 -0
- benchlab/vu/VU-Server/www/assets/js/tabler.js +7543 -0
- benchlab/vu/VU-Server/www/assets/js/tabler.min.js +15 -0
- benchlab/vu/VU-Server/www/assets/js/vu1_api.js +68 -0
- benchlab/vu/VU-Server/www/assets/js/vu1_gui_api_key_settings.js +167 -0
- benchlab/vu/VU-Server/www/assets/js/vu1_gui_api_keys.js +138 -0
- benchlab/vu/VU-Server/www/assets/js/vu1_gui_dial.js +179 -0
- benchlab/vu/VU-Server/www/assets/js/vu1_gui_home.js +60 -0
- benchlab/vu/VU-Server/www/assets/js/vu1_gui_root.js +125 -0
- benchlab/vu/VU-Server/www/favicon-16x16.png +0 -0
- benchlab/vu/VU-Server/www/favicon-32x32.png +0 -0
- benchlab/vu/VU-Server/www/favicon.ico +0 -0
- benchlab/vu/VU-Server/www/favicon.png +0 -0
- benchlab/vu/VU-Server/www/index.html +47 -0
- benchlab/vu/VU-Server/www/views/api_keys.html +151 -0
- benchlab/vu/VU-Server/www/views/dial.html +286 -0
- benchlab/vu/VU-Server/www/views/footer.html +27 -0
- benchlab/vu/VU-Server/www/views/header.html +86 -0
- benchlab/vu/VU-Server/www/views/key_settings.html +132 -0
- benchlab/vu/VU-Server/www/views/start.html +124 -0
- benchlab/vu/__init__.py +5 -0
- benchlab/vu/assets/Barlow-Bold.ttf +0 -0
- benchlab/vu/assets/bl_dial_144x200.png +0 -0
- benchlab/vu/assets/bl_logo_144x200.png +0 -0
- benchlab/vu/devices.py +166 -0
- benchlab/vu/requirements.txt +24 -0
- benchlab/vu/sensors.py +18 -0
- benchlab/vu/vu_dial.config_template +90 -0
- benchlab/vu/vu_logo_gen.py +97 -0
- benchlab/vu/vu_server.config_template +5 -0
- benchlab/vu/vu_server_config.py +40 -0
- benchlab/vu/vu_server_manager.py +192 -0
- benchlab/vu/vu_tui.py +919 -0
- benchlab/vu/vu_updater.py +540 -0
- benchlab/wigidash/README.md +182 -0
- benchlab/wigidash/__init__.py +5 -0
- benchlab/wigidash/assets/Inter-Bold.ttf +0 -0
- benchlab/wigidash/assets/Inter.ttf +0 -0
- benchlab/wigidash/assets/benchlab.png +0 -0
- benchlab/wigidash/benchlab_fleet.py +150 -0
- benchlab/wigidash/benchlab_graph.py +517 -0
- benchlab/wigidash/benchlab_overview.py +426 -0
- benchlab/wigidash/benchlab_telemetry.py +105 -0
- benchlab/wigidash/benchlab_ui.py +190 -0
- benchlab/wigidash/benchlab_utils.py +182 -0
- benchlab/wigidash/requirements.txt +9 -0
- benchlab/wigidash/wigidash_device.py +541 -0
- benchlab/wigidash/wigidash_manager.py +342 -0
- benchlab/wigidash/wigidash_session.py +355 -0
- benchlab/wigidash/wigidash_usb.py +247 -0
- benchlab/wigidash/wigidash_widget.py +88 -0
- benchlab_pytools-3.0.0.dist-info/METADATA +239 -0
- benchlab_pytools-3.0.0.dist-info/RECORD +184 -0
- benchlab_pytools-3.0.0.dist-info/WHEEL +4 -0
- benchlab_pytools-3.0.0.dist-info/entry_points.txt +2 -0
- benchlab_pytools-3.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# pylint: disable=E1101
|
|
2
|
+
import os
|
|
3
|
+
from ruamel.yaml import YAML
|
|
4
|
+
# import yaml
|
|
5
|
+
from dials.base_logger import logger
|
|
6
|
+
from vu_notifications import show_error_msg, show_warning_msg
|
|
7
|
+
import database as db
|
|
8
|
+
|
|
9
|
+
class ServerConfig:
|
|
10
|
+
config_path = None
|
|
11
|
+
server = None
|
|
12
|
+
hardware = None
|
|
13
|
+
server_default = {'hostname': 'localhost', 'port': 3000, 'communication_timeout': 10, 'master_key': 'cTpAWYuRpA2zx75Yh961Cg' }
|
|
14
|
+
hardware_default = {'port': None }
|
|
15
|
+
dials = {}
|
|
16
|
+
api_keys = {}
|
|
17
|
+
database = None
|
|
18
|
+
|
|
19
|
+
def __init__(self, config_file='config.yaml'):
|
|
20
|
+
self.config_path = os.path.join(os.path.dirname(__file__), config_file)
|
|
21
|
+
logger.info(f"VU1 config yaml file: {self.config_path}")
|
|
22
|
+
self.database = db.DialsDB(init_if_missing=True)
|
|
23
|
+
self._load_config() # Load configuration from .yaml file
|
|
24
|
+
self._load_API_keys() # Load API keys from `api_keys` section
|
|
25
|
+
self.debug_config()
|
|
26
|
+
|
|
27
|
+
# Save current configuration to .yaml file
|
|
28
|
+
def _save_config(self):
|
|
29
|
+
config = None
|
|
30
|
+
yaml = YAML(typ='safe', pure=True)
|
|
31
|
+
|
|
32
|
+
if os.path.exists(self.config_path):
|
|
33
|
+
with open(self.config_path, 'r', encoding="utf-8") as file:
|
|
34
|
+
config = yaml.load(file) # pylint: disable=assignment-from-no-return
|
|
35
|
+
|
|
36
|
+
if not config:
|
|
37
|
+
config = {}
|
|
38
|
+
|
|
39
|
+
# Update server config
|
|
40
|
+
if not config.get('server'):
|
|
41
|
+
config['server'] = self.server
|
|
42
|
+
config['port'] = self.server['port']
|
|
43
|
+
config['communication_timeout'] = 5000
|
|
44
|
+
else:
|
|
45
|
+
config['server']['hostname'] = self.server['hostname']
|
|
46
|
+
config['server']['port'] = self.server['port']
|
|
47
|
+
config['server']['communication_timeout'] = self.server['communication_timeout']
|
|
48
|
+
|
|
49
|
+
with open(self.config_path, 'w', encoding="utf-8") as file:
|
|
50
|
+
yaml.dump(config, file)
|
|
51
|
+
|
|
52
|
+
# Read .yaml config file
|
|
53
|
+
def _load_config(self):
|
|
54
|
+
if not os.path.exists(self.config_path):
|
|
55
|
+
logger.error(f"Can not load config. Config file '{self.config_path}' does not exist!")
|
|
56
|
+
show_error_msg("Can not find config.yaml", f"Config file '{self.config_path}' is missing!\r\n"\
|
|
57
|
+
"Please fix this issue by creating a default config.yaml file in the VU Server directory.\r\n"\
|
|
58
|
+
"Using default values for this session.")
|
|
59
|
+
self._create_default_config()
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
yaml = YAML(typ='safe', pure=True)
|
|
63
|
+
with open(self.config_path, 'r', encoding="utf-8") as file:
|
|
64
|
+
cfg = yaml.load(file) # pylint: disable=assignment-from-no-return
|
|
65
|
+
|
|
66
|
+
if cfg is None:
|
|
67
|
+
show_error_msg("Empty/corrupt config file!", "Config file exists but it is empty or corrupt!\r\n"\
|
|
68
|
+
"Using defaul values for this session.")
|
|
69
|
+
self._create_default_config()
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
# Check that config file meets the minimum requirements
|
|
73
|
+
if not isinstance(cfg, dict) or 'server' not in cfg or 'hardware' not in cfg:
|
|
74
|
+
show_warning_msg("Missing Key", f"Config file '{self.config_path}' \r\n"\
|
|
75
|
+
"Must have valid entries for 'server' and 'hardware' configuration!\r\n"\
|
|
76
|
+
"Using defaul values for this session.")
|
|
77
|
+
cfg = {}
|
|
78
|
+
cfg['server'] = self.server_default
|
|
79
|
+
cfg['hardware'] = self.hardware_default
|
|
80
|
+
|
|
81
|
+
elif not isinstance(cfg['server'], dict):
|
|
82
|
+
show_warning_msg("Invalid server config", f"Config file '{self.config_path}' \r\n"\
|
|
83
|
+
"Has invalid `server` config entry.\r\n"\
|
|
84
|
+
"Using defaul values for this session.")
|
|
85
|
+
self._force_default_config()
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
elif not isinstance(cfg['hardware'], dict):
|
|
89
|
+
show_warning_msg("Invalid server config", f"Config file '{self.config_path}' \r\n"\
|
|
90
|
+
"Has invalid `hardware` config entry.\r\n"\
|
|
91
|
+
"Using defaul values for this session.")
|
|
92
|
+
self._force_default_config()
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
elif ('hostname' not in cfg['server'] or
|
|
96
|
+
'port' not in cfg['server'] or
|
|
97
|
+
'communication_timeout' not in cfg['server'] or
|
|
98
|
+
'master_key' not in cfg['server']):
|
|
99
|
+
show_warning_msg("Missing Key", f"Config file '{self.config_path}' \r\n"\
|
|
100
|
+
"must have `hostname`, `port`, `communication_timeout` and `master_key` entries!\r\n"\
|
|
101
|
+
"Using defaul values for this session.")
|
|
102
|
+
self._force_default_config()
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
elif 'port' not in cfg['hardware']:
|
|
106
|
+
show_warning_msg("Missing Key", f"Config file '{self.config_path}' \r\n"\
|
|
107
|
+
"must have hardware `port` entry! (it can be left empty)\r\n"\
|
|
108
|
+
"Using defaul values for this session.")
|
|
109
|
+
self._force_default_config()
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
# Load yaml values
|
|
113
|
+
self.server = cfg.get('server', self.server_default)
|
|
114
|
+
self.hardware = cfg.get('hardware', self.hardware_default)
|
|
115
|
+
|
|
116
|
+
return True
|
|
117
|
+
|
|
118
|
+
def _force_default_config(self):
|
|
119
|
+
self.server = self.server_default
|
|
120
|
+
self.hardware = self.hardware_default
|
|
121
|
+
|
|
122
|
+
def _create_default_config(self):
|
|
123
|
+
logger.info("Using default config values")
|
|
124
|
+
self.server = self.server_default
|
|
125
|
+
self.hardware = self.hardware_default
|
|
126
|
+
|
|
127
|
+
# Load API keys from config file
|
|
128
|
+
def _load_API_keys(self):
|
|
129
|
+
# Make sure .yaml master key exists in the database
|
|
130
|
+
self.database.api_update_master(self.server['master_key'])
|
|
131
|
+
|
|
132
|
+
# Load all API keys from the database
|
|
133
|
+
self.api_keys = self.database.api_key_list()
|
|
134
|
+
|
|
135
|
+
def reload_API_keys(self):
|
|
136
|
+
# Load all API keys from the database
|
|
137
|
+
self.api_keys = self.database.api_key_list()
|
|
138
|
+
|
|
139
|
+
def update_dial_db_cell(self, dial_uid, cell, value):
|
|
140
|
+
try:
|
|
141
|
+
ret = self.database.dial_update_cell(dial_uid=dial_uid, cell=cell, value=value)
|
|
142
|
+
if ret:
|
|
143
|
+
self.dials[dial_uid][cell] = value
|
|
144
|
+
return True
|
|
145
|
+
return False
|
|
146
|
+
except Exception as e:
|
|
147
|
+
logger.error(e)
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
def update_dial_db_cell_with_dict(self, dial_uid, values_dict):
|
|
151
|
+
try:
|
|
152
|
+
self.database.dial_update_cell_with_dict(dial_uid=dial_uid, values_dict=values_dict)
|
|
153
|
+
except Exception as e:
|
|
154
|
+
logger.error(e)
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
# Read dial information stored in the DB and append to existing list
|
|
158
|
+
def append_dial_info_from_db(self, dial_list):
|
|
159
|
+
for key, dial in enumerate(dial_list):
|
|
160
|
+
dial_info = self.database.fetch_dial_info_or_create_default(dial['uid'])
|
|
161
|
+
|
|
162
|
+
dial_list[key]['dial_name'] = dial_info['dial_name']
|
|
163
|
+
dial_list[key]['fw_hash'] = dial_info['dial_build_hash']
|
|
164
|
+
dial_list[key]['fw_version'] = dial_info['dial_fw_version']
|
|
165
|
+
dial_list[key]['hw_version'] = dial_info['dial_hw_version']
|
|
166
|
+
dial_list[key]['protocol_version'] = dial_info['dial_protocol_version']
|
|
167
|
+
dial_list[key]['easing']['dial_step'] = dial_info['easing_dial_step']
|
|
168
|
+
dial_list[key]['easing']['dial_period'] = dial_info['easing_dial_period']
|
|
169
|
+
dial_list[key]['easing']['backlight_step'] = dial_info['easing_backlight_step']
|
|
170
|
+
dial_list[key]['easing']['backlight_period'] = dial_info['easing_backlight_period']
|
|
171
|
+
|
|
172
|
+
self.dials[dial['uid']] = dial
|
|
173
|
+
|
|
174
|
+
return dial_list
|
|
175
|
+
|
|
176
|
+
def dial_fetch_db_info(self, dial_uid):
|
|
177
|
+
return self.database.fetch_dial_info_or_create_default(dial_uid)
|
|
178
|
+
|
|
179
|
+
# Print out .yaml config
|
|
180
|
+
def debug_config(self):
|
|
181
|
+
logger.debug(f"\t Host: {self.server['hostname']}")
|
|
182
|
+
logger.debug("--- Server Config ---")
|
|
183
|
+
logger.debug(f"\t Port: {self.server['port']}")
|
|
184
|
+
logger.debug(f"\t Serial Timeout: {self.server['communication_timeout']}")
|
|
185
|
+
logger.debug(f"\t Master Key: {self.server['master_key']}")
|
|
186
|
+
|
|
187
|
+
logger.debug("--- API Keys ---")
|
|
188
|
+
logger.debug(f"\t There are {len(self.api_keys)} API keys loaded")
|
|
189
|
+
|
|
190
|
+
def get_server_config(self):
|
|
191
|
+
return self.server
|
|
192
|
+
|
|
193
|
+
def get_hardware_config(self):
|
|
194
|
+
return self.hardware
|
|
195
|
+
|
|
196
|
+
def create_api_key(self, key_name, priviledges=0):
|
|
197
|
+
generated_key = self.database.api_key_generate(key_name=key_name, level=priviledges)
|
|
198
|
+
logger.info(f"Generated API key '{generated_key}' (key_name:'{key_name}', priviledges:'{priviledges}')")
|
|
199
|
+
self.list_keys(reload=True)
|
|
200
|
+
return generated_key
|
|
201
|
+
|
|
202
|
+
def update_api_key(self, key_uid, key_name):
|
|
203
|
+
# Update key
|
|
204
|
+
if not self.database.api_key_update(key_uid=key_uid, key_name=key_name):
|
|
205
|
+
return False
|
|
206
|
+
return True
|
|
207
|
+
|
|
208
|
+
def delete_api_key(self, key_uid):
|
|
209
|
+
if not self.database.api_key_delete(key_uid=key_uid):
|
|
210
|
+
return False
|
|
211
|
+
self.list_keys(reload=True)
|
|
212
|
+
return True
|
|
213
|
+
|
|
214
|
+
def list_keys(self, reload=False):
|
|
215
|
+
if reload:
|
|
216
|
+
self.api_keys = self.database.api_key_list()
|
|
217
|
+
return self.api_keys
|
|
218
|
+
|
|
219
|
+
def api_key_add_dial_access(self, key, dials):
|
|
220
|
+
res = self.database.api_key_add_dial_access(key, dials)
|
|
221
|
+
if res:
|
|
222
|
+
self.list_keys(reload=True)
|
|
223
|
+
return res
|
|
224
|
+
|
|
225
|
+
# Returns True if provided key is listed as master key
|
|
226
|
+
# Otherwise return False
|
|
227
|
+
def validate_admin_key(self, key):
|
|
228
|
+
if not self.is_valid_api_key(key):
|
|
229
|
+
logger.debug(f"API key `{key}` does not exist")
|
|
230
|
+
return False
|
|
231
|
+
|
|
232
|
+
if self.api_keys[key]['priviledges'] >= 99:
|
|
233
|
+
return True
|
|
234
|
+
logger.debug("Key exists but not admin key.")
|
|
235
|
+
return False
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# Returns True if API key existis, otherwise returns False
|
|
239
|
+
def is_valid_api_key(self, key):
|
|
240
|
+
if key in self.api_keys.keys():
|
|
241
|
+
return True
|
|
242
|
+
return False
|
|
243
|
+
|
|
244
|
+
# Returns True if API key has access to dial UID, otherwise retrns False
|
|
245
|
+
def api_key_has_access_to_dial(self, key, dial):
|
|
246
|
+
if not self.is_valid_api_key(key):
|
|
247
|
+
return False
|
|
248
|
+
|
|
249
|
+
# Master key has wildcard access
|
|
250
|
+
if self.api_keys[key]['priviledges'] >= 99:
|
|
251
|
+
return True
|
|
252
|
+
|
|
253
|
+
if dial in self.api_keys[key]['dials']:
|
|
254
|
+
return True
|
|
255
|
+
return False
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from time import time, sleep
|
|
3
|
+
from math import trunc
|
|
4
|
+
from dials.base_logger import logger
|
|
5
|
+
|
|
6
|
+
# ServerDialHandler Class
|
|
7
|
+
# ---
|
|
8
|
+
# This class handles all the requests coming from the server.
|
|
9
|
+
# It stores update requests (value, backlight, image etc) that are coming from the server API calls.
|
|
10
|
+
# It will also periodically update the dials
|
|
11
|
+
# ---
|
|
12
|
+
# 'periodic_dial_update' function is called periodically from the main server loop
|
|
13
|
+
#
|
|
14
|
+
class ServerDialHandler:
|
|
15
|
+
dials = {}
|
|
16
|
+
hub_info = {}
|
|
17
|
+
communication_timeout = 3
|
|
18
|
+
|
|
19
|
+
def __init__(self, dial_driver, server_config):
|
|
20
|
+
self.dial_driver = dial_driver
|
|
21
|
+
self.server_config = server_config
|
|
22
|
+
|
|
23
|
+
# Communication timeout
|
|
24
|
+
cfg = self.server_config.get_server_config()
|
|
25
|
+
self.communication_timeout = cfg.get('communication_timeout', 3)
|
|
26
|
+
logger.info(f"Communication timeout set to {self.communication_timeout} seconds")
|
|
27
|
+
|
|
28
|
+
logger.debug("Retrieving list of dials")
|
|
29
|
+
self._reload_dials(True)
|
|
30
|
+
|
|
31
|
+
logger.debug("Reconfiguring dials with stored behaviour")
|
|
32
|
+
self._send_db_config_to_dials()
|
|
33
|
+
|
|
34
|
+
logger.debug("Setting all dials percentage to 0")
|
|
35
|
+
self.dial_driver.set_all_dials_to(0)
|
|
36
|
+
|
|
37
|
+
logger.debug("Server dial handler up and running.")
|
|
38
|
+
|
|
39
|
+
def periodic_dial_update(self):
|
|
40
|
+
updated = 0
|
|
41
|
+
ret=0
|
|
42
|
+
|
|
43
|
+
ret = self._periodic_update_dial_values()
|
|
44
|
+
updated = updated + ret
|
|
45
|
+
|
|
46
|
+
ret = self._periodic_update_dial_backlight()
|
|
47
|
+
updated = updated + ret
|
|
48
|
+
|
|
49
|
+
ret = self._periodic_update_dial_images()
|
|
50
|
+
updated = updated + ret
|
|
51
|
+
|
|
52
|
+
if updated <=0:
|
|
53
|
+
self._periodic_keep_alive()
|
|
54
|
+
|
|
55
|
+
def _convert_to_int(self, value):
|
|
56
|
+
try:
|
|
57
|
+
if not isinstance(value, int):
|
|
58
|
+
value = trunc(int(float(value)))
|
|
59
|
+
except Exception as e:
|
|
60
|
+
logger.error(e)
|
|
61
|
+
logger.error("Failed to convert value `{value}`to int. Defaulting to 0")
|
|
62
|
+
value = 0
|
|
63
|
+
|
|
64
|
+
return value
|
|
65
|
+
|
|
66
|
+
def _reload_dials(self, rescan=False):
|
|
67
|
+
# Get dial list from the dial driver (actual list reported from the hub)
|
|
68
|
+
dials = self.dial_driver.get_dial_list(rescan)
|
|
69
|
+
|
|
70
|
+
if len(dials)<=0:
|
|
71
|
+
logger.error("No dials connected to the bus!")
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
# 1 - Inform config/db what is the list of dials that we currently see
|
|
75
|
+
# 2 - Update handler information with any information retrieved from the database
|
|
76
|
+
# dials = self.server_config.append_dial_info_from_db(dials)
|
|
77
|
+
self.server_config.append_dial_info_from_db(dials)
|
|
78
|
+
|
|
79
|
+
# Dial HUB uses indexes to address each dial. On the server side we use UID for flexibility
|
|
80
|
+
# and also so that we can uniquely identify each dial.
|
|
81
|
+
for dial in dials:
|
|
82
|
+
dial['value'] = 0
|
|
83
|
+
dial['backlight'] = {'red':0, 'green':0, 'blue':0, 'white':0 }
|
|
84
|
+
dial['image_file'] = self._check_upload_for_dial_image(dial['uid'])
|
|
85
|
+
dial['update_deadline'] = time()
|
|
86
|
+
dial['value_changed'] = False
|
|
87
|
+
dial['backlight_changed'] = True
|
|
88
|
+
dial['image_changed'] = False
|
|
89
|
+
self.dials[dial['uid']] = dial
|
|
90
|
+
|
|
91
|
+
def _send_db_config_to_dials(self):
|
|
92
|
+
for _, dial in self.dials.items():
|
|
93
|
+
dial_step = dial['easing']['dial_step']
|
|
94
|
+
dial_period = dial['easing']['dial_period']
|
|
95
|
+
backlight_step = dial['easing']['backlight_step']
|
|
96
|
+
backlight_period = dial['easing']['backlight_period']
|
|
97
|
+
|
|
98
|
+
logger.debug(f"Configuring dial `{dial['uid']}`")
|
|
99
|
+
logger.debug(f"\tDial:{dial_step}% per {dial_period}ms")
|
|
100
|
+
logger.debug(f"\tBacklight {backlight_step}% {backlight_period}ms")
|
|
101
|
+
self.dial_set_easing_dial(dial['uid'], step=dial_step, period=dial_period)
|
|
102
|
+
self.dial_set_easing_backlight(dial['uid'], step=backlight_step, period=backlight_period)
|
|
103
|
+
|
|
104
|
+
def _check_upload_for_dial_image(self, dial_uid):
|
|
105
|
+
filename = f'img_{dial_uid}'
|
|
106
|
+
filepath = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'upload', filename)
|
|
107
|
+
if os.path.exists(filepath):
|
|
108
|
+
return filename
|
|
109
|
+
|
|
110
|
+
return 'img_blank'
|
|
111
|
+
|
|
112
|
+
# TODO: Update to send multiple/all dial values in one go instead one-by-one
|
|
113
|
+
def _periodic_update_dial_values(self):
|
|
114
|
+
updated = 0
|
|
115
|
+
for _, dial in self.dials.items():
|
|
116
|
+
if dial['value_changed']:
|
|
117
|
+
self.dial_driver.dial_single_set_percent(dial['index'], dial['value'])
|
|
118
|
+
dial['value_changed'] = False
|
|
119
|
+
dial['update_deadline'] = time() + self.communication_timeout
|
|
120
|
+
updated = updated+1
|
|
121
|
+
if updated>0:
|
|
122
|
+
logger.debug(f"Updated {updated} dial values.")
|
|
123
|
+
return updated
|
|
124
|
+
|
|
125
|
+
def _periodic_update_dial_backlight(self):
|
|
126
|
+
updated = 0
|
|
127
|
+
for _, dial in self.dials.items():
|
|
128
|
+
if dial['backlight_changed']:
|
|
129
|
+
self.dial_driver.dial_set_backlight(dial['index'],
|
|
130
|
+
dial['backlight']['red'],
|
|
131
|
+
dial['backlight']['green'],
|
|
132
|
+
dial['backlight']['blue'],
|
|
133
|
+
dial['backlight']['white']
|
|
134
|
+
)
|
|
135
|
+
dial['backlight_changed'] = False
|
|
136
|
+
dial['update_deadline'] = time() + self.communication_timeout
|
|
137
|
+
updated = updated+1
|
|
138
|
+
if updated>0:
|
|
139
|
+
logger.debug(f"Updated {updated} dial backlight(s).")
|
|
140
|
+
return updated
|
|
141
|
+
|
|
142
|
+
def _periodic_update_dial_images(self):
|
|
143
|
+
updated = 0
|
|
144
|
+
for _, dial in self.dials.items():
|
|
145
|
+
if dial['image_changed']:
|
|
146
|
+
logger.debug("Updating images")
|
|
147
|
+
self.dial_driver.update_display(device=dial['index'], imageFile=dial['image_file'])
|
|
148
|
+
dial['update_deadline'] = time() + self.communication_timeout
|
|
149
|
+
dial['image_changed'] = False
|
|
150
|
+
updated = updated+1
|
|
151
|
+
return updated
|
|
152
|
+
|
|
153
|
+
def _periodic_keep_alive(self):
|
|
154
|
+
#FIXME!
|
|
155
|
+
return
|
|
156
|
+
# for _, dial in self.dials.items():
|
|
157
|
+
# if time() >= dial['update_deadline']:
|
|
158
|
+
# logger.info("Keeping communication alive")
|
|
159
|
+
# self.dial_driver.dial_send_keep_comm_alive(device=dial['index'])
|
|
160
|
+
# dial['image_changed'] = False
|
|
161
|
+
|
|
162
|
+
def _dial_exists(self, dial_uid):
|
|
163
|
+
return dial_uid in self.dials
|
|
164
|
+
|
|
165
|
+
def provision_dials(self, num_attempts = 3):
|
|
166
|
+
logger.debug(f"Provisioning new dials (with {num_attempts} attempts)")
|
|
167
|
+
for _ in range(num_attempts):
|
|
168
|
+
self.dial_driver.provision_dials()
|
|
169
|
+
sleep(0.2)
|
|
170
|
+
logger.debug("Retrieving list of dials")
|
|
171
|
+
self._reload_dials(True)
|
|
172
|
+
|
|
173
|
+
def get_dial_info(self, dial_uid=None):
|
|
174
|
+
if dial_uid is not None:
|
|
175
|
+
return self.dials.get(dial_uid, None)
|
|
176
|
+
return self.dials
|
|
177
|
+
|
|
178
|
+
def dial_set_percent(self, dial_uid, value):
|
|
179
|
+
if not self._dial_exists(dial_uid):
|
|
180
|
+
logger.error(f"Dial {dial_uid} does not exist in dial list.")
|
|
181
|
+
return False
|
|
182
|
+
|
|
183
|
+
value = self._convert_to_int(value)
|
|
184
|
+
|
|
185
|
+
# Check if already at value
|
|
186
|
+
if self.dials[dial_uid]['value'] == value:
|
|
187
|
+
logger.debug(f"Dial {dial_uid} already at {value}")
|
|
188
|
+
return True
|
|
189
|
+
|
|
190
|
+
logger.debug(f"Queueing dial {dial_uid} value update to {value}")
|
|
191
|
+
self.dials[dial_uid]['value'] = value
|
|
192
|
+
self.dials[dial_uid]['value_changed'] = True
|
|
193
|
+
return True
|
|
194
|
+
|
|
195
|
+
# Debug function, mainly used for dial offset/calibration
|
|
196
|
+
def dial_set_raw(self, dial_uid, value):
|
|
197
|
+
if not self._dial_exists(dial_uid):
|
|
198
|
+
logger.error(f"Dial {dial_uid} does not exist in dial list.")
|
|
199
|
+
return False
|
|
200
|
+
|
|
201
|
+
value = self._convert_to_int(value)
|
|
202
|
+
self.dial_driver.dial_single_set_raw(self.dials[dial_uid]['index'], value)
|
|
203
|
+
return True
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
# Debug function, mainly used for dial offset/calibration
|
|
207
|
+
def dial_set_calibration(self, dial_uid, value, fullScale=False):
|
|
208
|
+
if not self._dial_exists(dial_uid):
|
|
209
|
+
logger.error(f"Dial {dial_uid} does not exist in dial list.")
|
|
210
|
+
return False
|
|
211
|
+
|
|
212
|
+
value = self._convert_to_int(value)
|
|
213
|
+
self.dial_driver.dial_calibrate(self.dials[dial_uid]['index'], value, fullScale)
|
|
214
|
+
return True
|
|
215
|
+
|
|
216
|
+
def dial_set_easing_dial(self, dial_uid, step=None, period=None):
|
|
217
|
+
if not self._dial_exists(dial_uid):
|
|
218
|
+
logger.error(f"Dial {dial_uid} does not exist in dial list.")
|
|
219
|
+
return False
|
|
220
|
+
|
|
221
|
+
if step is not None:
|
|
222
|
+
step = self._convert_to_int(step)
|
|
223
|
+
self.dial_driver.dial_easing_dial_step(self.dials[dial_uid]['index'], step)
|
|
224
|
+
|
|
225
|
+
if period is not None:
|
|
226
|
+
period = self._convert_to_int(period)
|
|
227
|
+
self.dial_driver.dial_easing_dial_period(self.dials[dial_uid]['index'], period)
|
|
228
|
+
|
|
229
|
+
return True
|
|
230
|
+
|
|
231
|
+
def dial_set_easing_backlight(self, dial_uid, step=None, period=None):
|
|
232
|
+
if not self._dial_exists(dial_uid):
|
|
233
|
+
logger.error(f"Dial {dial_uid} does not exist in dial list.")
|
|
234
|
+
return False
|
|
235
|
+
|
|
236
|
+
if step is not None:
|
|
237
|
+
step = self._convert_to_int(step)
|
|
238
|
+
self.dial_driver.dial_easing_backlight_step(self.dials[dial_uid]['index'], step)
|
|
239
|
+
|
|
240
|
+
if period is not None:
|
|
241
|
+
period = self._convert_to_int(period)
|
|
242
|
+
self.dial_driver.dial_easing_backlight_period(self.dials[dial_uid]['index'], period)
|
|
243
|
+
|
|
244
|
+
return True
|
|
245
|
+
|
|
246
|
+
def dial_set_backlight(self, dial_uid, red, green, blue, white):
|
|
247
|
+
if not self._dial_exists(dial_uid):
|
|
248
|
+
logger.error(f"Dial {dial_uid} does not exist in dial list.")
|
|
249
|
+
return False
|
|
250
|
+
|
|
251
|
+
red = self._convert_to_int(red)
|
|
252
|
+
green = self._convert_to_int(green)
|
|
253
|
+
blue = self._convert_to_int(blue)
|
|
254
|
+
white = self._convert_to_int(white)
|
|
255
|
+
|
|
256
|
+
red = min(red, 100)
|
|
257
|
+
green = min(green, 100)
|
|
258
|
+
blue = min(blue, 100)
|
|
259
|
+
blue = min(blue, 100)
|
|
260
|
+
white = min(white, 100)
|
|
261
|
+
|
|
262
|
+
new_value = {'red':red, 'green':green, 'blue':blue, 'white':white }
|
|
263
|
+
|
|
264
|
+
# Check if already at value
|
|
265
|
+
if self.dials[dial_uid]['backlight'] == new_value:
|
|
266
|
+
logger.debug(f"Dial {dial_uid} already at {red}:{green}:{blue}:{white}")
|
|
267
|
+
return True
|
|
268
|
+
|
|
269
|
+
logger.debug(f"Queueing dial {dial_uid} RGBW update to {red}:{green}:{blue}:{white}")
|
|
270
|
+
self.dials[dial_uid]['backlight'] = {'red':red, 'green':green, 'blue':blue, 'white':white }
|
|
271
|
+
self.dials[dial_uid]['backlight_changed'] = True
|
|
272
|
+
return True
|
|
273
|
+
|
|
274
|
+
def dial_set_image(self, dial_uid, image_file):
|
|
275
|
+
if not self._dial_exists(dial_uid):
|
|
276
|
+
logger.error(f"Dial {dial_uid} does not exist in dial list.")
|
|
277
|
+
return False
|
|
278
|
+
|
|
279
|
+
logger.debug(f"Queueing dial {dial_uid} background image to {image_file}")
|
|
280
|
+
self.dials[dial_uid]['image_file'] = image_file
|
|
281
|
+
self.dials[dial_uid]['image_changed'] = True
|
|
282
|
+
return True
|
|
283
|
+
|
|
284
|
+
def dial_reload_info_from_hardware(self, dial_uid):
|
|
285
|
+
if not self._dial_exists(dial_uid):
|
|
286
|
+
logger.error(f"Dial {dial_uid} does not exist in dial list.")
|
|
287
|
+
return False
|
|
288
|
+
|
|
289
|
+
deviceIndex = int(self.dials[dial_uid]['index'])
|
|
290
|
+
|
|
291
|
+
fw_hash = self.dial_driver.dial_get_fw_hash(deviceIndex)
|
|
292
|
+
fw_version = self.dial_driver.dial_get_fw_version(deviceIndex)
|
|
293
|
+
hw_version = self.dial_driver.dial_get_hw_version(deviceIndex)
|
|
294
|
+
protocol_version = self.dial_driver.dial_get_protocol_version(deviceIndex)
|
|
295
|
+
deviceEasing = self.dial_driver.dial_easing_get_config(deviceIndex) # Read dial easing config
|
|
296
|
+
|
|
297
|
+
self.dials[dial_uid]['fw_hash'] = fw_hash
|
|
298
|
+
self.dials[dial_uid]['fw_version'] = fw_version
|
|
299
|
+
self.dials[dial_uid]['hw_version'] = hw_version
|
|
300
|
+
self.dials[dial_uid]['protocol_version'] = protocol_version
|
|
301
|
+
self.dials[dial_uid]['easing']['dial_step'] = deviceEasing['dial_step']
|
|
302
|
+
self.dials[dial_uid]['easing']['dial_period'] = deviceEasing['dial_period']
|
|
303
|
+
self.dials[dial_uid]['easing']['backlight_step'] = deviceEasing['backlight_step']
|
|
304
|
+
self.dials[dial_uid]['easing']['backlight_period'] = deviceEasing['backlight_period']
|
|
305
|
+
|
|
306
|
+
self.server_config.update_dial_db_cell(dial_uid, 'dial_build_hash', fw_hash)
|
|
307
|
+
self.server_config.update_dial_db_cell(dial_uid, 'dial_fw_version', fw_version)
|
|
308
|
+
self.server_config.update_dial_db_cell(dial_uid, 'dial_hw_version', hw_version)
|
|
309
|
+
self.server_config.update_dial_db_cell(dial_uid, 'dial_protocol_version', protocol_version)
|
|
310
|
+
self.server_config.update_dial_db_cell(dial_uid, 'easing_dial_step', deviceEasing['dial_step'])
|
|
311
|
+
self.server_config.update_dial_db_cell(dial_uid, 'easing_dial_period', deviceEasing['dial_period'])
|
|
312
|
+
self.server_config.update_dial_db_cell(dial_uid, 'easing_backlight_step', deviceEasing['backlight_step'])
|
|
313
|
+
self.server_config.update_dial_db_cell(dial_uid, 'easing_backlight_period', deviceEasing['backlight_period'])
|
|
314
|
+
|
|
315
|
+
return self.dials[dial_uid]
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def dial_reload_info_from_database(self, dial_uid):
|
|
319
|
+
if not self._dial_exists(dial_uid):
|
|
320
|
+
logger.error(f"Dial {dial_uid} does not exist in dial list.")
|
|
321
|
+
return False
|
|
322
|
+
|
|
323
|
+
dial_info = self.server_config.dial_fetch_db_info(dial_uid)
|
|
324
|
+
|
|
325
|
+
self.dials[dial_uid]['fw_hash'] = dial_info['dial_build_hash']
|
|
326
|
+
self.dials[dial_uid]['fw_version'] = dial_info['dial_fw_version']
|
|
327
|
+
self.dials[dial_uid]['hw_version'] = dial_info['dial_hw_version']
|
|
328
|
+
self.dials[dial_uid]['protocol_version'] = dial_info['dial_protocol_version']
|
|
329
|
+
self.dials[dial_uid]['easing']['dial_step'] = dial_info['easing_dial_step']
|
|
330
|
+
self.dials[dial_uid]['easing']['dial_period'] = dial_info['easing_dial_period']
|
|
331
|
+
self.dials[dial_uid]['easing']['backlight_step'] = dial_info['easing_backlight_step']
|
|
332
|
+
self.dials[dial_uid]['easing']['backlight_period'] = dial_info['easing_backlight_period']
|
|
333
|
+
|
|
334
|
+
return self.dials[dial_uid]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# pylint: disable=import-outside-toplevel
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
# TODO: Add support for Linux and Mac OS
|
|
5
|
+
|
|
6
|
+
if sys.platform.lower() == "win32":
|
|
7
|
+
import ctypes
|
|
8
|
+
|
|
9
|
+
def show_error_msg(title, message):
|
|
10
|
+
ctypes.windll.user32.MessageBoxW(0, message, f"VU Server - {title}", 16)
|
|
11
|
+
|
|
12
|
+
def show_warning_msg(title, message):
|
|
13
|
+
ctypes.windll.user32.MessageBoxW(0, message, f"VU Server - {title}", 48)
|
|
14
|
+
|
|
15
|
+
def show_info_msg(title, message):
|
|
16
|
+
ctypes.windll.user32.MessageBoxW(0, message, f"VU Server - {title}", 64)
|
|
17
|
+
|
|
18
|
+
else:
|
|
19
|
+
def show_error_msg(title, message):
|
|
20
|
+
pass
|
|
21
|
+
def show_warning_msg(title, message):
|
|
22
|
+
pass
|
|
23
|
+
def show_info_msg(title, message):
|
|
24
|
+
pass
|
|
Binary file
|
|
Binary file
|
|
Binary file
|