lecore 0.2.2__tar.gz → 0.2.3__tar.gz

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 (81) hide show
  1. {lecore-0.2.2/src/lecore.egg-info → lecore-0.2.3}/PKG-INFO +1 -1
  2. {lecore-0.2.2 → lecore-0.2.3}/pyproject.toml +1 -1
  3. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/LeBinDev.py +271 -269
  4. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/LeBin/SerialCom.py +3 -2
  5. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/LeBin/VisualLeBin.py +2 -1
  6. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Scaffold.py +9 -1
  7. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/VisualModbus/AppLogging.py +3 -0
  8. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/VisualModbus/HelpAbout.py +1 -7
  9. lecore-0.2.3/src/lecore/_version.py +7 -0
  10. {lecore-0.2.2 → lecore-0.2.3/src/lecore.egg-info}/PKG-INFO +1 -1
  11. {lecore-0.2.2 → lecore-0.2.3}/src/lecore.egg-info/SOURCES.txt +1 -0
  12. {lecore-0.2.2 → lecore-0.2.3}/LICENSE +0 -0
  13. {lecore-0.2.2 → lecore-0.2.3}/MANIFEST.in +0 -0
  14. {lecore-0.2.2 → lecore-0.2.3}/README.md +0 -0
  15. {lecore-0.2.2 → lecore-0.2.3}/setup.cfg +0 -0
  16. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/DeviceBase.py +0 -0
  17. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/ModbusDev.py +0 -0
  18. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/PhaseDet/ComSettings.json +0 -0
  19. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/PhaseDet/PhaseDet.py +0 -0
  20. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/PhaseDet/PhaseDetRegs.py +0 -0
  21. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/PhaseDet/PhaseDet_Modbus.json +0 -0
  22. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/PhaseDet/UpgradeSettings.json +0 -0
  23. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/PhaseDet/VisualSettings.json +0 -0
  24. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/PhaseDet/__init__.py +0 -0
  25. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/Rftb/ComSettings.json +0 -0
  26. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/Rftb/Rftb.py +0 -0
  27. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/Rftb/RftbLeBin.py +0 -0
  28. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/Rftb/UpgradeSettings.json +0 -0
  29. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/Rftb/VisualSettings.json +0 -0
  30. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/Rftb/__init__.py +0 -0
  31. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/Rftb/rftbRegs.py +0 -0
  32. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/Rftb/rftb_Modbus.json +0 -0
  33. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/Rftb/rftb_registers.json +0 -0
  34. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/RtdEmul/ComSettings.json +0 -0
  35. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/RtdEmul/RtdEmul.py +0 -0
  36. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/RtdEmul/RtdEmulRegs.py +0 -0
  37. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/RtdEmul/RtdEmul_Modbus.json +0 -0
  38. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/RtdEmul/UpgradeSettings.json +0 -0
  39. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/RtdEmul/VisualSettings.json +0 -0
  40. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/RtdEmul/__init__.py +0 -0
  41. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/Validation.py +0 -0
  42. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/VmsRtd/ComSettings.json +0 -0
  43. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/VmsRtd/UpgradeSettings.json +0 -0
  44. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/VmsRtd/VMS_RTDRegs.py +0 -0
  45. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/VmsRtd/VMS_RTD_Modbus.json +0 -0
  46. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/VmsRtd/VisualSettings.json +0 -0
  47. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/VmsRtd/VmsRtd.py +0 -0
  48. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/VmsRtd/__init__.py +0 -0
  49. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Devices/__init__.py +0 -0
  50. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Doc/ModbusTable.py +0 -0
  51. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Doc/__init__.py +0 -0
  52. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Fg320/Fg320.py +0 -0
  53. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Fg320/__init__.py +0 -0
  54. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/LeBin/RegisterMap.py +0 -0
  55. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/LeBin/UpgradeFirmware.py +0 -0
  56. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/LeBin/__init__.py +0 -0
  57. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/Looger.py +0 -0
  58. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/QUICKSTART.md +0 -0
  59. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/TestFrame/CoreTest.py +0 -0
  60. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/TestFrame/ModbusUnitTest.py +0 -0
  61. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/TestFrame/RtuClient.py +0 -0
  62. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/TestFrame/Runner.py +0 -0
  63. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/TestFrame/TestUtils.py +0 -0
  64. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/TestFrame/__init__.py +0 -0
  65. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/VisualModbus/Crc32.py +0 -0
  66. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/VisualModbus/MbClient.py +0 -0
  67. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/VisualModbus/MbUpgrade.py +0 -0
  68. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/VisualModbus/ReadWrite.py +0 -0
  69. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/VisualModbus/RegMap.py +0 -0
  70. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/VisualModbus/TextMbApp.py +0 -0
  71. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/VisualModbus/TextMbAppCss.tcss +0 -0
  72. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/VisualModbus/VisualMbApp.py +0 -0
  73. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/VisualModbus/VmSettings.py +0 -0
  74. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/VisualModbus/__init__.py +0 -0
  75. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/__init__.py +0 -0
  76. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/__main__.py +0 -0
  77. {lecore-0.2.2 → lecore-0.2.3}/src/lecore/py.typed +0 -0
  78. {lecore-0.2.2 → lecore-0.2.3}/src/lecore.egg-info/dependency_links.txt +0 -0
  79. {lecore-0.2.2 → lecore-0.2.3}/src/lecore.egg-info/entry_points.txt +0 -0
  80. {lecore-0.2.2 → lecore-0.2.3}/src/lecore.egg-info/requires.txt +0 -0
  81. {lecore-0.2.2 → lecore-0.2.3}/src/lecore.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lecore
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Logic Elements core utilities
5
5
  Author-email: Jan Bartovský <jan.bartovsky@logicelements.cz>
