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.
Files changed (184) hide show
  1. benchlab/__init__.py +14 -0
  2. benchlab/__main__.py +21 -0
  3. benchlab/config/README.md +325 -0
  4. benchlab/config/__init__.py +9 -0
  5. benchlab/config/config_client.py +631 -0
  6. benchlab/config/config_manager.py +495 -0
  7. benchlab/config/config_tool.py +358 -0
  8. benchlab/config/diff.py +163 -0
  9. benchlab/config/examples/complete_config.json +56 -0
  10. benchlab/config/examples/rgb_lighting.json +24 -0
  11. benchlab/config/examples/simple_fan.json +32 -0
  12. benchlab/config/schema.py +138 -0
  13. benchlab/core/README.md +154 -0
  14. benchlab/core/__init__.py +78 -0
  15. benchlab/core/config.py +145 -0
  16. benchlab/core/datasource.py +1209 -0
  17. benchlab/core/datasource_manager.py +418 -0
  18. benchlab/core/device_registry.py +206 -0
  19. benchlab/core/discovery.py +92 -0
  20. benchlab/core/infrastructure.py +309 -0
  21. benchlab/core/process_manager.py +356 -0
  22. benchlab/core/retry.py +85 -0
  23. benchlab/core/shared_serial.py +235 -0
  24. benchlab/core/statistics.py +224 -0
  25. benchlab/csv_log/README.md +206 -0
  26. benchlab/csv_log/__init__.py +5 -0
  27. benchlab/csv_log/csv_logger_enhanced.py +416 -0
  28. benchlab/csv_log/message_batcher.py +361 -0
  29. benchlab/csv_log/requirements.txt +3 -0
  30. benchlab/csv_log/smart_retry.py +466 -0
  31. benchlab/graph/README.md +117 -0
  32. benchlab/graph/__init__.py +5 -0
  33. benchlab/graph/app.py +353 -0
  34. benchlab/graph/requirements.txt +2 -0
  35. benchlab/graph/runner.py +42 -0
  36. benchlab/graph/ui.py +288 -0
  37. benchlab/hwinfo/README.md +135 -0
  38. benchlab/hwinfo/__init__.py +5 -0
  39. benchlab/hwinfo/hwinfo_export.py +386 -0
  40. benchlab/hwinfo/requirements.txt +1 -0
  41. benchlab/launcher.py +387 -0
  42. benchlab/link/.env.EXAMPLE +10 -0
  43. benchlab/link/README.md +172 -0
  44. benchlab/link/__init__.py +1 -0
  45. benchlab/link/link_main.py +479 -0
  46. benchlab/main.py +462 -0
  47. benchlab/menu.py +729 -0
  48. benchlab/menu_classic.py +410 -0
  49. benchlab/mqtt/README.md +201 -0
  50. benchlab/mqtt/__init__.py +5 -0
  51. benchlab/mqtt/mqtt.config_template +2 -0
  52. benchlab/mqtt/mqtt_publisher.py +540 -0
  53. benchlab/mqtt/requirements.txt +3 -0
  54. benchlab/prefs.py +67 -0
  55. benchlab/restapi/.env.example +17 -0
  56. benchlab/restapi/__init__.py +5 -0
  57. benchlab/restapi/favicon.ico +0 -0
  58. benchlab/restapi/readme.md +202 -0
  59. benchlab/restapi/requirements.txt +5 -0
  60. benchlab/restapi/telemetry_api.py +808 -0
  61. benchlab/sources.py +515 -0
  62. benchlab/tools.py +147 -0
  63. benchlab/tui/README.md +266 -0
  64. benchlab/tui/__init__.py +5 -0
  65. benchlab/tui/config.py +477 -0
  66. benchlab/tui/requirements.txt +2 -0
  67. benchlab/tui/tui_core.py +1298 -0
  68. benchlab/tui/tui_main.py +418 -0
  69. benchlab/vu/README.md +64 -0
  70. benchlab/vu/VU-Server/NOTICE.md +13 -0
  71. benchlab/vu/VU-Server/README.md +80 -0
  72. benchlab/vu/VU-Server/Running_from_source_on_linux.md +97 -0
  73. benchlab/vu/VU-Server/__init__.py +0 -0
  74. benchlab/vu/VU-Server/assets/vu1_hello_world.png +0 -0
  75. benchlab/vu/VU-Server/community_applications.md +13 -0
  76. benchlab/vu/VU-Server/config.yaml +9 -0
  77. benchlab/vu/VU-Server/database.py +256 -0
  78. benchlab/vu/VU-Server/dial_driver.py +534 -0
  79. benchlab/vu/VU-Server/dials/Comms_Hub_Gauge.py +78 -0
  80. benchlab/vu/VU-Server/dials/Comms_Hub_Server.py +100 -0
  81. benchlab/vu/VU-Server/dials/__init__.py +0 -0
  82. benchlab/vu/VU-Server/dials/base_logger.py +100 -0
  83. benchlab/vu/VU-Server/installer/inc/icon.ico +0 -0
  84. benchlab/vu/VU-Server/installer/install.nsi +148 -0
  85. benchlab/vu/VU-Server/make_version.py +40 -0
  86. benchlab/vu/VU-Server/pylintrc +628 -0
  87. benchlab/vu/VU-Server/requirements.txt +9 -0
  88. benchlab/vu/VU-Server/serial_driver.py +244 -0
  89. benchlab/vu/VU-Server/server.py +670 -0
  90. benchlab/vu/VU-Server/server_config.py +255 -0
  91. benchlab/vu/VU-Server/server_dial_handler.py +334 -0
  92. benchlab/vu/VU-Server/vu_notifications.py +24 -0
  93. benchlab/vu/VU-Server/www/android-chrome-192x192.png +0 -0
  94. benchlab/vu/VU-Server/www/android-chrome-512x512.png +0 -0
  95. benchlab/vu/VU-Server/www/apple-touch-icon.png +0 -0
  96. benchlab/vu/VU-Server/www/assets/css/demo.css +291 -0
  97. benchlab/vu/VU-Server/www/assets/css/demo.min.css +9 -0
  98. benchlab/vu/VU-Server/www/assets/css/demo.rtl.css +276 -0
  99. benchlab/vu/VU-Server/www/assets/css/demo.rtl.min.css +9 -0
  100. benchlab/vu/VU-Server/www/assets/css/tabler-flags.css +1084 -0
  101. benchlab/vu/VU-Server/www/assets/css/tabler-flags.min.css +9 -0
  102. benchlab/vu/VU-Server/www/assets/css/tabler-flags.rtl.css +1084 -0
  103. benchlab/vu/VU-Server/www/assets/css/tabler-flags.rtl.min.css +9 -0
  104. benchlab/vu/VU-Server/www/assets/css/tabler-payments.css +528 -0
  105. benchlab/vu/VU-Server/www/assets/css/tabler-payments.min.css +9 -0
  106. benchlab/vu/VU-Server/www/assets/css/tabler-payments.rtl.css +528 -0
  107. benchlab/vu/VU-Server/www/assets/css/tabler-payments.rtl.min.css +9 -0
  108. benchlab/vu/VU-Server/www/assets/css/tabler-social.css +10 -0
  109. benchlab/vu/VU-Server/www/assets/css/tabler-social.min.css +8 -0
  110. benchlab/vu/VU-Server/www/assets/css/tabler-social.rtl.css +10 -0
  111. benchlab/vu/VU-Server/www/assets/css/tabler-social.rtl.min.css +8 -0
  112. benchlab/vu/VU-Server/www/assets/css/tabler-vendors.css +1504 -0
  113. benchlab/vu/VU-Server/www/assets/css/tabler-vendors.min.css +9 -0
  114. benchlab/vu/VU-Server/www/assets/css/tabler-vendors.rtl.css +1504 -0
  115. benchlab/vu/VU-Server/www/assets/css/tabler-vendors.rtl.min.css +9 -0
  116. benchlab/vu/VU-Server/www/assets/css/tabler.css +25813 -0
  117. benchlab/vu/VU-Server/www/assets/css/tabler.min.css +13 -0
  118. benchlab/vu/VU-Server/www/assets/css/tabler.rtl.css +25777 -0
  119. benchlab/vu/VU-Server/www/assets/css/tabler.rtl.min.css +13 -0
  120. benchlab/vu/VU-Server/www/assets/css/theme.min.css +261 -0
  121. benchlab/vu/VU-Server/www/assets/img/header.png +0 -0
  122. benchlab/vu/VU-Server/www/assets/img/logo.png +0 -0
  123. benchlab/vu/VU-Server/www/assets/js/dark-theme.js +35 -0
  124. benchlab/vu/VU-Server/www/assets/js/dark-theme.min.js +9 -0
  125. benchlab/vu/VU-Server/www/assets/js/demo.js +132 -0
  126. benchlab/vu/VU-Server/www/assets/js/demo.min.js +9 -0
  127. benchlab/vu/VU-Server/www/assets/js/jquery.min.js +2 -0
  128. benchlab/vu/VU-Server/www/assets/js/tabler.esm.js +7482 -0
  129. benchlab/vu/VU-Server/www/assets/js/tabler.esm.min.js +15 -0
  130. benchlab/vu/VU-Server/www/assets/js/tabler.js +7543 -0
  131. benchlab/vu/VU-Server/www/assets/js/tabler.min.js +15 -0
  132. benchlab/vu/VU-Server/www/assets/js/vu1_api.js +68 -0
  133. benchlab/vu/VU-Server/www/assets/js/vu1_gui_api_key_settings.js +167 -0
  134. benchlab/vu/VU-Server/www/assets/js/vu1_gui_api_keys.js +138 -0
  135. benchlab/vu/VU-Server/www/assets/js/vu1_gui_dial.js +179 -0
  136. benchlab/vu/VU-Server/www/assets/js/vu1_gui_home.js +60 -0
  137. benchlab/vu/VU-Server/www/assets/js/vu1_gui_root.js +125 -0
  138. benchlab/vu/VU-Server/www/favicon-16x16.png +0 -0
  139. benchlab/vu/VU-Server/www/favicon-32x32.png +0 -0
  140. benchlab/vu/VU-Server/www/favicon.ico +0 -0
  141. benchlab/vu/VU-Server/www/favicon.png +0 -0
  142. benchlab/vu/VU-Server/www/index.html +47 -0
  143. benchlab/vu/VU-Server/www/views/api_keys.html +151 -0
  144. benchlab/vu/VU-Server/www/views/dial.html +286 -0
  145. benchlab/vu/VU-Server/www/views/footer.html +27 -0
  146. benchlab/vu/VU-Server/www/views/header.html +86 -0
  147. benchlab/vu/VU-Server/www/views/key_settings.html +132 -0
  148. benchlab/vu/VU-Server/www/views/start.html +124 -0
  149. benchlab/vu/__init__.py +5 -0
  150. benchlab/vu/assets/Barlow-Bold.ttf +0 -0
  151. benchlab/vu/assets/bl_dial_144x200.png +0 -0
  152. benchlab/vu/assets/bl_logo_144x200.png +0 -0
  153. benchlab/vu/devices.py +166 -0
  154. benchlab/vu/requirements.txt +24 -0
  155. benchlab/vu/sensors.py +18 -0
  156. benchlab/vu/vu_dial.config_template +90 -0
  157. benchlab/vu/vu_logo_gen.py +97 -0
  158. benchlab/vu/vu_server.config_template +5 -0
  159. benchlab/vu/vu_server_config.py +40 -0
  160. benchlab/vu/vu_server_manager.py +192 -0
  161. benchlab/vu/vu_tui.py +919 -0
  162. benchlab/vu/vu_updater.py +540 -0
  163. benchlab/wigidash/README.md +182 -0
  164. benchlab/wigidash/__init__.py +5 -0
  165. benchlab/wigidash/assets/Inter-Bold.ttf +0 -0
  166. benchlab/wigidash/assets/Inter.ttf +0 -0
  167. benchlab/wigidash/assets/benchlab.png +0 -0
  168. benchlab/wigidash/benchlab_fleet.py +150 -0
  169. benchlab/wigidash/benchlab_graph.py +517 -0
  170. benchlab/wigidash/benchlab_overview.py +426 -0
  171. benchlab/wigidash/benchlab_telemetry.py +105 -0
  172. benchlab/wigidash/benchlab_ui.py +190 -0
  173. benchlab/wigidash/benchlab_utils.py +182 -0
  174. benchlab/wigidash/requirements.txt +9 -0
  175. benchlab/wigidash/wigidash_device.py +541 -0
  176. benchlab/wigidash/wigidash_manager.py +342 -0
  177. benchlab/wigidash/wigidash_session.py +355 -0
  178. benchlab/wigidash/wigidash_usb.py +247 -0
  179. benchlab/wigidash/wigidash_widget.py +88 -0
  180. benchlab_pytools-3.0.0.dist-info/METADATA +239 -0
  181. benchlab_pytools-3.0.0.dist-info/RECORD +184 -0
  182. benchlab_pytools-3.0.0.dist-info/WHEEL +4 -0
  183. benchlab_pytools-3.0.0.dist-info/entry_points.txt +2 -0
  184. benchlab_pytools-3.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,534 @@
