egauge-python 0.9.8__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 (44) hide show
  1. egauge/ctid/__init__.py +7 -0
  2. egauge/ctid/bit_stuffer.py +65 -0
  3. egauge/ctid/ctid.py +967 -0
  4. egauge/ctid/encoder.py +436 -0
  5. egauge/ctid/intel_hex_encoder.py +98 -0
  6. egauge/ctid/waveform.py +299 -0
  7. egauge/examples/data/test-ctid-decoder.raw +0 -0
  8. egauge/examples/test_capture.py +77 -0
  9. egauge/examples/test_common.py +26 -0
  10. egauge/examples/test_ctid.py +89 -0
  11. egauge/examples/test_ctid_decoder.py +93 -0
  12. egauge/examples/test_local.py +201 -0
  13. egauge/examples/test_register.py +104 -0
  14. egauge/loggers.py +72 -0
  15. egauge/pyside/__init__.py +0 -0
  16. egauge/pyside/ansi2html.py +112 -0
  17. egauge/pyside/terminal.py +295 -0
  18. egauge/webapi/__init__.py +34 -0
  19. egauge/webapi/auth.py +364 -0
  20. egauge/webapi/cloud/__init__.py +30 -0
  21. egauge/webapi/cloud/credentials.py +86 -0
  22. egauge/webapi/cloud/credentials_dialog.py +58 -0
  23. egauge/webapi/cloud/gui/credentials_dialog.py +100 -0
  24. egauge/webapi/cloud/serial_number.py +276 -0
  25. egauge/webapi/device/__init__.py +38 -0
  26. egauge/webapi/device/capture.py +453 -0
  27. egauge/webapi/device/ctid_info.py +553 -0
  28. egauge/webapi/device/device.py +349 -0
  29. egauge/webapi/device/local.py +268 -0
  30. egauge/webapi/device/physical_quantity.py +439 -0
  31. egauge/webapi/device/physical_units.py +473 -0
  32. egauge/webapi/device/register.py +338 -0
  33. egauge/webapi/device/register_row.py +145 -0
  34. egauge/webapi/device/register_type.py +851 -0
  35. egauge/webapi/device/slop.py +334 -0
  36. egauge/webapi/device/virtual_register.py +353 -0
  37. egauge/webapi/error.py +34 -0
  38. egauge/webapi/json_api.py +332 -0
  39. egauge_python-0.9.8.dist-info/METADATA +148 -0
  40. egauge_python-0.9.8.dist-info/RECORD +44 -0
  41. egauge_python-0.9.8.dist-info/WHEEL +5 -0
  42. egauge_python-0.9.8.dist-info/entry_points.txt +2 -0
  43. egauge_python-0.9.8.dist-info/licenses/LICENSE +22 -0
  44. egauge_python-0.9.8.dist-info/top_level.txt +1 -0