6
6
  License-Expression: MIT
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "lecore"
7
7
  description = "Logic Elements core utilities"
8
- version = "0.2.2"
8
+ version = "0.2.3"
9
9
  authors = [
10
10
  { name="Jan Bartovský", email="jan.bartovsky@logicelements.cz" },
11
11
  ]
@@ -1,269 +1,271 @@
1
- from __future__ import annotations
2
-
3
- import time
4
- from typing import Any, ClassVar, Dict, List, Optional, Union
5
-
6
- import serial
7
-
8
- from ..TestFrame.TestUtils import *
9
- from ..LeBin.SerialCom import SerialCom
10
- from ..LeBin.RegisterMap import RegisterMap
11
- from ..LeBin.VisualLeBin import VisualLeBin
12
- from ..LeBin.UpgradeFirmware import UpgradeFirmware
13
- from .DeviceBase import DeviceBase
14
- from .Validation import (PathLike, ComSettings, UsbId, load_json, check_lebin_reg_map, check_reg_value, check_file,
15
- check_comport, check_baud_rate, check_parity, check_stop_bits, check_timeout)
16
-
17
-
18
- class LeBinDev(DeviceBase):
19
- """
20
- Generic LeBin device using proprietary LE register map and protocol.
21
-
22
- A new device is declared by class attributes, the configuration files are looked up in the directory
23
- of the module declaring the class::
24
-
25
- class MyDevice(LeBinDev):
26
- reg_map = "my_registers.json" # required
27
- usb = UsbId(vid=1155, pid=22336, sn="MY") # optional, used by search_com()
28
- reg_names = MyDeviceRegs # optional, checked against the register map
29
-
30
- Without subclassing, use ``LeBinDev(reg_map="path/to/map.json", config_dir="path/to/configs")``.
31
- """
32
- #: USB identification used by search_com()
33
- usb: ClassVar[UsbId] = UsbId()
34
-
35
- def __init_subclass__(cls, **kwargs):
36
- super().__init_subclass__(**kwargs)
37
- if not isinstance(cls.usb, UsbId):
38
- raise TypeError(f"{cls.__name__}.usb must be UsbId, got {type(cls.usb).__name__}")
39
-
40
- def __init__(self, com: Optional[PathLike] = None, reg_map: Optional[PathLike] = None, *,
41
- config_dir: Optional[PathLike] = None):
42
- """
43
- Initialize LeBin device
44
-
45
- The respective parameters allow to redefine default settings. In most cases, it is not necessary.
46
- Relative paths are resolved against current working directory, then against config_dir.
47
- :param com: Communication settings file override
48
- :param reg_map: Register map definition file override
49
- :param config_dir: Directory with default configuration files override
50
- """
51
- self.com = SerialCom()
52
- self.regs = RegisterMap(self.com)
53
- self._path = ""
54
- self._upg = None
55
- self._vis = None
56
- self._def_com = self.com_settings
57
- self._def_regs = None
58
- self._com_file = ""
59
- self._reg_file = ""
60
- self._def_usb = {'vid': self.usb.vid, 'pid': self.usb.pid, 'sn': self.usb.sn}
61
- self._regs_by_name: Dict[str, dict] = {}
62
-
63
- if not self._init_paths(config_dir, reg_map, type(self).__init__ is not LeBinDev.__init__):
64
- return
65
-
66
- # Initialize all internal structures
67
- self._com(com)
68
- self._reg_map(reg_map)
69
- self._upgrade()
70
- self._visual()
71
-
72
- def open(self, comport: Optional[str] = None, baud_rate: Optional[int] = None, parity: Optional[str] = None,
73
- stop_bits: Optional[Union[int, float]] = None, timeout: Optional[float] = None,
74
- client: Optional[SerialCom] = None) -> None:
75
- """
76
- Open COM port with optional redefinition of default properties.
77
-
78
- Any from the following may redefine default COM port property defined in default json files. For all defaults,
79
- just open() may be used.
80
- :param comport: Comport name, e.g., 'COM5'
81
- :param baud_rate: Baud rate speed
82
- :param parity: Parity, either 'N', 'E', 'O'
83
- :param stop_bits: Number of stop bits, 1, 1.5 or 2
84
- :param timeout: Timeout of communication response in seconds
85
- :param client: Use previously created communication object (SerialCom), e.g., for sharing a COM port
86
- :return: None
87
- """
88
- # Validate all parameters before changing anything
89
- if client is not None:
90
- if not hasattr(client, 'request_response'):
91
- raise TypeError(f"client must be SerialCom compatible object, got {type(client).__name__}")
92
- if any(p is not None for p in (comport, baud_rate, parity, stop_bits, timeout)):
93
- raise ValueError("COM port parameters cannot be combined with external client")
94
- if comport is not None:
95
- check_comport(comport)
96
- if baud_rate is not None:
97
- check_baud_rate(baud_rate)
98
- if parity is not None:
99
- parity = check_parity(parity)
100
- if stop_bits is not None:
101
- check_stop_bits(stop_bits)
102
- if timeout is not None:
103
- check_timeout(timeout)
104
-
105
- # External communication object
106
- if client is not None:
107
- self.com = client
108
- self.regs.com = client
109
- if self._upg is not None:
110
- self._upg.com = client
111
- return
112
-
113
- # Com port settings
114
- self.close()
115
- if comport is not None:
116
- self.com.comport = comport
117
- if baud_rate is not None:
118
- self.com.baud = baud_rate
119
- if parity is not None:
120
- self.com.parity = parity
121
- if stop_bits is not None:
122
- self.com.stop = stop_bits
123
- if timeout is not None:
124
- self.com.timeout = timeout
125
- self.com.reopen()
126
-
127
- def search_com(self) -> List[str]:
128
- """
129
- Search USB devices by VID, PID, and serial number a return a list of viable COM ports
130
- :return: List of COM ports
131
- """
132
- usb = UsbId(**self._def_usb)
133
- return self.com.get_list_ports(usb.vid, usb.pid, usb.sn)
134
-
135
- def read(self, name: str) -> Any:
136
- """
137
- Read register
138
- :param name: Register name
139
- :return: Register value on read success
140
- :return: None on read error
141
- """
142
- self._get_reg(name)
143
- try:
144
- return self.regs.read_by_name(name)
145
- except serial.SerialException:
146
- return None
147
-
148
- def write(self, name: str, value: Any) -> Optional[bool]:
149
- """
150
- Write register. Value is checked against register format, Min/Max limits and enumeration.
151
- A list of values writes consecutive registers starting with the given one.
152
- :param name: Register name
153
- :param value: Register value or list of values
154
- :return: True on write success
155
- :return: None on write error
156
- """
157
- reg = self._get_reg(name)
158
- values = list(value) if isinstance(value, (list, tuple)) else [value]
159
- idx = self.regs.regs.index(reg)
160
- if idx + len(values) > len(self.regs.regs):
161
- raise ValueError(f"Writing {len(values)} values from register {name} exceeds the register map")
162
- for i, val in enumerate(values):
163
- r = self.regs.regs[idx + i]
164
- if str(r['Access']).startswith('RO'):
165
- raise ValueError(f"Register {r['Name']} is read-only")
166
- # LeBin FLOAT is a 32-bit float
167
- fmt = 'FLOAT32' if r['VarType'] == 'FLOAT' else r['VarType']
168
- check_reg_value(r, val, fmt, 8 * (1 << (r['Id'] & 7)), signed_ok=False)
169
- try:
170
- return self.regs.write_by_name(name, value)
171
- except serial.SerialException:
172
- return None
173
-
174
- def close(self) -> None:
175
- """
176
- Close communication
177
- """
178
- self.com.close()
179
-
180
- def visual(self, timeout: Optional[float] = None, height: int = 800, width: int = 600):
181
- """
182
- Run visual modbus graphic application
183
- :param timeout: Timeout of automatic application shutdown in seconds, None for no timeout
184
- :param height: Height of window
185
- :param width: Width of window
186
- :return: True on exit
187
- """
188
- if timeout is not None:
189
- check_timeout(timeout)
190
- for n, v in (('height', height), ('width', width)):
191
- if not isinstance(v, int) or v <= 0:
192
- raise ValueError(f"{n} must be a positive integer, got {v!r}")
193
- self.close()
194
- self._vis.draw(height, width)
195
- return self._vis.handle(timeout)
196
-
197
- def upgrade_firmware(self, file: PathLike, reboot_time: float = 5) -> bool:
198
- """
199
- Upgrade firmware in the device. This function also checks the version of firmware after upgrade.
200
- :param file: Firmware file to upgrade
201
- :param reboot_time: Time device takes to reboot
202
- :return: Status of update, True on success, False on failure
203
- """
204
- check_file(file, "Firmware")
205
- if not isinstance(reboot_time, (int, float)) or reboot_time < 0:
206
- raise ValueError(f"reboot_time must be a non-negative number, got {reboot_time!r}")
207
- self._get_reg("FIRM_REVISION")
208
- ver_orig = get_firmware_version(file, 512)
209
-
210
- # Run upgrade procedure
211
- self._upg.load_file(file)
212
- ret = self._upg.run()
213
- self.com.close()
214
- # Sleep reboot time
215
- time.sleep(reboot_time)
216
- self.com.reopen()
217
- # Check status and version after upgrade
218
- ver = self.regs.read_by_name("FIRM_REVISION")
219
-
220
- return ret and ver == ver_orig
221
-
222
- def send_file(self, file: PathLike) -> bool:
223
- """
224
- Send generic file to device
225
- :param file: File to send
226
- :return: Status of update, True on success, False on failure
227
- """
228
- check_file(file, "File to send")
229
- # Run upgrade procedure
230
- self._upg.load_file(file)
231
- # LeBin upgrade returns True on success (unlike Modbus return code 0)
232
- return bool(self._upg.run())
233
-
234
- def _visual(self) -> None:
235
- """
236
- Initialize VisualModbus graphic application
237
- """
238
- self._vis = VisualLeBin(self.regs)
239
-
240
- def _upgrade(self) -> None:
241
- """
242
- Initialize upgrade module
243
- """
244
- self._upg = UpgradeFirmware(self.com)
245
-
246
- def _com(self, settings: Optional[PathLike]) -> None:
247
- """
248
- Initialize communication module
249
- :param settings: Communication settings file
250
- """
251
- self._com_file = self._path_complete(settings, self._def_com, "Communication settings")
252
- ComSettings.from_json(self._com_file)
253
- self.com.open(self._com_file)
254
-
255
- def _reg_map(self, reg_map: Optional[PathLike]) -> None:
256
- """
257
- Initialize register map module
258
- :param reg_map: Register map definition json file
259
- """
260
- self._reg_file = self._path_complete(reg_map, self._def_regs, "Register map")
261
- check_lebin_reg_map(load_json(self._reg_file, "Register map"), self._reg_file)
262
- self.regs.load(self._reg_file)
263
- self._regs_by_name = {r['Name']: r for r in self.regs.regs}
264
- self._check_reg_names()
265
-
266
- def _reg_dict(self) -> Dict[str, dict]:
267
- if not self._regs_by_name:
268
- raise RuntimeError(f"{type(self).__name__}: register map is not loaded")
269
- return self._regs_by_name
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from typing import Any, ClassVar, Dict, List, Optional, Union
5
+
6
+ import serial
7
+
8
+ from ..TestFrame.TestUtils import *
9
+ from ..LeBin.SerialCom import SerialCom
10
+ from ..LeBin.RegisterMap import RegisterMap
11
+ from ..LeBin.VisualLeBin import VisualLeBin
12
+ from ..LeBin.UpgradeFirmware import UpgradeFirmware
13
+ from .DeviceBase import DeviceBase
14
+ from .Validation import (PathLike, ComSettings, UsbId, load_json, check_lebin_reg_map, check_reg_value, check_file,
15
+ check_comport, check_baud_rate, check_parity, check_stop_bits, check_timeout)
16
+
17
+
18
+ class LeBinDev(DeviceBase):
19
+ """
20
+ Generic LeBin device using proprietary LE register map and protocol.
21
+
22
+ A new device is declared by class attributes, the configuration files are looked up in the directory
23
+ of the module declaring the class::
24
+
25
+ class MyDevice(LeBinDev):
26
+ reg_map = "my_registers.json" # required
27
+ usb = UsbId(vid=1155, pid=22336, sn="MY") # optional, used by search_com()
28
+ reg_names = MyDeviceRegs # optional, checked against the register map
29
+
30
+ Without subclassing, use ``LeBinDev(reg_map="path/to/map.json", config_dir="path/to/configs")``.
31
+ """
32
+ #: USB identification used by search_com()
33
+ usb: ClassVar[UsbId] = UsbId()
34
+
35
+ def __init_subclass__(cls, **kwargs):
36
+ super().__init_subclass__(**kwargs)
37
+ if not isinstance(cls.usb, UsbId):
38
+ raise TypeError(f"{cls.__name__}.usb must be UsbId, got {type(cls.usb).__name__}")
39
+
40
+ def __init__(self, com: Optional[PathLike] = None, reg_map: Optional[PathLike] = None, *,
41
+ config_dir: Optional[PathLike] = None):
42
+ """
43
+ Initialize LeBin device
44
+
45
+ The respective parameters allow to redefine default settings. In most cases, it is not necessary.
46
+ Relative paths are resolved against current working directory, then against config_dir.
47
+ :param com: Communication settings file override
48
+ :param reg_map: Register map definition file override
49
+ :param config_dir: Directory with default configuration files override
50
+ """
51
+ self.com = SerialCom()
52
+ self.regs = RegisterMap(self.com)
53
+ self._path = ""
54
+ self._upg = None
55
+ self._vis = None
56
+ self._def_com = self.com_settings
57
+ self._def_regs = None
58
+ self._com_file = ""
59
+ self._reg_file = ""
60
+ self._def_usb = {'vid': self.usb.vid, 'pid': self.usb.pid, 'sn': self.usb.sn}
61
+ self._regs_by_name: Dict[str, dict] = {}
62
+
63
+ if not self._init_paths(config_dir, reg_map, type(self).__init__ is not LeBinDev.__init__):
64
+ return
65
+
66
+ # Initialize all internal structures
67
+ self._com(com)
68
+ self._reg_map(reg_map)
69
+ self._upgrade()
70
+ self._visual()
71
+
72
+ def open(self, comport: Optional[str] = None, baud_rate: Optional[int] = None, parity: Optional[str] = None,
73
+ stop_bits: Optional[Union[int, float]] = None, timeout: Optional[float] = None,
74
+ client: Optional[SerialCom] = None) -> None:
75
+ """
76
+ Open COM port with optional redefinition of default properties.
77
+
78
+ Any from the following may redefine default COM port property defined in default json files. For all defaults,
79
+ just open() may be used.
80
+ :param comport: Comport name, e.g., 'COM5'
81
+ :param baud_rate: Baud rate speed
82
+ :param parity: Parity, either 'N', 'E', 'O'
83
+ :param stop_bits: Number of stop bits, 1, 1.5 or 2
84
+ :param timeout: Timeout of communication response in seconds
85
+ :param client: Use previously created communication object (SerialCom), e.g., for sharing a COM port
86
+ :return: None
87
+ """
88
+ # Validate all parameters before changing anything
89
+ if client is not None:
90
+ if not hasattr(client, 'request_response'):
91
+ raise TypeError(f"client must be SerialCom compatible object, got {type(client).__name__}")
92
+ if any(p is not None for p in (comport, baud_rate, parity, stop_bits, timeout)):
93
+ raise ValueError("COM port parameters cannot be combined with external client")
94
+ if comport is not None:
95
+ check_comport(comport)
96
+ if baud_rate is not None:
97
+ check_baud_rate(baud_rate)
98
+ if parity is not None:
99
+ parity = check_parity(parity)
100
+ if stop_bits is not None:
101
+ check_stop_bits(stop_bits)
102
+ if timeout is not None:
103
+ check_timeout(timeout)
104
+
105
+ # External communication object
106
+ if client is not None:
107
+ self.com = client
108
+ self.regs.com = client
109
+ if self._upg is not None:
110
+ self._upg.com = client
111
+ return
112
+
113
+ # Com port settings
114
+ self.close()
115
+ if comport is not None:
116
+ self.com.comport = comport
117
+ if baud_rate is not None:
118
+ self.com.baud = baud_rate
119
+ if parity is not None:
120
+ self.com.parity = parity
121
+ if stop_bits is not None:
122
+ self.com.stop = stop_bits
123
+ if timeout is not None:
124
+ self.com.timeout = timeout
125
+ self.com.reopen()
126
+
127
+ def search_com(self) -> List[str]:
128
+ """
129
+ Search USB devices by VID, PID, and serial number a return a list of viable COM ports
130
+ :return: List of COM ports
131
+ """
132
+ usb = UsbId(**self._def_usb)
133
+ return self.com.get_list_ports(usb.vid, usb.pid, usb.sn)
134
+
135
+ def read(self, name: str) -> Any:
136
+ """
137
+ Read register
138
+ :param name: Register name
139
+ :return: Register value on read success
140
+ :return: None on read error
141
+ """
142
+ self._get_reg(name)
143
+ try:
144
+ return self.regs.read_by_name(name)
145
+ except serial.SerialException:
146
+ return None
147
+
148
+ def write(self, name: str, value: Any) -> Optional[bool]:
149
+ """
150
+ Write register. Value is checked against register format, Min/Max limits and enumeration.
151
+ A list of values writes consecutive registers starting with the given one.
152
+ :param name: Register name
153
+ :param value: Register value or list of values
154
+ :return: True on write success
155
+ :return: None on write error
156
+ """
157
+ reg = self._get_reg(name)
158
+ values = list(value) if isinstance(value, (list, tuple)) else [value]
159
+ idx = self.regs.regs.index(reg)
160
+ if idx + len(values) > len(self.regs.regs):
161
+ raise ValueError(f"Writing {len(values)} values from register {name} exceeds the register map")
162
+ for i, val in enumerate(values):
163
+ r = self.regs.regs[idx + i]
164
+ if str(r['Access']).startswith('RO'):
165
+ raise ValueError(f"Register {r['Name']} is read-only")
166
+ # LeBin FLOAT is a 32-bit float
167
+ fmt = 'FLOAT32' if r['VarType'] == 'FLOAT' else r['VarType']
168
+ check_reg_value(r, val, fmt, 8 * (1 << (r['Id'] & 7)), signed_ok=False)
169
+ try:
170
+ return self.regs.write_by_name(name, value)
171
+ except serial.SerialException:
172
+ return None
173
+
174
+ def close(self) -> None:
175
+ """
176
+ Close communication
177
+ """
178
+ self.com.close()
179
+
180
+ def visual(self, timeout: Optional[float] = None, height: int = 800, width: int = 600):
181
+ """
182
+ Run visual modbus graphic application
183
+ :param timeout: Timeout of automatic application shutdown in seconds, None for no timeout
184
+ :param height: Height of window
185
+ :param width: Width of window
186
+ :return: True on exit
187
+ """
188
+ if timeout is not None:
189
+ check_timeout(timeout)
190
+ for n, v in (('height', height), ('width', width)):
191
+ if not isinstance(v, int) or v <= 0:
192
+ raise ValueError(f"{n} must be a positive integer, got {v!r}")
193
+ self.close()
194
+ self._vis.draw(height, width)
195
+ return self._vis.handle(timeout)
196
+
197
+ def upgrade_firmware(self, file: PathLike, reboot_time: float = 5) -> bool:
198
+ """
199
+ Upgrade firmware in the device. This function also checks the version of firmware after upgrade.
200
+ :param file: Firmware file to upgrade
201
+ :param reboot_time: Time device takes to reboot
202
+ :return: Status of update, True on success, False on failure
203
+ """
204
+ check_file(file, "Firmware")
205
+ if not isinstance(reboot_time, (int, float)) or reboot_time < 0:
206
+ raise ValueError(f"reboot_time must be a non-negative number, got {reboot_time!r}")
207
+ self._get_reg("FIRM_REVISION")
208
+ ver_orig = get_firmware_version(file, 512)
209
+
210
+ # Run upgrade procedure
211
+ self._upg.load_file(file)
212
+ ret = self._upg.run()
213
+ self.com.close()
214
+ # Sleep reboot time
215
+ time.sleep(reboot_time)
216
+ self.com.reopen()
217
+ # Check status and version after upgrade
218
+ ver = self.regs.read_by_name("FIRM_REVISION")
219
+
220
+ return ret and ver == ver_orig
221
+
222
+ def send_file(self, file: PathLike) -> bool:
223
+ """
224
+ Send generic file to device
225
+ :param file: File to send
226
+ :return: Status of update, True on success, False on failure
227
+ """
228
+ check_file(file, "File to send")
229
+ # Run upgrade procedure
230
+ self._upg.load_file(file)
231
+ # LeBin upgrade returns True on success (unlike Modbus return code 0)
232
+ return bool(self._upg.run())
233
+
234
+ def _visual(self) -> None:
235
+ """
236
+ Initialize VisualModbus graphic application
237
+ """
238
+ self._vis = VisualLeBin(self.regs)
239
+
240
+ def _upgrade(self) -> None:
241
+ """
242
+ Initialize upgrade module
243
+ """
244
+ self._upg = UpgradeFirmware(self.com)
245
+
246
+ def _com(self, settings: Optional[PathLike]) -> None:
247
+ """
248
+ Initialize communication module
249
+ :param settings: Communication settings file
250
+ """
251
+ self._com_file = self._path_complete(settings, self._def_com, "Communication settings")
252
+ ComSettings.from_json(self._com_file)
253
+ # A device with USB identification is looked up by it, the com port in settings is only a fallback
254
+ ports = self.search_com() if self.usb.vid else []
255
+ self.com.open(self._com_file, ports[0] if ports else None)
256
+
257
+ def _reg_map(self, reg_map: Optional[PathLike]) -> None:
258
+ """
259
+ Initialize register map module
260
+ :param reg_map: Register map definition json file
261
+ """
262
+ self._reg_file = self._path_complete(reg_map, self._def_regs, "Register map")
263
+ check_lebin_reg_map(load_json(self._reg_file, "Register map"), self._reg_file)
264
+ self.regs.load(self._reg_file)
265
+ self._regs_by_name = {r['Name']: r for r in self.regs.regs}
266
+ self._check_reg_names()
267
+
268
+ def _reg_dict(self) -> Dict[str, dict]:
269
+ if not self._regs_by_name:
270
+ raise RuntimeError(f"{type(self).__name__}: register map is not loaded")
271
+ return self._regs_by_name
@@ -18,17 +18,18 @@ class SerialCom:
18
18
  self.timeout = 1.0