1
+ import os
2
+ import time
3
+ import textwrap
4
+ import math
5
+ from datetime import timedelta
6
+ from io import BytesIO
7
+ import numpy as np
8
+ from PIL import Image
9
+ from serial.tools.list_ports import comports
10
+ from dials.Comms_Hub_Server import hub_config, hub_commands, hub_data_types, hub_status_codes
11
+ from dials.base_logger import logger
12
+ from serial_driver import SerialHardware
13
+
14
+
15
+ class DialSerialDriver(SerialHardware):
16
+ dials = {}
17
+ hub_info = {}
18
+
19
+ def __init__(self, port_info):
20
+ super(DialSerialDriver, self).__init__(port_info, timeout=2)
21
+
22
+ self.commands = hub_commands()
23
+ self.hub_config = hub_config()
24
+ self.data_type = hub_data_types()
25
+ self.status_codes = hub_status_codes()
26
+
27
+ def _get_max_packet_size(self):
28
+ max_size = math.floor( (self.hub_config.GAUGE_COMM_MAX_RX_DATA_LEN - (self.hub_config.GAUGE_COMM_HEADER_LEN*2) )/2)
29
+ return max_size
30
+
31
+ def _sendCommand(self, cmd, dataType, dataLen=0, data=None):
32
+ if dataLen == 0:
33
+ payload = ">{:02X}{:02X}{:04X}".format(cmd, dataType, dataLen)
34
+ elif dataLen == 1:
35
+ if data < 256:
36
+ payload = ">{:02X}{:02X}{:04X}{:02X}".format(cmd, dataType, dataLen, data)
37
+ elif data >= 256:
38
+ payload = ">{:02X}{:02X}{:04X}{:04X}".format(cmd, dataType, dataLen+1, data)
39
+ elif dataLen > 1:
40
+ formattedData = ""
41
+ for elem in data:
42
+ if isinstance(elem, str):
43
+ formattedData = formattedData + f"{int(elem):0{2 if int(elem) < 256 else 4}X}"
44
+ elif isinstance(elem, int):
45
+ formattedData = formattedData + f"{elem:0{2 if elem < 256 else 4}X}"
46
+ else:
47
+ raise ValueError('Unsupported data type ({})'.format(type(elem)))
48
+
49
+ payload = ">{:02X}{:02X}{:04X}{}".format(cmd, dataType, int(len(formattedData)/2), formattedData)
50
+
51
+ logger.debug(f"CMD:{cmd} - Type:{dataType} - Len:{dataLen}".format(payload))
52
+ logger.debug("Sending `{}`".format(payload))
53
+ response = self.serial_transaction(payload)
54
+ return self._parseResponse(response)
55
+
56
+ def _send_cmd_with_uin32(self, dialID, cmd, value, dt=None):
57
+ if dt is None:
58
+ dt = self.data_type.COMM_DATA_SINGLE_VALUE
59
+ data = [dialID, ((value>>24)&0xFF), ((value>>16)&0xFF), ((value>>8)&0xFF), (value&0xFF)]
60
+ return self._sendCommand(cmd, dt, len(data), data)
61
+
62
+ def _parseResponse(self, response):
63
+ for line in response:
64
+ logger.debug(line)
65
+ if line.startswith('<'):
66
+ cmd = line[1:3]
67
+ dataType = line[3:5]
68
+ dataLen = line[5:9]
69
+ data = line[9:]
70
+ ret = {'cmd':cmd, 'dataType':dataType, 'dataLen':dataLen, 'data':data}
71
+
72
+ if dataType == self.data_type.COMM_DATA_STATUS_CODE:
73
+ return self._checkStatus(ret['data'])
74
+ return ret['data']
75
+ return False
76
+
77
+ def _checkStatus(self, statusCode):
78
+ if int(statusCode, 16) == self.status_codes.GAUGE_STATUS_OK:
79
+ return True
80
+ logger.error("Error code: {}".format(int(statusCode, 16)))
81
+ return False
82
+
83
+ def _convert_hex_str_to_str(self, hex_string):
84
+ if not hex_string:
85
+ logger.error(f"Empty hex string received {hex_string}")
86
+ return ''
87
+
88
+ if len(hex_string)%2:
89
+ logger.error(f"Hex string should be divisible by 2! (len={len(hex_string)})")
90
+ return ''
91
+
92
+ try:
93
+ byte_array = bytearray.fromhex(hex_string)
94
+ hex_string = byte_array.decode()
95
+ return hex_string
96
+ except Exception as e:
97
+ logger.error(e)
98
+ return ''
99
+
100
+ def _convert_hex_str_to_byte_array(self, hex_string):
101
+ if not hex_string:
102
+ logger.error(f"Empty hex string received {hex_string}")
103
+ return bytes()
104
+
105
+ if len(hex_string)%2:
106
+ logger.error(f"Hex string should be divisible by 2! (len={len(hex_string)})")
107
+ return bytes()
108
+
109
+ try:
110
+ byte_array = bytearray.fromhex(hex_string)
111
+ return byte_array
112
+ except Exception as e:
113
+ logger.error(e)
114
+ return bytes()
115
+
116
+ def bus_rescan(self):
117
+ logger.debug("@bus_rescan")
118
+ return self._sendCommand(self.commands.COMM_CMD_RESCAN_BUS, self.data_type.COMM_DATA_NONE)
119
+
120
+ def get_dial_list(self, rescan=False):
121
+ logger.debug(f"@get_dial_list(rescan={rescan})")
122
+ if rescan:
123
+ resp = self.bus_rescan()
124
+ resp = self._sendCommand(self.commands.COMM_CMD_GET_DEVICES_MAP, self.data_type.COMM_DATA_NONE)
125
+ if not resp:
126
+ logger.error("Invalid response received from COMM_CMD_GET_DEVICES_MAP")
127
+ logger.error(resp)
128
+ return []
129
+
130
+ resp = textwrap.wrap(resp, 2)
131
+ onlineDials = []
132
+ for key, elem in enumerate(resp):
133
+ if int(elem, 16) == 1:
134
+ onlineDials.append(key)
135
+
136
+ for dialIndex in onlineDials:
137
+ deviceUID = self.dial_get_uid(dialIndex) # Read dial UID
138
+ # Friendly name will be added from config
139
+ self.dials[dialIndex] = {
140
+ 'index': str(dialIndex),
141
+ 'uid': deviceUID,
142
+ 'dial_name': 'Not set',
143
+ 'value': 0,
144
+ 'rgbw': [0, 0, 0, 0],
145
+ 'easing': {
146
+ 'dial_step': '?',
147
+ 'dial_period': '?',
148
+ 'backlight_step': '?',
149
+ 'backlight_period': '?',
150
+ },
151
+ 'fw_hash': '?',
152
+ 'fw_version': '?',
153
+ 'hw_version': '?',
154
+ 'protocol_version': '?',
155
+ }
156
+
157
+ dialList = []
158
+ for key, val in self.dials.items():
159
+ dialList.append(val)
160
+
161
+ return dialList
162
+
163
+ def set_all_dials_to(self, value):
164
+ logger.debug(f"@set_all_dials_to(value={value})")
165
+ dials = []
166
+ values = []
167
+
168
+ for dial in self.dials:
169
+ dials.append(dial)
170
+ values.append(value)
171
+ self.dials[dial]['value'] = 0
172
+
173
+ self.dial_multiple_set_percent(dials, values)
174
+
175
+
176
+ def get_dial(self, dialID=None, UID=None):
177
+ if dialID is None and UID is None:
178
+ logger.error("Both dial ID and UID can't be none!")
179
+ return {}
180
+
181
+ if dialID is None:
182
+ dialID = self._findDial(UID)
183
+
184
+ if dialID is None:
185
+ logger.error("Dial with UID `{}` is not present.".format(UID))
186
+ return None
187
+
188
+ return self.dials[dialID]
189
+
190
+ def set_dial(self, dialID=None, UID=None, value=None, sendCMD=True):
191
+ if dialID is None and UID is None:
192
+ logger.error("Both dial ID and UID can't be none!")
193
+ return {}
194
+
195
+ if dialID is None:
196
+ dialID = self._findDial(UID)
197
+
198
+ if dialID is None:
199
+ logger.error("Dial with UID `{}` is not present.".format(UID))
200
+ return False
201
+
202
+ if sendCMD:
203
+ self.dial_single_set_percent(dialID, int(value))
204
+ return True
205
+
206
+ def _findDial(self, UID):
207
+ for entry in self.dials:
208
+ if self.dials[entry]['uid'] == UID:
209
+ return entry
210
+ return None
211
+
212
+ def _verify_device(self, device):
213
+ if isinstance(device, str):
214
+ if len(device) <= 3:
215
+ return int(device)
216
+ device = self._findDial(device)
217
+
218
+ if device is None:
219
+ logger.error(f"Can not find dial '{device}'")
220
+ return device
221
+
222
+ elif isinstance(device, int):
223
+ return device
224
+
225
+ else:
226
+ raise ValueError(f"Unexpected device type type(device)='{type(device)}'")
227
+
228
+ def dial_get_uid(self, dialIndex):
229
+ logger.debug(f"@dial_get_uid(dialIndex={dialIndex})")
230
+ return self._sendCommand(self.commands.COMM_CMD_GET_DEVICE_UID, self.data_type.COMM_DATA_SINGLE_VALUE, 1, dialIndex)
231
+
232
+ def dial_get_fw_hash(self, dialIndex):
233
+ logger.debug(f"@dial_get_fw_hash(dialIndex={dialIndex})")
234
+ cmd = self.commands.COMM_CMD_GET_BUILD_INFO
235
+ data = dialIndex
236
+ ret = self._sendCommand(cmd, self.data_type.COMM_DATA_SINGLE_VALUE, 1, data)
237
+ return self._convert_hex_str_to_str(ret)
238
+
239
+ def dial_get_fw_version(self, dialIndex):
240
+ logger.debug(f"@dial_get_fw_version(dialIndex={dialIndex})")
241
+ cmd = self.commands.COMM_CMD_GET_FW_INFO
242
+ data = dialIndex
243
+ ret = self._sendCommand(cmd, self.data_type.COMM_DATA_SINGLE_VALUE, 1, data)
244
+ return self._convert_hex_str_to_str(ret)
245
+
246
+ def dial_get_hw_version(self, dialIndex):
247
+ logger.debug(f"@dial_get_hw_version(dialIndex={dialIndex})")
248
+ cmd = self.commands.COMM_CMD_GET_HW_INFO
249
+ data = dialIndex
250
+ ret = self._sendCommand(cmd, self.data_type.COMM_DATA_SINGLE_VALUE, 1, data)
251
+ return self._convert_hex_str_to_str(ret)
252
+
253
+ def dial_get_protocol_version(self, dialIndex):
254
+ logger.debug(f"@dial_get_hw_version(dialIndex={dialIndex})")
255
+ cmd = self.commands.COMM_CMD_GET_PROTOCOL_INFO
256
+ data = dialIndex
257
+ ret = self._sendCommand(cmd, self.data_type.COMM_DATA_SINGLE_VALUE, 1, data)
258
+ return self._convert_hex_str_to_str(ret)
259
+
260
+ def set_dial_power(self, powerOn=True):
261
+ logger.debug(f"@set_dial_power(powerOn={powerOn})")
262
+ data = 0
263
+ if powerOn is True:
264
+ data = 1
265
+ return self._sendCommand(self.commands.COMM_CMD_DIAL_POWER, self.data_type.COMM_DATA_SINGLE_VALUE, 1, data)
266
+
267
+ def dial_calibrate(self, dialID, value, fullScale=True):
268
+ logger.debug(f"@dial_calibrate(dialID={dialID}, value={value}, fullScale={fullScale})")
269
+ if fullScale:
270
+ cmd = self.commands.COMM_CMD_SET_DIAL_CALIBRATE_MAX
271
+ else:
272
+ cmd = self.commands.COMM_CMD_SET_DIAL_CALIBRATE_HALF
273
+
274
+ return self._send_cmd_with_uin32(dialID, cmd, value, dt=self.data_type.COMM_DATA_KEY_VALUE_PAIR)
275
+
276
+ def dial_easing_dial_step(self, dialID, value):
277
+ logger.debug(f"@dial_easing_dial_step(dialID={dialID}, value={value})")
278
+ cmd = self.commands.COMM_CMD_SET_DIAL_EASING_STEP
279
+ data = [dialID, ((value>>24)&0xFF), ((value>>16)&0xFF), ((value>>8)&0xFF), (value&0xFF)]
280
+ return self._sendCommand(cmd, self.data_type.COMM_DATA_SINGLE_VALUE, len(data), data)
281
+
282
+ def dial_easing_dial_period(self, dialID, value):
283
+ logger.debug(f"@dial_easing_dial_period(dialID={dialID}, value={value})")
284
+ cmd = self.commands.COMM_CMD_SET_DIAL_EASING_PERIOD
285
+ data = [dialID, ((value>>24)&0xFF), ((value>>16)&0xFF), ((value>>8)&0xFF), (value&0xFF)]
286
+ return self._sendCommand(cmd, self.data_type.COMM_DATA_SINGLE_VALUE, len(data), data)
287
+
288
+ def dial_easing_backlight_step(self, dialID, value):
289
+ logger.debug(f"@dial_easing_backlight_step(dialID={dialID}, value={value})")
290
+ cmd = self.commands.COMM_CMD_SET_BACKLIGHT_EASING_STEP
291
+ data = [dialID, ((value>>24)&0xFF), ((value>>16)&0xFF), ((value>>8)&0xFF), (value&0xFF)]
292
+ return self._sendCommand(cmd, self.data_type.COMM_DATA_SINGLE_VALUE, len(data), data)
293
+
294
+ def dial_easing_backlight_period(self, dialID, value):
295
+ logger.debug(f"@dial_easing_backlight_period(dialID={dialID}, value={value})")
296
+ cmd = self.commands.COMM_CMD_SET_BACKLIGHT_EASING_PERIOD
297
+ data = [dialID, ((value>>24)&0xFF), ((value>>16)&0xFF), ((value>>8)&0xFF), (value&0xFF)]
298
+ return self._sendCommand(cmd, self.data_type.COMM_DATA_SINGLE_VALUE, len(data), data)
299
+
300
+ def dial_easing_get_config(self, dialID):
301
+ easing = { 'dial_step':0, 'dial_period':0, 'backlight_step':0, 'backlight_period':0 }
302
+ logger.debug(f"@dial_easing_get_config(dialID={dialID})")
303
+ ret = self._sendCommand(self.commands.COMM_CMD_GET_EASING_CONFIG, self.data_type.COMM_DATA_SINGLE_VALUE, 1, dialID)
304
+ ret = self._convert_hex_str_to_byte_array(ret)
305
+
306
+ easing['dial_step'] = int(ret[0]) << 24 | int(ret[1]) << 16 | int(ret[2]) << 8 | int(ret[3])
307
+ easing['dial_period'] = int(ret[4]) << 24 | int(ret[5]) << 16 | int(ret[6]) << 8 | int(ret[7])
308
+ easing['backlight_step'] = int(ret[8]) << 24 | int(ret[9]) << 16 | int(ret[10]) << 8 | int(ret[11])
309
+ easing['backlight_period'] = int(ret[12]) << 24 | int(ret[13]) << 16 | int(ret[14]) << 8 | int(ret[15])
310
+ # logger.debug(f"@ret:{ret}")
311
+ # logger.debug(f"@easing:{easing}")
312
+
313
+ return easing
314
+
315
+ def dial_single_set_raw(self, dialID, value):
316
+ logger.debug(f"@dial_single_set_raw(dialID={dialID}, value={value})")
317
+ data = [dialID, ((value>>8)&0xFF), (value&0xFF)]
318
+ return self._sendCommand(self.commands.COMM_CMD_SET_DIAL_RAW_SINGLE, self.data_type.COMM_DATA_KEY_VALUE_PAIR, len(data), data)
319
+
320
+ def dial_single_set_percent(self, dialID, value):
321
+ logger.debug(f"@dial_single_set_percent(dialID={dialID}, value={value})")
322
+ if self.dials.get(int(dialID), False):
323
+ self.dials[int(dialID)]['value'] = value
324
+ data = [dialID, (value&0xFF)]
325
+ return self._sendCommand(self.commands.COMM_CMD_SET_DIAL_PERC_SINGLE, self.data_type.COMM_DATA_KEY_VALUE_PAIR, len(data), data)
326
+
327
+ def dial_multiple_set_percent(self, devices, values):
328
+ logger.debug(f"@dial_multiple_set_percent(devices={devices}, values={values})")
329
+ if len(devices) != len(values):
330
+ logger.error("Number of devices does not match number of values")
331
+ return False
332
+
333
+ data = []
334
+ for i in range(len(devices)):
335
+ self.set_dial(devices[i], values[i], sendCMD=False)
336
+ data.append(devices[i])
337
+ data.append(values[i])
338
+
339
+ return self._sendCommand(self.commands.COMM_CMD_SET_DIAL_PERC_MULTIPLE, self.data_type.COMM_DATA_KEY_VALUE_PAIR, len(data), data)
340
+
341
+ def dial_display_clear(self, device, whiteBackground=True):
342
+ logger.debug(f"@dial_display_clear(device={device}, whiteBackground={whiteBackground})")
343
+ if whiteBackground:
344
+ data = [int(device), 0]
345
+ else:
346
+ data = [int(device), 1]
347
+ return self._sendCommand(self.commands.COMM_CMD_DISPLAY_CLEAR, self.data_type.COMM_DATA_SINGLE_VALUE, len(data), data)
348
+
349
+ def dial_display_goto_xy(self, device, x, y):
350
+ logger.debug(f"@dial_display_goto_xy(device={device}, x={x}, y={y})")
351
+ data = [int(device), ((x>>8)&0xFF), (x&0xFF), ((y>>8)&0xFF), (y&0xFF)]
352
+ return self._sendCommand(self.commands.COMM_CMD_DISPLAY_GOTO_XY, self.data_type.COMM_DATA_SINGLE_VALUE, len(data), data)
353
+
354
+ def display_send_image(self, device, img_filepath):
355
+ logger.debug(f"@display_send_image(device={device}, img_filepath={img_filepath})")
356
+ if not os.path.exists(img_filepath):
357
+ logger.error(f"File '{img_filepath}' does not exist.")
358
+ return False
359
+
360
+ img_data = self.img_to_binary(img_filepath, True)
361
+ return self.display_send_image_data(device, img_data)
362
+
363
+ def display_send_image_data(self, device, imageData):
364
+ logger.debug(f"@display_send_image_data(device={device}, imageData={imageData})")
365
+ device = self._verify_device(device)
366
+
367
+ # chunkSize = self._get_max_packet_size()
368
+ chunkSize = 1000 # 1000 bytes at a time
369
+ dataLen = len(imageData)
370
+ chunks = math.ceil(len(imageData)/chunkSize)
371
+
372
+ logger.debug(f"Split {dataLen} bytes into {chunks} chunks of {chunkSize} bytes.")
373
+
374
+ start_time = time.time()
375
+ for i in range(chunks):
376
+ start = i*chunkSize
377
+ end = i*chunkSize + chunkSize
378
+ dataChunk = imageData[start:end]
379
+ if not self._send_image_chunk(device, dataChunk):
380
+ return False
381
+ time.sleep(0.2)
382
+ end_time = time.time()
383
+ logger.debug(f"Send image data took {timedelta(seconds=end_time-start_time)}")
384
+ return True
385
+
386
+ def _send_image_chunk(self, device, imageBuffer):
387
+ logger.debug(f"@_send_image_chunk(device={device})")
388
+ if len(imageBuffer) <= 0:
389
+ logger.error("Invalid image buffer size!")
390
+ return False
391
+ if isinstance(device, str):
392
+ device = self._findDial(device)
393
+ device = int(device)
394
+
395
+ data = [ device ]
396
+ data.extend(imageBuffer)
397
+ return self._sendCommand(self.commands.COMM_CMD_DISPLAY_IMG_DATA, self.data_type.COMM_DATA_SINGLE_VALUE, len(data), data)
398
+
399
+ def _format_bits(self, bits):
400
+ buff = []
401
+ for i in bits:
402
+ if i > 127:
403
+ buff.append(1)
404
+ else:
405
+ buff.append(0)
406
+ return buff
407
+
408
+ def binary_to_image_data(self, image):
409
+ img = Image.open(Image.open(BytesIO(image)))
410
+ img = img.convert("L")
411
+
412
+ imgData = np.asarray(img)
413
+ imgData = imgData.T.tolist()
414
+
415
+ buff = []
416
+ # Each byte is 8 vertical bits
417
+ for bits in imgData:
418
+ bits = self._format_bits(bits)
419
+ byte = [int("".join(map(str, bits[i:i+8])), 2) for i in range(0, len(bits), 8)]
420
+ buff.append(byte)
421
+
422
+ buff = [item for sublist in buff for item in sublist]
423
+
424
+ return buff
425
+
426
+ def img_to_binary(self, img_filepath, flatten=True):
427
+ buff = []
428
+
429
+ if not os.path.exists(img_filepath):
430
+ logger.error(f"File {img_filepath} does not exist!. Returning empty array.")
431
+ return buff
432
+
433
+ try:
434
+ #Load image and convert to greyscale
435
+ img = Image.open(img_filepath)
436
+ img = img.convert("L")
437
+
438
+ imgData = np.asarray(img)
439
+ imgData = imgData.T.tolist()
440
+
441
+ # Each byte is 8 vertical bits
442
+ for bits in imgData:
443
+ bits = self._format_bits(bits)
444
+ byte = [int("".join(map(str, bits[i:i+8])), 2) for i in range(0, len(bits), 8)]
445
+ buff.append(byte)
446
+
447
+ if flatten:
448
+ buff = [item for sublist in buff for item in sublist]
449
+ except Exception as e:
450
+ logger.error(e)
451
+
452
+ return buff
453
+
454
+ def update_display(self, device, imageData=None, imageFile=None):
455
+ logger.debug(f"@update_display(device={device})")
456
+ device = self._verify_device(device)
457
+
458
+ self.dial_display_clear(device, True)
459
+ self.dial_display_goto_xy(device, 0, 0)
460
+
461
+ if imageData is not None:
462
+ self.display_send_image_data(device, imageData)
463
+ elif imageFile is not None:
464
+ self.display_send_image(device, imageFile)
465
+ else:
466
+ raise ValueError("Image data and ImageFile can't both be none!")
467
+ self.dial_display_show(device)
468
+ return True
469
+
470
+
471
+ def get_dial_rx_buffer_size(self, device):
472
+ logger.debug(f"@get_dial_rx_buffer_size(device={device})")
473
+ rxLen = self._sendCommand(self.commands.COMM_CMD_RX_BUFFER_SIZE, self.data_type.COMM_DATA_SINGLE_VALUE, 1, int(device))
474
+ rxLen = int(rxLen[:8], 16)
475
+ return rxLen
476
+
477
+ def dial_display_show(self, device):
478
+ logger.debug(f"@dial_display_show(device={device})")
479
+ return self._sendCommand(self.commands.COMM_CMD_DISPLAY_SHOW_IMG, self.data_type.COMM_DATA_SINGLE_VALUE, 1, int(device))
480
+
481
+ def dial_set_backlight(self, device, red, green, blue, white):
482
+ logger.debug(f"@dial_set_backlight(device={device}, red={red}, green={green}, green={green}, blue={blue}, white={white})")
483
+ device = self._verify_device(device)
484
+ self.dials[device]['rgbw'][0] = red
485
+ self.dials[device]['rgbw'][1] = green
486
+ self.dials[device]['rgbw'][2] = blue
487
+ self.dials[device]['rgbw'][3] = white
488
+ data = [device, red, green, blue, white]
489
+ return self._sendCommand(self.commands.COMM_CMD_SET_RGB_BACKLIGHT, self.data_type.COMM_DATA_MULTIPLE_VALUE, len(data), data)
490
+
491
+ def dial_send_keep_comm_alive(self, device):
492
+ pass
493
+ # logger.debug(f"@dial_send_keep_comm_alive(device={device})")
494
+ # device = self._verify_device(device)
495
+ # return self._sendCommand(self.commands.DG_HUB_TO_DEV_KEEP_ALIVE, self.data_type.COMM_DATA_NONE)
496
+
497
+
498
+ def provision_dials(self):
499
+ logger.debug("@provision_dials")
500
+ return self._sendCommand(self.commands.COMM_CMD_PROVISION_DEVICE, self.data_type.COMM_DATA_NONE)
501
+
502
+ def reset_all_devices(self):
503
+ logger.debug("@reset_all_devices")
504
+ return self._sendCommand(self.commands.COMM_CMD_RESET_ALL_DEVICES, self.data_type.COMM_DATA_NONE)
505
+
506
+ def debug_i2c_scan(self):
507
+ logger.debug("@debug_i2c_scan")
508
+ return self._sendCommand(self.commands.COMM_CMD_DEBUG_I2C_SCAN, self.data_type.COMM_DATA_NONE)
509
+
510
+ def debug_print_all_dials(self):
511
+ # Show found dials
512
+ for entry in self.dials:
513
+ dial = self.dials[entry]
514
+ print(f"Dial #{dial['index']}")
515
+ print(f" - UID:{dial['uid']}")
516
+ print(f" - FriendlyName:{dial['friendlyName']}")
517
+ print(f" - Value:{dial['value']}")
518
+
519
+ @classmethod
520
+ def find_gauge_hub(cls):
521
+ availablePorts = comports()
522
+ logger.debug("Searching for COM port with VID:1027 and PID:24597")
523
+ for port in availablePorts:
524
+ logger.debug(f"{port.device}")
525
+ logger.debug(f"\tProduct: {port.product}")
526
+ logger.debug(f"\tDesc: {port.description}")
527
+ logger.debug(f"\tSN: {port.serial_number}")
528
+ logger.debug(f"\tVID:{port.vid} PID:{port.pid}")
529
+ logger.debug(f"\tLocation: {port.location}")
530
+ logger.debug(f"\tInterface: {port.interface}")
531
+ if port.vid == 1027 and port.pid == 24597:
532
+ logger.debug("Using '{}' as GaugeHub COM port".format(port.description))
533
+ return port
534
+ return None
@@ -0,0 +1,78 @@
1
+ # This file was automatically generated using `generate_pyton_classes.py` script.
2
+ # Please don't make any direct modifications to this file as they will be overwritten and lost in the next cycle.
3
+ # Instead please make modification in the respective .yaml file.
4
+ # Generated on 2023-10-06 23:18
5
+
6
+
7
+ class dial_bus_config:
8
+ GAUGE_HUB_MAX_DEVICES = 100
9
+ GAUGE_HUB_UID_LENGTH = 12
10
+ GAUGE_I2C_ADDRESS_BOOTLOADER = 0x08
11
+ GAUGE_I2C_ADDRESS_PROVISIONING = 0x09
12
+ GAUGE_I2C_ADDRESS_GENERAL_CALL = 0x00
13
+ GAUGE_I2C_ADDRESS_FIRST = 0x0A
14
+ GAUGE_I2C_ADDRESS_LAST = (GAUGE_I2C_ADDRESS_FIRST+GAUGE_HUB_MAX_DEVICES)
15
+ GAUGE_COMMS_POS_CMD = 0
16
+ GAUGE_COMMS_POS_DATA_LEN = 1
17
+ GAUGE_COMMS_POS_DATA = GAUGE_COMMS_POS_DATA_LEN+2
18
+ GAUGE_COMMS_RX_SIZE = 1100
19
+ GAUGE_I2C_DEVICE_READY_RETRIES = 15
20
+ GAUGE_DIAL_PROTOCOL_VERSION = "v1"
21
+ GAUGE_I2C_JUMP_TO_BTL_KEY1 = 0x53
22
+ GAUGE_I2C_JUMP_TO_BTL_KEY2 = 0x4B
23
+
24
+ class dial_commands:
25
+ DG_HUB_CMD_FIRST = 0x00
26
+ DG_HUB_TO_DEV_GET_UID = 0x01
27
+ DG_HUB_TO_DEV_HUB_UID = 0x02
28
+ DG_HUB_TO_DEV_SET_ADDRESS = 0x03
29
+ DG_HUB_TO_DEV_SET_DAC = 0x04
30
+ DG_HUB_TO_DEV_GET_DAC = 0x05
31
+ DG_HUB_TO_DEV_CYCLE_GAUGE = 0x06
32
+ DG_HUB_TO_DEV_PWR_LED_ENABLE = 0x07
33
+ DG_HUB_TO_DEV_PWR_LED_DISABLE = 0x08
34
+ DG_HUB_TO_DEV_ACT_LED_ENABLE = 0x09
35
+ DG_HUB_TO_DEV_ACT_LED_DISABLE = 0x0A
36
+ DG_HUB_TO_DEV_VERIFY_UID = 0x0B
37
+ DG_HUB_TO_DEV_GET_PROPERTIES = 0x0C
38
+ DG_HUB_TO_DEV_SET_PERCENT = 0x0D
39
+ DG_HUB_TO_DEV_GET_PERCENT = 0x0E
40
+ DG_HUB_TO_DEV_SET_DIAL_MAX = 0x0F
41
+ DG_HUB_TO_DEV_SET_DIAL_HALF = 0x10
42
+ DG_HUB_TO_DEV_GET_VERSION = 0x11
43
+ DG_HUB_TO_DEV_SET_RGB_BACKLIGHT = 0x12
44
+ DG_HUB_TO_DEV_GET_RGB_BACKLIGHT = 0x13
45
+ DG_HUB_TO_DEV_DISPLAY_CLEAR = 0x14
46
+ DG_HUB_TO_DEV_DISPLAY_GOTO_XY = 0x15
47
+ DG_HUB_TO_DEV_DISPLAY_IMG_DATA = 0x16
48
+ DG_HUB_TO_DEV_DISPLAY_SHOW_IMG = 0x17
49
+ DG_HUB_TO_DEV_RX_BUFFER_SIZE = 0x18
50
+ DG_HUB_TO_DEV_KEEP_ALIVE = 0x19
51
+ DG_HUB_TO_DEV_SERVER_ABSENT = 0x1A
52
+ DG_HUB_TO_DEV_SERVER_PRESENT = 0x1B
53
+ DG_HUB_TO_DEV_DIAL_EASING_STEP = 0x1C
54
+ DG_HUB_TO_DEV_DIAL_EASING_PERIOD = 0x1D
55
+ DG_HUB_TO_DEV_BACKLIGHT_EASING_STEP = 0x1E
56
+ DG_HUB_TO_DEV_BACKLIGHT_EASING_PERIOD = 0x1F
57
+ DG_HUB_TO_DEV_GET_EASING_CONFIG = 0x20
58
+ DG_HUB_TO_DEV_BUILD_INFO = 0x21
59
+ DG_HUB_TO_DEV_FW_INFO = 0x22
60
+ DG_HUB_TO_DEV_HW_INFO = 0x23
61
+ DG_HUB_TO_DEV_PROTOCOL_INFO = 0x24
62
+ DG_HUB_TO_DEV_BTL_JUMP_TO_BOOTLOADER = 0xF0
63
+ DG_HUB_TO_DEV_BTL_BOOTLOADER_INFO = 0xF1
64
+ DG_HUB_TO_DEV_BTL_GET_CRC = 0xF2
65
+ DG_HUB_TO_DEV_BTL_ERASE_APP = 0xF3
66
+ DG_HUB_TO_DEV_FWUP_PACKAGE = 0xF4
67
+ DG_HUB_TO_DEV_FWUP_FINISHED = 0xF5
68
+ DG_HUB_TO_DEV_BTL_EXIT = 0xF6
69
+ DG_HUB_TO_DEV_BTL_RESTART_UPLOAD = 0xF7
70
+ DG_HUB_TO_DEV_BTL_READ_STATUS_CODE = 0xF8
71
+ DG_HUB_TO_DEV_RESET_CFG = 0xFA
72
+ DG_HUB_CMD_LAST = None
73
+
74
+ class device_types:
75
+ DG_DEVICE_TYPE_FIRST = 0x00
76
+ DG_DEVICE_TYPE_ANALOG_GAUGE = 0x01
77
+ DG_DEVICE_TYPE_EINK_GAUGE = 0x02
78
+ DG_DEVICE_TYPE_LAST = None