egauge/ctid/encoder.py ADDED
@@ -0,0 +1,436 @@
1
+ #
2
+ # Copyright (c) 2018-2022, 2024 eGauge Systems LLC
3
+ # 1644 Conestoga St, Suite 2
4
+ # Boulder, CO 80301
5
+ # voice: 720-545-9767
6
+ # email: davidm@egauge.net
7
+ #
8
+ # All rights reserved.
9
+ #
10
+ # This code is the property of eGauge Systems LLC and may not be
11
+ # copied, modified, or disclosed without any prior and written
12
+ # permission from eGauge Systems LLC.
13
+ #
14
+ # MIT License
15
+ #
16
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
17
+ # of this software and associated documentation files (the "Software"), to deal
18
+ # in the Software without restriction, including without limitation the rights
19
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20
+ # copies of the Software, and to permit persons to whom the Software is
21
+ # furnished to do so, subject to the following conditions:
22
+ #
23
+ # The above copyright notice and this permission notice shall be included in
24
+ # all copies or substantial portions of the Software.
25
+ #
26
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
32
+ # THE SOFTWARE.
33
+ #
34
+ import argparse
35
+ import sys
36
+
37
+ from intelhex import IntelHex
38
+
39
+ import egauge.ctid as CTid
40
+
41
+ CTid_table_addr = 0x3C0 # table goes in last 64 bytes
42
+
43
+
44
+ def is_abbrev(abbrev: str, full: str) -> bool:
45
+ if not abbrev:
46
+ return False
47
+ if len(abbrev) > len(full):
48
+ return False
49
+ return abbrev == full[0 : len(abbrev)]
50
+
51
+
52
+ def auto_int(x: str) -> int:
53
+ """Accept base-prefixed decimal."""
54
+ return int(x, 0)
55
+
56
+
57
+ def edge_mask(x: str) -> int:
58
+ try:
59
+ return int(x)
60
+ except ValueError:
61
+ pass
62
+ if is_abbrev(x, "rising"): # rising
63
+ return 0x1
64
+ if is_abbrev(x, "falling"):
65
+ return 0x2
66
+ if is_abbrev(x, "both"):
67
+ return 0x3
68
+ raise ValueError(
69
+ 'Edge mask must be one of "rising", "falling", or "both".'
70
+ )
71
+
72
+
73
+ def cal_table_entry(string: str) -> tuple[float, float, float]:
74
+ pair = string.split(":")
75
+ if len(pair) != 2:
76
+ raise argparse.ArgumentTypeError("missing `:'")
77
+ idx = float(pair[0])
78
+ if idx not in table.cal_table:
79
+ allowed = ", ".join(f"{k:g}" for k in table.cal_table)
80
+ raise argparse.ArgumentTypeError(
81
+ f"Invalid index {idx} (must be one of {allowed})."
82
+ )
83
+ val = pair[1].split("/")
84
+ if len(val) != 2:
85
+ raise argparse.ArgumentTypeError("missing `/'")
86
+ v_adj = float(val[0])
87
+ p_adj = float(val[1])
88
+ return (idx, v_adj, p_adj)
89
+
90
+
91
+ def hexdump(title: str, data: bytes):
92
+ print(title + ":", end="")
93
+ c = 0
94
+ for b in data:
95
+ if c % 8 == 0:
96
+ print(f"\n 0x{c:08x}:", end="")
97
+ print(f" 0x{b:02x}", end="")
98
+ c += 1
99
+ print("")
100
+
101
+
102
+ def main():
103
+ global table
104
+ table = CTid.Table()
105
+
106
+ parser = argparse.ArgumentParser(
107
+ description="Encode CTid parameters.",
108
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
109
+ )
110
+ parser.add_argument(
111
+ "program_template",
112
+ help="Filename of the program template.",
113
+ default=None,
114
+ )
115
+ parser.add_argument(
116
+ "-d", "--debug", type=int, default=0, help="Debug level"
117
+ )
118
+ parser.add_argument(
119
+ "-o",
120
+ "--output-file",
121
+ type=argparse.FileType("w"),
122
+ default=sys.stdout,
123
+ )
124
+ #
125
+ # Common information:
126
+ #
127
+ parser.add_argument(
128
+ "-S",
129
+ "--sensor-type",
130
+ default="AC",
131
+ choices=CTid.SENSOR_TYPE_NAME,
132
+ help="Sensor-type.",
133
+ )
134
+ parser.add_argument(
135
+ "-M",
136
+ "--manufacturer",
137
+ default="0",
138
+ help="Manufacturer name or ID.",
139
+ )
140
+ parser.add_argument(
141
+ "-m",
142
+ "--model",
143
+ default="",
144
+ help="Model name (up to 4 characters).",
145
+ )
146
+ parser.add_argument(
147
+ "-n",
148
+ "--serial-number",
149
+ type=auto_int,
150
+ default=0,
151
+ help="Serial number.",
152
+ )
153
+ parser.add_argument(
154
+ "-r",
155
+ "--r-source",
156
+ type=float,
157
+ default=0,
158
+ help="Source resistance in Ohms.",
159
+ )
160
+ parser.add_argument(
161
+ "-l",
162
+ "--r-load",
163
+ type=float,
164
+ default=0,
165
+ help="Rated load resistance in Ohms.",
166
+ )
167
+ parser.add_argument(
168
+ "-V",
169
+ "--table-version",
170
+ type=int,
171
+ default=None,
172
+ help="Version of the CTid table to generate.",
173
+ )
174
+ #
175
+ # CT Parameters:
176
+ #
177
+ parser.add_argument(
178
+ "-a",
179
+ "--calibration-entry",
180
+ type=cal_table_entry,
181
+ action="append",
182
+ help="Calibration-entry of the form X:VA/PA, where "
183
+ "X is the percentage of the rated current to which the "
184
+ "entry applies, VA is the voltage-adjustment in %% and "
185
+ "PA is the phase-adjustment in \u00b0.",
186
+ )
187
+ parser.add_argument(
188
+ "-c",
189
+ "--rated-current",
190
+ type=float,
191
+ default=100,
192
+ help="Rated current of CT in Ampère.",
193
+ )
194
+ parser.add_argument(
195
+ "-i",
196
+ "--manufacturer-info",
197
+ type=auto_int,
198
+ default=0,
199
+ help="Manufacturer-specific information.",
200
+ )
201
+ parser.add_argument(
202
+ "-p",
203
+ "--phase",
204
+ type=float,
205
+ default=0,
206
+ help="Phase at rated current in \u00b0.",
207
+ )
208
+ parser.add_argument(
209
+ "-s",
210
+ "--size",
211
+ type=float,
212
+ default=0,
213
+ help="Size of the sensor in millimeter.",
214
+ )
215
+ parser.add_argument(
216
+ "-t",
217
+ "--voltage-temp-coeff",
218
+ type=float,
219
+ default=0,
220
+ help="Voltage temperature coefficient in ppm/\u00b0C.",
221
+ )
222
+ parser.add_argument(
223
+ "-T",
224
+ "--phase-temp-coeff",
225
+ type=float,
226
+ default=0,
227
+ help="Phase temperature coefficient in m\u00b0/\u00b0C.",
228
+ )
229
+ parser.add_argument(
230
+ "-v",
231
+ "--rated-voltage",
232
+ type=float,
233
+ default=1 / 3.0,
234
+ help="Rated voltage of CT in Volt.",
235
+ )
236
+ parser.add_argument(
237
+ "-b",
238
+ "--bias-voltage",
239
+ type=float,
240
+ default=0,
241
+ help="Output voltage when no current is flowing through the CD.",
242
+ )
243
+ #
244
+ # Linear Parameters:
245
+ #
246
+ parser.add_argument("--scale", type=float, default=None, help="Scale.")
247
+ parser.add_argument("--offset", type=float, default=None, help="Offset.")
248
+ parser.add_argument(
249
+ "--delay", type=float, default=None, help="Signal delay in μs."
250
+ )
251
+ parser.add_argument(
252
+ "--unit",
253
+ type=int,
254
+ default=0,
255
+ help=f"Physical unit code (0..{len(CTid.SENSOR_UNITS) - 1}).",
256
+ )
257
+ #
258
+ # NTC Temperature Parameters:
259
+ #
260
+ parser.add_argument(
261
+ "--ntc-a", type=float, default=None, help="NTC A coefficient."
262
+ )
263
+ parser.add_argument(
264
+ "--ntc-b", type=float, default=None, help="NTC B coefficient."
265
+ )
266
+ parser.add_argument(
267
+ "--ntc-c", type=float, default=None, help="NTC C coefficient."
268
+ )
269
+ parser.add_argument(
270
+ "--ntc-m", type=float, default=None, help="NTC parameter M."
271
+ )
272
+ parser.add_argument(
273
+ "--ntc-n", type=float, default=None, help="NTC parameter N."
274
+ )
275
+ parser.add_argument(
276
+ "--ntc-k", type=float, default=None, help="NTC parameter K."
277
+ )
278
+ #
279
+ # Pulse Temperature Parameters:
280
+ #
281
+ parser.add_argument(
282
+ "--threshold",
283
+ type=float,
284
+ default=None,
285
+ help="Pulse threshold in Volts.",
286
+ )
287
+ parser.add_argument(
288
+ "--hysteresis",
289
+ type=float,
290
+ default=None,
291
+ help="Hysteresis for threhold in Volts.",
292
+ )
293
+ parser.add_argument(
294
+ "--debounce-time",
295
+ type=float,
296
+ default=None,
297
+ help="Debounce time for pulse in milli-seconds.",
298
+ )
299
+ parser.add_argument(
300
+ "--edge-mask",
301
+ type=edge_mask,
302
+ default=None,
303
+ help="Edge-mask. May be an integer or one of "
304
+ '"rising", "falling", or "both".',
305
+ )
306
+
307
+ args = parser.parse_args()
308
+
309
+ table_version = args.table_version
310
+
311
+ mfg_name = args.manufacturer
312
+ try:
313
+ mfg_id = CTid.get_mfg_id_for_name(mfg_name)
314
+ except CTid.Error as e:
315
+ print(
316
+ f'{parser.prog}: "{mfg_name}" is invalid: {e}',
317
+ file=sys.stderr,
318
+ )
319
+ sys.exit(1)
320
+
321
+ type_name = args.sensor_type
322
+ try:
323
+ sensor_type = CTid.get_sensor_type_id(type_name)
324
+ except CTid.Error as e:
325
+ print(
326
+ f'{parser.prog}: "{type_name}" is invalid: {e}',
327
+ file=sys.stderr,
328
+ )
329
+ sys.exit(1)
330
+
331
+ table.mfg_id = mfg_id
332
+ table.model = args.model
333
+ table.size = args.size
334
+ table.serial_number = args.serial_number
335
+ table.sensor_type = sensor_type
336
+ table.r_source = args.r_source
337
+ table.r_load = args.r_load
338
+ # CT parameters:
339
+ table.rated_current = args.rated_current
340
+ table.voltage_at_rated_current = args.rated_voltage
341
+ table.bias_voltage = args.bias_voltage
342
+ table.phase_at_rated_current = args.phase
343
+ table.voltage_temp_coeff = args.voltage_temp_coeff
344
+ table.phase_temp_coeff = args.phase_temp_coeff
345
+ if args.calibration_entry is not None:
346
+ for idx, v_adj, p_adj in args.calibration_entry:
347
+ table.cal_table[idx][0] = v_adj
348
+ table.cal_table[idx][1] = p_adj
349
+
350
+ # Voltage and temperature parameters:
351
+
352
+ if args.scale is not None:
353
+ table.scale = args.scale
354
+ if args.offset is not None:
355
+ table.offset = args.offset
356
+ if args.delay is not None:
357
+ table.delay = args.delay
358
+ if args.unit is not None:
359
+ table.sensor_unit = args.unit
360
+
361
+ # NTC parameters:
362
+
363
+ if args.ntc_a is not None:
364
+ table.ntc_a = args.ntc_a
365
+ if args.ntc_b is not None:
366
+ table.ntc_b = args.ntc_b
367
+ if args.ntc_c is not None:
368
+ table.ntc_c = args.ntc_c
369
+ if args.ntc_m is not None:
370
+ table.ntc_m = args.ntc_m
371
+ if args.ntc_n is not None:
372
+ table.ntc_n = args.ntc_n
373
+ if args.ntc_k is not None:
374
+ table.ntc_k = args.ntc_k
375
+
376
+ # Pulse parameters:
377
+
378
+ if args.threshold is not None:
379
+ table.threshold = args.threshold
380
+ if args.hysteresis is not None:
381
+ table.hysteresis = args.hysteresis
382
+ if args.debounce_time is not None:
383
+ table.debounce_time = args.debounce_time
384
+ if args.edge_mask is not None:
385
+ table.edge_mask = args.edge_mask
386
+
387
+ table.mfg_info = int(args.manufacturer_info)
388
+
389
+ if args.debug > 0:
390
+ print("CTid params:\n\t", table)
391
+
392
+ exception = None
393
+ try:
394
+ if table_version is not None:
395
+ table_data = table.encode(table_version)
396
+ else:
397
+ table_data = table.encode()
398
+ except CTid.Error as e:
399
+ table_data = None
400
+ exception = e
401
+ if table_version is None and CTid.CTID_VERSION < 6:
402
+ try:
403
+ table_data = table.encode(version=5)
404
+ print(f"{parser.prog}: using v5 encoding")
405
+ exception = None
406
+ except CTid.Error as e:
407
+ exception = e
408
+ if table_data is None:
409
+ print(f"{parser.prog}: {exception}", file=sys.stderr)
410
+ sys.exit(1)
411
+
412
+ if args.debug > 0:
413
+ hexdump("table", bytes((CTid.START_SYM,)) + table_data)
414
+
415
+ bitstream = CTid.bitstuff(table_data)
416
+
417
+ if args.debug > 0:
418
+ hexdump("bit stream", bitstream)
419
+
420
+ ihex = IntelHex(args.program_template)
421
+
422
+ max_addr = max(ihex.todict().keys())
423
+ if max_addr > CTid_table_addr:
424
+ print(
425
+ f"{parser.prog}: max address in program file "
426
+ f"0x{max_addr:x} is >= table address 0x{CTid_table_addr:x}"
427
+ )
428
+ sys.exit(1)
429
+
430
+ addr = CTid_table_addr
431
+ ihex[addr] = len(bitstream)
432
+ addr += 1
433
+ for i, byte in enumerate(bitstream):
434
+ ihex[addr + i] = byte
435
+
436
+ ihex.write_hex_file(args.output_file)
@@ -0,0 +1,98 @@
1
+ #
2
+ # Copyright (c) 2024 eGauge Systems LLC
3
+ # 1644 Conestoga St, Suite 2
4
+ # Boulder, CO 80301
5
+ # voice: 720-545-9767
6
+ # email: davidm@egauge.net
7
+ #
8
+ # All rights reserved.
9
+ #
10
+ # This code is the property of eGauge Systems LLC and may not be
11
+ # copied, modified, or disclosed without any prior and written
12
+ # permission from eGauge Systems LLC.
13
+ #
14
+ # MIT License
15
+ #
16
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
17
+ # of this software and associated documentation files (the "Software"), to deal
18
+ # in the Software without restriction, including without limitation the rights
19
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20
+ # copies of the Software, and to permit persons to whom the Software is
21
+ # furnished to do so, subject to the following conditions:
22
+ #
23
+ # The above copyright notice and this permission notice shall be included in
24
+ # all copies or substantial portions of the Software.
25
+ #
26
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
32
+ # THE SOFTWARE.
33
+ #
34
+ import typing
35
+ from pathlib import Path
36
+
37
+ from intelhex import IntelHex
38
+
39
+ from . import ctid
40
+
41
+ # the default (ATtiny10) address at which the CTid table is stored:
42
+ CTID_TABLE_ADDR = 0x3C0
43
+
44
+
45
+ def intel_hex_encode(
46
+ program_image: Path | str | typing.IO | dict | IntelHex,
47
+ table: ctid.Table,
48
+ table_addr: int = CTID_TABLE_ADDR,
49
+ ctid_version: int = ctid.CTID_VERSION,
50
+ ) -> bytes:
51
+ """Encode a program image and a CTid table into an Intel hex
52
+ formatted file and return it as a byte stream.
53
+
54
+ This raises ctid.Error if there is a problem.
55
+
56
+ Required arguments:
57
+
58
+ program_image -- The program image to encode the CTid table into.
59
+
60
+ table -- The CTid table to encode.
61
+
62
+ Keyword arguments:
63
+
64
+ table_addr -- The base address at which to store the CTid table.
65
+ It is an error if the program_image already has content at the
66
+ addresses to be occupied by the CTid table (default 0x3c0).
67
+
68
+ ctid_version -- The CTid specification version to use when encoding
69
+ the table (default ctid.CTID_VERSION).
70
+
71
+ """
72
+ bitstream = ctid.bitstuff(table.encode(ctid_version))
73
+ bitstream_len = len(bitstream)
74
+
75
+ if bitstream_len > 255:
76
+ raise ctid.Error("CTid table too large", bitstream_len, 255)
77
+
78
+ if isinstance(program_image, Path):
79
+ program_image = str(program_image)
80
+
81
+ ihex = IntelHex(program_image)
82
+
83
+ max_addr = ihex.maxaddr()
84
+ if max_addr is None:
85
+ raise ctid.Error("program image is empty")
86
+
87
+ if max_addr >= table_addr:
88
+ raise ctid.Error(
89
+ "CTid table overlaps program image", max_addr, table_addr
90
+ )
91
+
92
+ # the byte at table_addr stores the bitstream length:
93
+ ihex[table_addr] = bitstream_len
94
+
95
+ for i, byte in enumerate(bitstream):
96
+ ihex[table_addr + 1 + i] = byte
97
+
98
+ return ihex.tobinstr()