19
19
  self.comport = None
20
20
 
21
- def open(self, filename):
21
+ def open(self, filename, comport=None):
22
22
  """
23
23
  Open com port with given communication settings
24
24
  :param filename: Json settings of COM port
25
+ :param comport: Com port name overriding the one from settings, None to use settings
25
26
  :return: None
26
27
  """
27
28
  with io.open(filename, 'r', encoding='utf-8-sig') as f:
28
29
  s = json.load(f)
29
30
  self.settings = s
30
31
 
31
- self.comport = s['comport']
32
+ self.comport = s['comport'] if comport is None else comport
32
33
  self.baud = s['baud_rate']
33
34
  self.stop = s['stop_bits']
34
35
  self.parity = s['parity']
@@ -1,5 +1,6 @@
1
1
  import time
2
2
  from .RegisterMap import *
3
+ from .._version import __version__
3
4
  try:
4
5
  import FreeSimpleGUI as SG
5
6
  except ImportError:
@@ -60,7 +61,7 @@ class VisualLeBin:
60
61
  SG.Text("RSSI"), SG.Input('-1', size=(4, 1), key='T_RSSI')])
61
62
  layout.append([SG.Text('Status', key='T_STATUS', size=(width // 8, None))])
62
63
 
63
- self.window = SG.Window('LeBin visual application. Version: 1.1', layout, resizable=True, finalize=True)
64
+ self.window = SG.Window('LeBin visual application. Version: ' + __version__, layout, resizable=True, finalize=True)
64
65
  self.update()
65
66
  except AttributeError:
66
67
  raise ImportError("FreeSimpleGUI is probably missing")
@@ -355,7 +355,9 @@ def new_device(name: str, protocol: Optional[str] = None, directory: PathLike =
355
355
  f"reg_names of {cls_name} is not declared", UserWarning, stacklevel=2)
356
356
 
357
357
  # Configuration files, existing ones are kept
358
- configs = {com_file: dict(_COM_SETTINGS, comport=comport, baud_rate=baud_rate, parity=parity)}
358
+ # Com port, baud rate and parity apply to the Modbus device, LeBin of a dual device keeps the defaults
359
+ com = {} if dual and proto == 'lebin' else dict(comport=comport, baud_rate=baud_rate, parity=parity)
360
+ configs = {com_file: dict(_COM_SETTINGS, **com)}
359
361
  if proto == 'modbus':
360
362
  configs['UpgradeSettings.json'] = _UPGRADE_SETTINGS
361
363
  configs['VisualSettings.json'] = dict(_VISUAL_SETTINGS, slave_address=slave, reg_map=map_file)
@@ -375,6 +377,12 @@ def new_device(name: str, protocol: Optional[str] = None, directory: PathLike =
375
377
  init = os.path.join(directory, '__init__.py')
376
378
  if os.path.exists(init):
377
379
  result[init] = 'kept'
380
+ with open(init, encoding='utf-8') as f:
381
+ init_text = f.read()
382
+ absent = [d[0] for d in devices if not re.search(rf"import\s+(?:.*[\s,])?{re.escape(d[0])}(?![\w])", init_text)]
383
+ if absent:
384
+ warnings.warn(f"{init} is kept and does not export {', '.join(absent)}: 'from package import {absent[0]}' "
385
+ f"returns the submodule, add 'from .{absent[0]} import {absent[0]}'", stacklevel=2)
378
386
  else:
379
387
  text = "".join(f"from .{d[0]} import {d[0]}\n" for d in devices)
380
388
  if regs_found:
@@ -14,6 +14,7 @@ class LastRecordHandler(logging.Handler):
14
14
  Initialize logging handler and clear all records
15
15
  """
16
16
  logging.Handler.__init__(self)
17
+ self.formatter = logging.Formatter()
17
18
  self.records = []
18
19
 
19
20
  def emit(self, record):
@@ -22,6 +23,8 @@ class LastRecordHandler(logging.Handler):
22
23
  :param record: Current record
23
24
  :return: None
24
25
  """
26
+ # asctime is set only by Formatter.format(), which this handler does not call
27
+ record.asctime = self.formatter.formatTime(record)
25
28
  self.records.append(record)
26
29
  if len(self.records) > self.MAX_COUNT:
27
30
  self.records.pop(0)
@@ -7,13 +7,7 @@ except ImportError:
7
7
  except ImportError:
8
8
  import datetime as SG
9
9
 
10
- VERSION_MAJOR = 0
11
-
12
- VERSION_MINOR = 1
13
-
14
- VERSION_BUILD = 8
15
-
16
- VERSION = "{}.{}.{}".format(VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD)
10
+ from .._version import __version__ as VERSION
17
11
 
18
12
  RELEASE_NOTE_9 = "2020/01/31 - 0.1.8 - \n"\
19
13
  " - Fix podpory čtení a zápisu stringů \n" \
@@ -0,0 +1,7 @@
1
+ """Package version, taken from the installed distribution metadata (single source: pyproject.toml)."""
2
+ from importlib import metadata
3
+
4
+ try:
5
+ __version__ = metadata.version("lecore")
6
+ except metadata.PackageNotFoundError: # running from a source tree without installation
7
+ __version__ = "unknown"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lecore
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Logic Elements core utilities
5
5
  Author-email: Jan Bartovský <jan.bartovsky@logicelements.cz>
6
6
  License-Expression: MIT
@@ -7,6 +7,7 @@ src/lecore/QUICKSTART.md
7
7
  src/lecore/Scaffold.py
8
8
  src/lecore/__init__.py
9
9
  src/lecore/__main__.py
10
+ src/lecore/_version.py
10
11
  src/lecore/py.typed
11
12
  src/lecore.egg-info/PKG-INFO
12
13
  src/lecore.egg-info/SOURCES.txt
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes