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,670 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
import signal
|
|
4
|
+
import argparse
|
|
5
|
+
import zlib
|
|
6
|
+
import time
|
|
7
|
+
import re
|
|
8
|
+
from mimetypes import guess_type
|
|
9
|
+
from dials.base_logger import logger, set_logger_level
|
|
10
|
+
from tornado.web import Application, RequestHandler, Finish, StaticFileHandler
|
|
11
|
+
from tornado.ioloop import IOLoop, PeriodicCallback
|
|
12
|
+
from dial_driver import DialSerialDriver
|
|
13
|
+
from server_config import ServerConfig
|
|
14
|
+
from server_dial_handler import ServerDialHandler
|
|
15
|
+
from vu_notifications import show_error_msg, show_info_msg, show_warning_msg
|
|
16
|
+
from serial.serialutil import SerialException
|
|
17
|
+
|
|
18
|
+
BASEDIR_NAME = os.path.dirname(__file__)
|
|
19
|
+
BASEDIR_PATH = os.path.abspath(BASEDIR_NAME)
|
|
20
|
+
WEB_ROOT = os.path.join(BASEDIR_PATH, 'www')
|
|
21
|
+
|
|
22
|
+
def pid_lock(service_name, create=True):
|
|
23
|
+
file_name = "service.{}.pid.lock".format(service_name)
|
|
24
|
+
pid_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), file_name)
|
|
25
|
+
|
|
26
|
+
if create:
|
|
27
|
+
pid = os.getpid()
|
|
28
|
+
with open(pid_file, "w", encoding="utf-8") as file:
|
|
29
|
+
file.write(str(pid))
|
|
30
|
+
else:
|
|
31
|
+
if os.path.exists(pid_file):
|
|
32
|
+
os.remove(pid_file)
|
|
33
|
+
|
|
34
|
+
class BaseHandler(RequestHandler):
|
|
35
|
+
def initialize(self, handler, config):
|
|
36
|
+
self.handler = handler # pylint: disable=attribute-defined-outside-init
|
|
37
|
+
self.config = config # pylint: disable=attribute-defined-outside-init
|
|
38
|
+
self.upload_path = os.path.join(os.path.dirname(__file__), 'upload') # pylint: disable=attribute-defined-outside-init
|
|
39
|
+
|
|
40
|
+
def set_default_headers(self):
|
|
41
|
+
self.set_header("Access-Control-Allow-Origin", "*")
|
|
42
|
+
# self.set_header("Access-Control-Allow-Headers", "x-requested-with")
|
|
43
|
+
# self.set_header('Access-Control-Allow-Methods', ' PUT, DELETE, OPTIONS, GET')
|
|
44
|
+
self.set_header('Access-Control-Allow-Methods', 'POST, GET')
|
|
45
|
+
self.set_header('Content-Type', 'application/json')
|
|
46
|
+
|
|
47
|
+
# self.set_header("Access-Control-Allow-Origin", "Origin");
|
|
48
|
+
# self.set_header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
|
|
49
|
+
# self.set_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
|
50
|
+
# self.set_header("Access-Control-Allow-Credentials", "true");
|
|
51
|
+
|
|
52
|
+
# Helper function to send response
|
|
53
|
+
def send_response(self, status, message='', data=None, status_code=200):
|
|
54
|
+
resp = {'status': status, 'message': message, 'data': data}
|
|
55
|
+
self.set_status(status_code)
|
|
56
|
+
self.write(resp)
|
|
57
|
+
self.finish()
|
|
58
|
+
|
|
59
|
+
def api_key_has_access_to_dial(self, gaugeUID, api_key=None):
|
|
60
|
+
if api_key is None:
|
|
61
|
+
api_key = self.get_argument('key', None)
|
|
62
|
+
|
|
63
|
+
if not self.config.api_key_has_access_to_dial(api_key, gaugeUID):
|
|
64
|
+
return False
|
|
65
|
+
return True
|
|
66
|
+
|
|
67
|
+
def is_valid_api_key(self):
|
|
68
|
+
if not self.config.is_valid_api_key(self.get_argument('key', None)):
|
|
69
|
+
return False
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
def valid_admin_key(self):
|
|
73
|
+
admin_key = self.get_argument('admin_key', None)
|
|
74
|
+
if not admin_key:
|
|
75
|
+
logger.error("Missing API key")
|
|
76
|
+
self.send_response(status='fail', message='Invalid or missing API key.', status_code=401)
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
if not self.config.validate_admin_key(admin_key):
|
|
80
|
+
logger.error("Invalid API key")
|
|
81
|
+
self.send_response(status='fail', message='Invalid or missing API key.', status_code=401)
|
|
82
|
+
return False
|
|
83
|
+
return True
|
|
84
|
+
|
|
85
|
+
def get_file_crc(self, filepath):
|
|
86
|
+
if not os.path.exists(filepath):
|
|
87
|
+
logger.error(f"File {filepath} does not exist!")
|
|
88
|
+
return "00000000"
|
|
89
|
+
|
|
90
|
+
with open(filepath, 'rb') as fh:
|
|
91
|
+
fileCrc = 0
|
|
92
|
+
while True:
|
|
93
|
+
s = fh.read(65536)
|
|
94
|
+
if not s:
|
|
95
|
+
break
|
|
96
|
+
fileCrc = zlib.crc32(s, fileCrc)
|
|
97
|
+
return "%08X" % (fileCrc & 0xFFFFFFFF)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class Device_Status_Handler(BaseHandler):
|
|
101
|
+
def get(self, dial_uid):
|
|
102
|
+
logger.debug(f"Request:STATUS - Device:{dial_uid}")
|
|
103
|
+
dial = self.handler.get_dial_info(dial_uid=dial_uid)
|
|
104
|
+
if dial is not None:
|
|
105
|
+
return self.send_response(status='ok', data=dial)
|
|
106
|
+
return self.send_response(status='fail', message='Invalid dial_uid or device is offline.')
|
|
107
|
+
|
|
108
|
+
class Device_Set_Handler(BaseHandler):
|
|
109
|
+
def get(self, dial_uid):
|
|
110
|
+
value = self.get_argument('value', 0)
|
|
111
|
+
logger.debug(f"Request:SET - Device:{dial_uid} To:{value}")
|
|
112
|
+
|
|
113
|
+
# Validate API key
|
|
114
|
+
if not self.is_valid_api_key():
|
|
115
|
+
return self.send_response(status='fail', message='Unauthorized', status_code=401)
|
|
116
|
+
|
|
117
|
+
if self.handler.dial_set_percent(dial_uid=dial_uid, value=value):
|
|
118
|
+
return self.send_response(status='ok', message='Update queued')
|
|
119
|
+
return self.send_response(status='fail', message='Invalid dial_uid or device is offline.')
|
|
120
|
+
|
|
121
|
+
class Device_SetRaw_Handler(BaseHandler):
|
|
122
|
+
def get(self, dial_uid):
|
|
123
|
+
value = self.get_argument('value', 0)
|
|
124
|
+
logger.debug(f"Request:SET_RAW - Device:{dial_uid} To:{value}")
|
|
125
|
+
|
|
126
|
+
# Validate API key
|
|
127
|
+
if not self.is_valid_api_key():
|
|
128
|
+
return self.send_response(status='fail', message='Unauthorized', status_code=401)
|
|
129
|
+
|
|
130
|
+
if self.handler.dial_set_raw(dial_uid=dial_uid, value=value):
|
|
131
|
+
return self.send_response(status='ok', message='Dial RAW value updated', status_code=201)
|
|
132
|
+
return self.send_response(status='fail', message='Invalid dial_uid or device is offline.', status_code=503)
|
|
133
|
+
|
|
134
|
+
class Device_Backlight_Handler(BaseHandler):
|
|
135
|
+
def get(self, dial_uid):
|
|
136
|
+
red = self.get_argument('red', 0)
|
|
137
|
+
green = self.get_argument('green', 0)
|
|
138
|
+
blue = self.get_argument('blue', 0)
|
|
139
|
+
white = self.get_argument('white', 0)
|
|
140
|
+
|
|
141
|
+
logger.debug(f"Request:BACKLIGHT - Device:{dial_uid} To: (red:{red} green:{green} blue:{blue} white:{white})")
|
|
142
|
+
|
|
143
|
+
# Validate API key
|
|
144
|
+
if not self.is_valid_api_key():
|
|
145
|
+
return self.send_response(status='fail', message='Unauthorized', status_code=401)
|
|
146
|
+
|
|
147
|
+
if self.handler.dial_set_backlight(dial_uid=dial_uid, red=red, green=green, blue=blue, white=white):
|
|
148
|
+
return self.send_response(status='ok', message='Update queued', status_code=201)
|
|
149
|
+
return self.send_response(status='fail', message='Invalid dial_uid or device is offline.', status_code=503)
|
|
150
|
+
|
|
151
|
+
class Device_Set_Image(BaseHandler):
|
|
152
|
+
def post(self, dial_uid):
|
|
153
|
+
get_force = self.get_argument('force', False)
|
|
154
|
+
|
|
155
|
+
force_img_update = bool(get_force is True)
|
|
156
|
+
|
|
157
|
+
logger.debug(f"Request:SET_IMAGE - Device:{dial_uid}")
|
|
158
|
+
|
|
159
|
+
# Validate API key
|
|
160
|
+
if not self.is_valid_api_key():
|
|
161
|
+
return self.send_response(status='fail', message='Unauthorized', status_code=401)
|
|
162
|
+
|
|
163
|
+
# Store new image
|
|
164
|
+
img_file = self.handle_image_upload(dial_uid)
|
|
165
|
+
if not img_file:
|
|
166
|
+
logger.error("Handle image upload failed")
|
|
167
|
+
return self.send_response(status='fail', message='image upload failed', status_code=503)
|
|
168
|
+
|
|
169
|
+
current_img = os.path.join(self.upload_path, f'img_{dial_uid}')
|
|
170
|
+
new_img = os.path.join(self.upload_path, f'tmp_{dial_uid}')
|
|
171
|
+
|
|
172
|
+
# If this is a different image from existing one
|
|
173
|
+
if self.different_image_uploaded(current_img, new_img) or force_img_update:
|
|
174
|
+
|
|
175
|
+
# Remove existing image (if exists)
|
|
176
|
+
if os.path.exists(current_img):
|
|
177
|
+
os.remove(current_img)
|
|
178
|
+
# Move (rename) new image and set as current
|
|
179
|
+
os.rename(new_img, current_img)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
if self.handler.dial_set_image(dial_uid=dial_uid, image_file=current_img):
|
|
183
|
+
return self.send_response(status='ok', status_code=201)
|
|
184
|
+
return self.send_response(status='fail', message='Invalid dial_uid or device is offline.', status_code=503)
|
|
185
|
+
|
|
186
|
+
logger.debug(f"Skipping dial `{dial_uid}` image update. Contents already match.")
|
|
187
|
+
return self.send_response(status='ok', message='Image CRC already maches existing one. Skipping update.')
|
|
188
|
+
|
|
189
|
+
def handle_image_upload(self, dial_uid):
|
|
190
|
+
self.make_upload_folder()
|
|
191
|
+
image_data = self.request.files.get('imgfile', None)
|
|
192
|
+
if image_data is None:
|
|
193
|
+
logger.error("imgfile field missing from request.")
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
# First upload image as temporary
|
|
197
|
+
file_path = os.path.join(self.upload_path, f'tmp_{dial_uid}')
|
|
198
|
+
|
|
199
|
+
with open(file_path, 'wb') as img:
|
|
200
|
+
img.write(image_data[0]['body'])
|
|
201
|
+
|
|
202
|
+
return file_path
|
|
203
|
+
|
|
204
|
+
def different_image_uploaded(self, old, new):
|
|
205
|
+
if self.get_file_crc(old) != self.get_file_crc(new):
|
|
206
|
+
return True
|
|
207
|
+
return False
|
|
208
|
+
|
|
209
|
+
def make_upload_folder(self):
|
|
210
|
+
if not os.path.exists(self.upload_path):
|
|
211
|
+
os.makedirs(self.upload_path)
|
|
212
|
+
|
|
213
|
+
class Dial_Get_Image(BaseHandler):
|
|
214
|
+
def get(self, gaugeUID):
|
|
215
|
+
self.set_header("Content-Type", "image/png")
|
|
216
|
+
|
|
217
|
+
logger.debug("Request: GET_IMAGE")
|
|
218
|
+
dial_image = os.path.join(os.path.dirname(__file__), 'upload', f'img_{gaugeUID}')
|
|
219
|
+
|
|
220
|
+
if os.path.exists(dial_image):
|
|
221
|
+
filepath = dial_image
|
|
222
|
+
logger.debug(f"Serving image from {filepath}")
|
|
223
|
+
else:
|
|
224
|
+
filepath = os.path.join(os.path.dirname(__file__), 'upload', 'img_blank')
|
|
225
|
+
logger.debug(f"Serving DEFAULT image from {filepath}")
|
|
226
|
+
|
|
227
|
+
try:
|
|
228
|
+
with open(filepath, 'rb') as f:
|
|
229
|
+
data = f.read()
|
|
230
|
+
self.write(data)
|
|
231
|
+
return self.finish()
|
|
232
|
+
except IOError as e:
|
|
233
|
+
logger.error(e)
|
|
234
|
+
return self.send_response(status='fail', message='Internal sever error!', status_code=500)
|
|
235
|
+
|
|
236
|
+
class Dial_Get_Image_CRC(BaseHandler):
|
|
237
|
+
def get(self, gaugeUID):
|
|
238
|
+
logger.debug("Request: GET_IMAGE_CRC")
|
|
239
|
+
|
|
240
|
+
img_file = os.path.join(os.path.dirname(__file__), 'upload', f'img_{gaugeUID}')
|
|
241
|
+
|
|
242
|
+
crc = self.get_file_crc(img_file)
|
|
243
|
+
return self.send_response(status='ok', data=crc)
|
|
244
|
+
|
|
245
|
+
class Dial_Get_List(BaseHandler):
|
|
246
|
+
def get(self):
|
|
247
|
+
logger.debug("Request: DEVICE_LIST")
|
|
248
|
+
|
|
249
|
+
# Validate API key
|
|
250
|
+
if not self.is_valid_api_key():
|
|
251
|
+
return self.send_response(status='fail', message='Missing API key!', status_code=403)
|
|
252
|
+
|
|
253
|
+
if not self.is_valid_api_key():
|
|
254
|
+
return self.send_response(status='fail', message='Unauthorized', status_code=401)
|
|
255
|
+
|
|
256
|
+
dials = self.handler.get_dial_info()
|
|
257
|
+
# logger.debug(dials)
|
|
258
|
+
|
|
259
|
+
# Reshape dials data to respond with only relevant information
|
|
260
|
+
dialData = []
|
|
261
|
+
for uid in dials:
|
|
262
|
+
tmp_dial = {
|
|
263
|
+
'uid' : uid,
|
|
264
|
+
'dial_name': dials[uid]['dial_name'],
|
|
265
|
+
'value': dials[uid]['value'],
|
|
266
|
+
'backlight': dials[uid]['backlight'],
|
|
267
|
+
'image_file' : dials[uid]['image_file']
|
|
268
|
+
}
|
|
269
|
+
# Remove unused keys
|
|
270
|
+
tmp_dial['backlight'].pop('white', None)
|
|
271
|
+
|
|
272
|
+
# If key has access to dial
|
|
273
|
+
if self.api_key_has_access_to_dial(gaugeUID=uid):
|
|
274
|
+
dialData.append(tmp_dial)
|
|
275
|
+
|
|
276
|
+
return self.send_response(status='ok', data=dialData)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class Dial_Provision(BaseHandler):
|
|
280
|
+
def get(self):
|
|
281
|
+
|
|
282
|
+
logger.debug("Request: PROVISION_NEW_DIALS")
|
|
283
|
+
|
|
284
|
+
# Validate master key
|
|
285
|
+
if not self.valid_admin_key():
|
|
286
|
+
return False
|
|
287
|
+
|
|
288
|
+
dials = self.handler.provision_dials()
|
|
289
|
+
logger.debug(dials)
|
|
290
|
+
|
|
291
|
+
return self.send_response(status='ok', data=dials)
|
|
292
|
+
|
|
293
|
+
class Dial_Set_Dial_Name(BaseHandler):
|
|
294
|
+
def get(self, gaugeUID):
|
|
295
|
+
new_name = self.get_argument('name', None)
|
|
296
|
+
logger.debug(f"Request:SET_NAME - Device:{gaugeUID} To: friendly name={new_name}")
|
|
297
|
+
|
|
298
|
+
# Validate API key
|
|
299
|
+
if not self.is_valid_api_key():
|
|
300
|
+
return self.send_response(status='fail', message='Unauthorized', status_code=401)
|
|
301
|
+
|
|
302
|
+
if new_name is not None:
|
|
303
|
+
# Dial name should be 3 or more characters
|
|
304
|
+
if len(new_name) < 3:
|
|
305
|
+
return self.send_response(status='fail', message='Dial name should be at least 3 characters long.', status_code=400)
|
|
306
|
+
|
|
307
|
+
# Limit dial name to 30 characters
|
|
308
|
+
if len(new_name) > 30:
|
|
309
|
+
return self.send_response(status='fail', message='Dial name should be 30 characters or less.', status_code=400)
|
|
310
|
+
|
|
311
|
+
# Verify valid name
|
|
312
|
+
if not re.search(r"^[a-z0-9\-_\ ]*$", new_name, re.IGNORECASE):
|
|
313
|
+
return self.send_response(status='fail', message='Invalid characters! Only `A-Z`, `0-9`, `-`, `_` and space allowed.', status_code=400)
|
|
314
|
+
|
|
315
|
+
# Finally update dial name
|
|
316
|
+
ret = self.config.update_dial_db_cell(dial_uid=gaugeUID, cell='dial_name', value=new_name)
|
|
317
|
+
if ret:
|
|
318
|
+
return self.send_response(status='ok', status_code=201)
|
|
319
|
+
return self.send_response(status='fail', message='Can not update dial name! Dial does not exist?', status_code=406)
|
|
320
|
+
return self.send_response(status='fail', message='Device not present!', status_code=406)
|
|
321
|
+
|
|
322
|
+
class Dial_Reload_Device_Info(BaseHandler):
|
|
323
|
+
def get(self, gaugeUID):
|
|
324
|
+
|
|
325
|
+
logger.debug(f"Request:GET_INFO - Device:{gaugeUID}")
|
|
326
|
+
|
|
327
|
+
# Validate API key
|
|
328
|
+
if not self.is_valid_api_key():
|
|
329
|
+
return self.send_response(status='fail', message='Unauthorized', status_code=401)
|
|
330
|
+
|
|
331
|
+
dial_info = self.handler.dial_reload_info_from_hardware(gaugeUID)
|
|
332
|
+
return self.send_response(status='ok', data=dial_info)
|
|
333
|
+
|
|
334
|
+
class Dial_Set_Calibration(BaseHandler):
|
|
335
|
+
def get(self, gaugeUID):
|
|
336
|
+
dac_calibration = self.get_argument('value', None)
|
|
337
|
+
logger.debug(f"Request:SET_CALIBRATION - Device:{gaugeUID} To: value={dac_calibration}")
|
|
338
|
+
|
|
339
|
+
# Validate API key
|
|
340
|
+
if not self.is_valid_api_key():
|
|
341
|
+
return self.send_response(status='fail', message='Unauthorized', status_code=401)
|
|
342
|
+
|
|
343
|
+
if dac_calibration is not None:
|
|
344
|
+
self.handler.dial_set_calibration(dial_uid=gaugeUID, value=dac_calibration, fullScale=False)
|
|
345
|
+
return self.send_response(status='ok', message="Calibration value updated", status_code=201)
|
|
346
|
+
return self.send_response(status='fail', message="Device not present", status_code=406)
|
|
347
|
+
|
|
348
|
+
class Dial_Set_Easing_Dial(BaseHandler):
|
|
349
|
+
def get(self, gaugeUID):
|
|
350
|
+
step = self.get_argument('step', None)
|
|
351
|
+
period = self.get_argument('period', None)
|
|
352
|
+
logger.debug(f"Request:SET_EASING_DIAL - Device:{gaugeUID} Step:{step} Period:{period}")
|
|
353
|
+
|
|
354
|
+
# Validate API key
|
|
355
|
+
if not self.is_valid_api_key():
|
|
356
|
+
return self.send_response(status='fail', message='Unauthorized', status_code=401)
|
|
357
|
+
|
|
358
|
+
if step is None and period is None:
|
|
359
|
+
return self.send_response(status='fail', message="Please provide at least one of required parameters (`step` or `period`)", status_code=400)
|
|
360
|
+
|
|
361
|
+
if self.handler.dial_set_easing_dial(dial_uid=gaugeUID, step=step, period=period):
|
|
362
|
+
values_dict = { 'easing_dial_step': int(step), 'easing_dial_period': int(period) }
|
|
363
|
+
self.config.update_dial_db_cell_with_dict(gaugeUID, values_dict)
|
|
364
|
+
self.handler.dial_reload_info_from_database(gaugeUID)
|
|
365
|
+
return self.send_response(status='ok')
|
|
366
|
+
return self.send_response(status='fail', message="Device not present", status_code=406)
|
|
367
|
+
|
|
368
|
+
class Dial_Set_Easing_Backlight(BaseHandler):
|
|
369
|
+
def get(self, gaugeUID):
|
|
370
|
+
step = self.get_argument('step', None)
|
|
371
|
+
period = self.get_argument('period', None)
|
|
372
|
+
logger.debug(f"Request:SET_EASING_BACKLIGHT - Device:{gaugeUID} Step:{step} Period:{period}")
|
|
373
|
+
|
|
374
|
+
# Validate API key
|
|
375
|
+
if not self.is_valid_api_key():
|
|
376
|
+
return self.send_response(status='fail', message='Unauthorized', status_code=401)
|
|
377
|
+
|
|
378
|
+
if step is None and period is None:
|
|
379
|
+
return self.send_response(status='fail', message="Please provide at least one of required parameters (`step` or `period`)", status_code=400)
|
|
380
|
+
|
|
381
|
+
if self.handler.dial_set_easing_backlight(dial_uid=gaugeUID, step=step, period=period):
|
|
382
|
+
values_dict = { 'easing_backlight_step': step, 'easing_backlight_period': period }
|
|
383
|
+
self.config.update_dial_db_cell_with_dict(gaugeUID, values_dict)
|
|
384
|
+
self.handler.dial_reload_info_from_database(gaugeUID)
|
|
385
|
+
return self.send_response(status='ok')
|
|
386
|
+
return self.send_response(status='fail', message="Device not present", status_code=406)
|
|
387
|
+
|
|
388
|
+
class Dial_Get_Easing_Config(BaseHandler):
|
|
389
|
+
def get(self, gaugeUID):
|
|
390
|
+
logger.debug(f"Request:GET_EASING_CONFIG - Device:{gaugeUID}")
|
|
391
|
+
|
|
392
|
+
# Validate API key
|
|
393
|
+
if not self.is_valid_api_key():
|
|
394
|
+
return self.send_response(status='fail', message='Unauthorized', status_code=401)
|
|
395
|
+
|
|
396
|
+
# TODO: Implement in dial handler
|
|
397
|
+
return self.send_response(status='ok', message="not supported yet")
|
|
398
|
+
|
|
399
|
+
# -- Keys --
|
|
400
|
+
class Admin_Keys_List(BaseHandler):
|
|
401
|
+
def get(self):
|
|
402
|
+
logger.debug("Request:Admin_Keys_List")
|
|
403
|
+
|
|
404
|
+
# Validate master key
|
|
405
|
+
if not self.valid_admin_key():
|
|
406
|
+
return False # Above function already sends response
|
|
407
|
+
|
|
408
|
+
keys = self.config.list_keys()
|
|
409
|
+
|
|
410
|
+
ret = []
|
|
411
|
+
for _, value in keys.items():
|
|
412
|
+
# value.pop('dials', None)
|
|
413
|
+
ret.append(value)
|
|
414
|
+
return self.send_response(status='ok', data=ret)
|
|
415
|
+
|
|
416
|
+
class Admin_Keys_Create(BaseHandler):
|
|
417
|
+
def post(self):
|
|
418
|
+
logger.debug("Request:Admin_Keys_Create")
|
|
419
|
+
|
|
420
|
+
# Validate master key
|
|
421
|
+
if not self.valid_admin_key():
|
|
422
|
+
return False # Above function already sends response
|
|
423
|
+
|
|
424
|
+
key_name = self.get_argument('name', 'Not set')
|
|
425
|
+
dial_access = self.get_argument('dials', None)
|
|
426
|
+
priviledges = self.get_argument('priviledges', 1)
|
|
427
|
+
|
|
428
|
+
if dial_access:
|
|
429
|
+
dials = dial_access.split(';')
|
|
430
|
+
else:
|
|
431
|
+
dials = None
|
|
432
|
+
|
|
433
|
+
new_key = self.config.create_api_key(key_name, priviledges)
|
|
434
|
+
|
|
435
|
+
if dials:
|
|
436
|
+
self.config.api_key_add_dial_access(new_key, dials)
|
|
437
|
+
|
|
438
|
+
return self.send_response(status='ok', data=new_key)
|
|
439
|
+
|
|
440
|
+
class Admin_Keys_Update(BaseHandler):
|
|
441
|
+
def post(self):
|
|
442
|
+
logger.debug("Request:Admin_Keys_Update")
|
|
443
|
+
|
|
444
|
+
dial_list = self.get_argument('dials', None)
|
|
445
|
+
key = self.get_argument('key', None)
|
|
446
|
+
name = self.get_argument('name', None)
|
|
447
|
+
|
|
448
|
+
# Validate master key
|
|
449
|
+
if not self.valid_admin_key():
|
|
450
|
+
return False # Above function already sends response
|
|
451
|
+
|
|
452
|
+
# Check if API key exists
|
|
453
|
+
if not self.is_valid_api_key():
|
|
454
|
+
return self.send_response(status='fail', message='Invalid key selected!')
|
|
455
|
+
|
|
456
|
+
logger.debug(dial_list)
|
|
457
|
+
logger.debug(key)
|
|
458
|
+
logger.debug(name)
|
|
459
|
+
|
|
460
|
+
if dial_list is None and key is None:
|
|
461
|
+
return self.send_response(status='fail', message='Key, Key name and Dial list are all empty. Aborting.', status_code=400)
|
|
462
|
+
|
|
463
|
+
# Update key
|
|
464
|
+
if name is not None:
|
|
465
|
+
if not self.config.update_api_key(key_uid=key, key_name=name):
|
|
466
|
+
return self.send_response(status='fail', message='Failed to update key!')
|
|
467
|
+
|
|
468
|
+
# Update dial access
|
|
469
|
+
if dial_list:
|
|
470
|
+
dial_list = dial_list.split(';')
|
|
471
|
+
if self.config.api_key_add_dial_access(key, dial_list):
|
|
472
|
+
return self.send_response(status='ok', message='Key updated!')
|
|
473
|
+
|
|
474
|
+
return self.send_response(status='fail', message='Failed to update key!')
|
|
475
|
+
|
|
476
|
+
class Admin_Keys_Remove(BaseHandler):
|
|
477
|
+
def get(self):
|
|
478
|
+
logger.debug("Request:Admin_Keys_Remove")
|
|
479
|
+
key_uid = self.get_argument('key', None)
|
|
480
|
+
|
|
481
|
+
# Validate master key
|
|
482
|
+
if not self.valid_admin_key():
|
|
483
|
+
return False # Above function already sends response
|
|
484
|
+
|
|
485
|
+
# Check if API key exists
|
|
486
|
+
if not self.is_valid_api_key():
|
|
487
|
+
return self.send_response(status='fail', message='Invalid key selected!')
|
|
488
|
+
|
|
489
|
+
if not self.config.delete_api_key(key_uid):
|
|
490
|
+
return self.send_response(status='fail', message='Failed to remove key!')
|
|
491
|
+
return self.send_response(status='ok', message='Key removed!')
|
|
492
|
+
|
|
493
|
+
# -- Default 404 --
|
|
494
|
+
class Default_404_Handler(RequestHandler):
|
|
495
|
+
# Override prepare() instead of get() to cover all possible HTTP methods.
|
|
496
|
+
def prepare(self):
|
|
497
|
+
self.set_status(404)
|
|
498
|
+
resp = {'status': 'fail', 'message': 'Unsupported method'}
|
|
499
|
+
self.write(resp)
|
|
500
|
+
raise Finish()
|
|
501
|
+
|
|
502
|
+
class FileHandler(RequestHandler):
|
|
503
|
+
def get(self, path=None):
|
|
504
|
+
if path:
|
|
505
|
+
logger.debug(f"Requesting: {path}")
|
|
506
|
+
file_location = os.path.join(WEB_ROOT, path)
|
|
507
|
+
else:
|
|
508
|
+
file_location = os.path.join(WEB_ROOT, 'index.html')
|
|
509
|
+
|
|
510
|
+
if not os.path.isfile(file_location):
|
|
511
|
+
logger.error(f"Requested file can not be found: {path}")
|
|
512
|
+
self.set_status(404)
|
|
513
|
+
resp = {'status': 'fail', 'message': 'Page not found'}
|
|
514
|
+
self.write(resp)
|
|
515
|
+
raise Finish()
|
|
516
|
+
content_type, _ = guess_type(file_location)
|
|
517
|
+
self.add_header('Content-Type', content_type)
|
|
518
|
+
with open(file_location, encoding="utf-8") as source_file:
|
|
519
|
+
self.write(source_file.read())
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
class Dial_API_Service(Application):
|
|
523
|
+
def __init__(self):
|
|
524
|
+
pid_lock('server', True)
|
|
525
|
+
signal.signal(signal.SIGTERM, self.signal_handler)
|
|
526
|
+
signal.signal(signal.SIGINT, self.signal_handler)
|
|
527
|
+
|
|
528
|
+
logger.info("Loading server config...")
|
|
529
|
+
self.config = ServerConfig('config.yaml')
|
|
530
|
+
|
|
531
|
+
# If config contains COM port, use it. Otherwise try to find it
|
|
532
|
+
hardware_config = self.config.get_hardware_config()
|
|
533
|
+
port = hardware_config.get('port', None)
|
|
534
|
+
if port:
|
|
535
|
+
self.serialPort = port
|
|
536
|
+
else:
|
|
537
|
+
self.serialPort = DialSerialDriver.find_gauge_hub()
|
|
538
|
+
if self.serialPort is None:
|
|
539
|
+
logger.error("Could not find VU1 Dials Hub. Please make sure it's plugged in and (if necessary) drivers are installed.")
|
|
540
|
+
show_error_msg("Hub not found", "Could not find VU1 Hub on the USB bus.\r\n"\
|
|
541
|
+
"Please make sure it is plugged in and (if necessary) drivers are installed.\r\n"\
|
|
542
|
+
"Then restart the VU Server application.\r\nVU server application will close now.")
|
|
543
|
+
sys.exit(0)
|
|
544
|
+
# raise Exception("Could not find VU1 Dials Hub. Please make sure it's plugged in and (if necessary) drivers are installed.")
|
|
545
|
+
|
|
546
|
+
logger.info("VU1 HUB port: {}".format(self.serialPort))
|
|
547
|
+
self.dial_driver = DialSerialDriver(self.serialPort)
|
|
548
|
+
self.dial_handler = ServerDialHandler(self.dial_driver, self.config)
|
|
549
|
+
|
|
550
|
+
# If we don't see any dials, try looking/provisioning some
|
|
551
|
+
if len(self.dial_handler.dials) <= 1:
|
|
552
|
+
logger.info("No additional dials found. Searching the bus for new ones...")
|
|
553
|
+
self.dial_handler.provision_dials(num_attempts=3)
|
|
554
|
+
|
|
555
|
+
handlers_config = { "handler":self.dial_handler, "config":self.config }
|
|
556
|
+
self.handlers = [
|
|
557
|
+
(r"/api/v0/dial/provision", Dial_Provision, handlers_config),
|
|
558
|
+
(r"/api/v0/dial/list", Dial_Get_List, handlers_config),
|
|
559
|
+
(r"/api/v0/dial/([0-9A-F]*?)/status", Device_Status_Handler, handlers_config),
|
|
560
|
+
(r"/api/v0/dial/([0-9A-F]*?)/set", Device_Set_Handler, handlers_config),
|
|
561
|
+
(r"/api/v0/dial/([0-9A-F]*?)/setRaw", Device_SetRaw_Handler, handlers_config),
|
|
562
|
+
(r"/api/v0/dial/([0-9A-F]*?)/image/set", Device_Set_Image, handlers_config),
|
|
563
|
+
(r"/api/v0/dial/([0-9A-F]*?)/image/get", Dial_Get_Image, handlers_config),
|
|
564
|
+
(r"/api/v0/dial/([0-9A-F]*?)/image/crc", Dial_Get_Image_CRC, handlers_config),
|
|
565
|
+
(r"/api/v0/dial/([0-9A-F]*?)/backlight", Device_Backlight_Handler, handlers_config),
|
|
566
|
+
(r"/api/v0/dial/([0-9A-F]*?)/name", Dial_Set_Dial_Name, handlers_config),
|
|
567
|
+
(r"/api/v0/dial/([0-9A-F]*?)/reload", Dial_Reload_Device_Info, handlers_config),
|
|
568
|
+
(r"/api/v0/dial/([0-9A-F]*?)/calibrate", Dial_Set_Calibration, handlers_config),
|
|
569
|
+
(r"/api/v0/dial/([0-9A-F]*?)/easing/dial", Dial_Set_Easing_Dial, handlers_config),
|
|
570
|
+
(r"/api/v0/dial/([0-9A-F]*?)/easing/backlight", Dial_Set_Easing_Backlight, handlers_config),
|
|
571
|
+
(r"/api/v0/dial/([0-9A-F]*?)/easing/get", Dial_Get_Easing_Config, handlers_config),
|
|
572
|
+
(r"/api/v0/admin/keys/list", Admin_Keys_List, handlers_config),
|
|
573
|
+
(r"/api/v0/admin/keys/create", Admin_Keys_Create, handlers_config),
|
|
574
|
+
(r"/api/v0/admin/keys/remove", Admin_Keys_Remove, handlers_config),
|
|
575
|
+
(r"/api/v0/admin/keys/update", Admin_Keys_Update, handlers_config),
|
|
576
|
+
(r"/", FileHandler),
|
|
577
|
+
(r'/(.*)', StaticFileHandler, {'path': WEB_ROOT}),
|
|
578
|
+
]
|
|
579
|
+
|
|
580
|
+
self.server_settings = {
|
|
581
|
+
"debug": True,
|
|
582
|
+
"autoreload": False,
|
|
583
|
+
# "autoreload": True,
|
|
584
|
+
"default_handler_class": Default_404_Handler,
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
def signal_handler(self, signal, frame):
|
|
588
|
+
pid_lock('server', False)
|
|
589
|
+
self.shut_down_dials()
|
|
590
|
+
IOLoop.current().add_callback_from_signal(self.shutdown_server)
|
|
591
|
+
print('\r\nYou pressed Ctrl+C!')
|
|
592
|
+
show_info_msg("CTRL+C", "CTRL+C pressed.\r\nVU Server app will exit now.") # Remove if becomes annoying
|
|
593
|
+
sys.exit(0)
|
|
594
|
+
|
|
595
|
+
def shut_down_dials(self):
|
|
596
|
+
print("Shutting down dials...")
|
|
597
|
+
try:
|
|
598
|
+
for dial in self.dial_driver.dials:
|
|
599
|
+
logger.debug(f"Shutting down dial {dial}")
|
|
600
|
+
self.dial_driver.set_dial(dialID=dial, value=0)
|
|
601
|
+
self.dial_driver.dial_set_backlight(device=dial, red=0, green=0, blue=0, white=0)
|
|
602
|
+
|
|
603
|
+
except Exception as e:
|
|
604
|
+
logger.error("Failed to gracefully shut down dials")
|
|
605
|
+
logger.error(f"Error: {e}")
|
|
606
|
+
|
|
607
|
+
def shutdown_server(self):
|
|
608
|
+
logger.info('Stopping API server')
|
|
609
|
+
logger.info('Will shutdown in 3 seconds ...')
|
|
610
|
+
io_loop = IOLoop.instance()
|
|
611
|
+
deadline = time.time() + 3
|
|
612
|
+
|
|
613
|
+
def stop_loop():
|
|
614
|
+
now = time.time()
|
|
615
|
+
if now < deadline and (io_loop._callbacks or io_loop._timeouts):
|
|
616
|
+
io_loop.add_timeout(now + 1, stop_loop)
|
|
617
|
+
else:
|
|
618
|
+
io_loop.stop()
|
|
619
|
+
logger.info('Shutdown')
|
|
620
|
+
stop_loop()
|
|
621
|
+
|
|
622
|
+
def run_forever(self):
|
|
623
|
+
logger.info("Karanovic Research Dials - Starting API server")
|
|
624
|
+
app = Application(self.handlers, **self.server_settings)
|
|
625
|
+
|
|
626
|
+
# Port from config.yaml or default 5340
|
|
627
|
+
server_config = self.config.get_server_config()
|
|
628
|
+
port = server_config.get('port', 5340)
|
|
629
|
+
master_key = server_config.get('master_key', None)
|
|
630
|
+
dial_update_period = server_config.get('dial_update_period', 1000)
|
|
631
|
+
logger.info(f"VU1 API server is listening on http://localhost:{port}")
|
|
632
|
+
app.listen(port)
|
|
633
|
+
|
|
634
|
+
if master_key is not None:
|
|
635
|
+
logger.info("Master Key is present in config.yaml (or using default)")
|
|
636
|
+
logger.info(f"Provide '{master_key}' to your main application.")
|
|
637
|
+
logger.info("to allow it to manage this server and the VU dials.")
|
|
638
|
+
else:
|
|
639
|
+
show_error_msg(title='Key missing from config', message='Entry "master_key" is missing from the "config.yaml"!')
|
|
640
|
+
logger.error("Master Key is MISSING from config.yaml")
|
|
641
|
+
logger.error("Check your 'config.yaml' or add it manually under 'server' section.")
|
|
642
|
+
sys.exit(0)
|
|
643
|
+
|
|
644
|
+
pc = PeriodicCallback(self.dial_handler.periodic_dial_update, dial_update_period)
|
|
645
|
+
pc.start()
|
|
646
|
+
|
|
647
|
+
IOLoop.instance().start()
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
def main(cmd_args=None):
|
|
651
|
+
if cmd_args is None:
|
|
652
|
+
set_logger_level('info')
|
|
653
|
+
else:
|
|
654
|
+
set_logger_level(cmd_args.logging)
|
|
655
|
+
try:
|
|
656
|
+
Dial_API_Service().run_forever()
|
|
657
|
+
except SerialException:
|
|
658
|
+
logger.exception("VU Dials API service - Serial port access denied")
|
|
659
|
+
show_warning_msg("Serial Port Access Denied", "VU Server failed to start. Access to serial port denied.\r\nVU Server already running?")
|
|
660
|
+
except Exception as e:
|
|
661
|
+
logger.exception("VU Dials API service crashed during setup.")
|
|
662
|
+
logger.exception(f"Unhandled exception: ({type(e)}) {e}")
|
|
663
|
+
show_error_msg("Crashed", "VU Server has crashed unexpectedly!\r\nPlease check log files for more information.")
|
|
664
|
+
os._exit(0)
|
|
665
|
+
|
|
666
|
+
if __name__ == '__main__':
|
|
667
|
+
parser = argparse.ArgumentParser(description='Karanovic Research - VU Dials API service')
|
|
668
|
+
parser.add_argument('-l', '--logging', type=str, default='info', help='Set logging level. Default is `info`')
|
|
669
|
+
args = parser.parse_args()
|
|
670
|
+
main(args)
|