lecore 0.2.5__tar.gz → 0.2.7__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.
- {lecore-0.2.5/src/lecore.egg-info → lecore-0.2.7}/PKG-INFO +2 -1
- {lecore-0.2.5 → lecore-0.2.7}/README.md +56 -55
- {lecore-0.2.5 → lecore-0.2.7}/pyproject.toml +1 -1
- lecore-0.2.7/src/lecore/FwBuild.py +247 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/__main__.py +89 -75
- {lecore-0.2.5 → lecore-0.2.7/src/lecore.egg-info}/PKG-INFO +2 -1
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore.egg-info/SOURCES.txt +1 -0
- {lecore-0.2.5 → lecore-0.2.7}/LICENSE +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/MANIFEST.in +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/setup.cfg +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/DeviceTests/Base.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/DeviceTests/LeBinTests.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/DeviceTests/ModbusTests.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/DeviceTests/Plugin.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/DeviceTests/__init__.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/DeviceBase.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/LeBinDev.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/ModbusDev.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/PhaseDet/ComSettings.json +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/PhaseDet/PhaseDet.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/PhaseDet/PhaseDetRegs.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/PhaseDet/PhaseDet_Modbus.json +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/PhaseDet/UpgradeSettings.json +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/PhaseDet/VisualSettings.json +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/PhaseDet/__init__.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/Rftb/ComSettings.json +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/Rftb/Rftb.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/Rftb/RftbLeBin.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/Rftb/UpgradeSettings.json +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/Rftb/VisualSettings.json +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/Rftb/__init__.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/Rftb/rftbRegs.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/Rftb/rftb_Modbus.json +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/Rftb/rftb_registers.json +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/RtdEmul/ComSettings.json +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/RtdEmul/RtdEmul.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/RtdEmul/RtdEmulRegs.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/RtdEmul/RtdEmul_Modbus.json +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/RtdEmul/UpgradeSettings.json +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/RtdEmul/VisualSettings.json +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/RtdEmul/__init__.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/Validation.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/VmsRtd/ComSettings.json +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/VmsRtd/UpgradeSettings.json +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/VmsRtd/VMS_RTDRegs.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/VmsRtd/VMS_RTD_Modbus.json +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/VmsRtd/VisualSettings.json +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/VmsRtd/VmsRtd.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/VmsRtd/__init__.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Devices/__init__.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Doc/ModbusTable.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Doc/__init__.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Fg320/Fg320.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Fg320/__init__.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/LeBin/RegisterMap.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/LeBin/SerialCom.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/LeBin/UpgradeFirmware.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/LeBin/VisualLeBin.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/LeBin/__init__.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Looger.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/QUICKSTART.md +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/Scaffold.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/TestFrame/CoreTest.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/TestFrame/ModbusUnitTest.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/TestFrame/RtuClient.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/TestFrame/Runner.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/TestFrame/TestUtils.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/TestFrame/__init__.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/VisualModbus/AppLogging.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/VisualModbus/Crc32.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/VisualModbus/HelpAbout.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/VisualModbus/MbClient.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/VisualModbus/MbUpgrade.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/VisualModbus/ReadWrite.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/VisualModbus/RegMap.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/VisualModbus/TextMbApp.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/VisualModbus/TextMbAppCss.tcss +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/VisualModbus/VisualMbApp.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/VisualModbus/VmSettings.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/VisualModbus/__init__.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/__init__.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/_version.py +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore/py.typed +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore.egg-info/dependency_links.txt +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore.egg-info/entry_points.txt +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/src/lecore.egg-info/requires.txt +0 -0
- {lecore-0.2.5 → lecore-0.2.7}/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.
|
|
3
|
+
Version: 0.2.7
|
|
4
4
|
Summary: Logic Elements core utilities
|
|
5
5
|
Author-email: Jan Bartovský <jan.bartovsky@logicelements.cz>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -81,6 +81,7 @@ New device class (in the directory with the register map): `python -m lecore new
|
|
|
81
81
|
- [Device API](doc/api.md) - Modbus and LeBin device: read, write, firmware upgrade
|
|
82
82
|
- [User interfaces](doc/user-interfaces.md) - graphical and terminal register browser
|
|
83
83
|
- [Devices](doc/devices.md) - RTD Emulator, VMS-1502 RTD Meter, Phase Detector, FG-320
|
|
84
|
+
- [Firmware post-build](doc/fwbuild.md) - FW information block and CRC (replaces BinBuild.exe)
|
|
84
85
|
- [Build and release](doc/build.md)
|
|
85
86
|
- [Looger](doc/looger.md)
|
|
86
87
|
|
|
@@ -1,55 +1,56 @@
|
|
|
1
|
-
# LE core package
|
|
2
|
-
|
|
3
|
-
[](https://github.com/LogicElements/lecore/actions/workflows/ci.yml)
|
|
4
|
-
[](https://pypi.org/project/lecore/)
|
|
5
|
-
[](https://pypi.org/project/lecore/)
|
|
6
|
-
[](LICENSE)
|
|
7
|
-
|
|
8
|
-
Core Python utilities for Logic Elements:
|
|
9
|
-
- Looger client
|
|
10
|
-
- Proprietary LeBin protocol
|
|
11
|
-
- Proprietary Modbus RTU register map handling - VisualModbus
|
|
12
|
-
- Test frame
|
|
13
|
-
- Logic Elements devices
|
|
14
|
-
|
|
15
|
-
## Install
|
|
16
|
-
|
|
17
|
-
Requires Python 3.9 or newer.
|
|
18
|
-
|
|
19
|
-
```
|
|
20
|
-
pip install lecore
|
|
21
|
-
pip install --upgrade lecore
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
Optional extras: `lecore[extra]` (graphical UI, Word documentation) and `lecore[tui]` (terminal UI).
|
|
25
|
-
|
|
26
|
-
## Usage
|
|
27
|
-
|
|
28
|
-
```python
|
|
29
|
-
import lecore.Devices.RtdEmul as RE
|
|
30
|
-
|
|
31
|
-
# Create RTD emulator object and open communication at COM2 (default is COM1)
|
|
32
|
-
rtd = RE.RtdEmul()
|
|
33
|
-
rtd.open(comport='COM2')
|
|
34
|
-
|
|
35
|
-
# Read serial number
|
|
36
|
-
sn = rtd.read(RE.Reg.FACT_SERIAL_NUMBER)
|
|
37
|
-
|
|
38
|
-
# Write emulated temperature at output 1
|
|
39
|
-
rtd.write(RE.Reg.EMUL_TEMPERATURE_1, 30.5)
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
New device class (in the directory with the register map): `python -m lecore new-device MyDevice`, quick guide: `python -m lecore guide`.
|
|
43
|
-
|
|
44
|
-
## Documentation
|
|
45
|
-
|
|
46
|
-
- [Creating a new device](doc/new-device.md)
|
|
47
|
-
- [Device API](doc/api.md) - Modbus and LeBin device: read, write, firmware upgrade
|
|
48
|
-
- [User interfaces](doc/user-interfaces.md) - graphical and terminal register browser
|
|
49
|
-
- [Devices](doc/devices.md) - RTD Emulator, VMS-1502 RTD Meter, Phase Detector, FG-320
|
|
50
|
-
- [
|
|
51
|
-
- [
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
1
|
+
# LE core package
|
|
2
|
+
|
|
3
|
+
[](https://github.com/LogicElements/lecore/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/lecore/)
|
|
5
|
+
[](https://pypi.org/project/lecore/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
Core Python utilities for Logic Elements:
|
|
9
|
+
- Looger client
|
|
10
|
+
- Proprietary LeBin protocol
|
|
11
|
+
- Proprietary Modbus RTU register map handling - VisualModbus
|
|
12
|
+
- Test frame
|
|
13
|
+
- Logic Elements devices
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
Requires Python 3.9 or newer.
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
pip install lecore
|
|
21
|
+
pip install --upgrade lecore
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Optional extras: `lecore[extra]` (graphical UI, Word documentation) and `lecore[tui]` (terminal UI).
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import lecore.Devices.RtdEmul as RE
|
|
30
|
+
|
|
31
|
+
# Create RTD emulator object and open communication at COM2 (default is COM1)
|
|
32
|
+
rtd = RE.RtdEmul()
|
|
33
|
+
rtd.open(comport='COM2')
|
|
34
|
+
|
|
35
|
+
# Read serial number
|
|
36
|
+
sn = rtd.read(RE.Reg.FACT_SERIAL_NUMBER)
|
|
37
|
+
|
|
38
|
+
# Write emulated temperature at output 1
|
|
39
|
+
rtd.write(RE.Reg.EMUL_TEMPERATURE_1, 30.5)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
New device class (in the directory with the register map): `python -m lecore new-device MyDevice`, quick guide: `python -m lecore guide`.
|
|
43
|
+
|
|
44
|
+
## Documentation
|
|
45
|
+
|
|
46
|
+
- [Creating a new device](doc/new-device.md)
|
|
47
|
+
- [Device API](doc/api.md) - Modbus and LeBin device: read, write, firmware upgrade
|
|
48
|
+
- [User interfaces](doc/user-interfaces.md) - graphical and terminal register browser
|
|
49
|
+
- [Devices](doc/devices.md) - RTD Emulator, VMS-1502 RTD Meter, Phase Detector, FG-320
|
|
50
|
+
- [Firmware post-build](doc/fwbuild.md) - FW information block and CRC (replaces BinBuild.exe)
|
|
51
|
+
- [Build and release](doc/build.md)
|
|
52
|
+
- [Looger](doc/looger.md)
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
MIT, see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Firmware post-build: stamps the FW information block (version, build date, CRC, size) into a binary.
|
|
3
|
+
|
|
4
|
+
Python port of the legacy ``BinBuild.exe``, usable on any platform: ``python -m lecore binbuild``.
|
|
5
|
+
The CRC is the one of the STM32 hardware CRC unit: polynom 0x04C11DB7, init 0xFFFFFFFF, 32-bit little-endian
|
|
6
|
+
words, no reflection and no final XOR. The same CRC is produced for all targets.
|
|
7
|
+
|
|
8
|
+
Targets 1-7 are verified byte by byte against BinBuild.exe (build of 2024-09). Target 8 is not implemented.
|
|
9
|
+
Intel HEX files are supported too (except target 1): the image from the lowest address is processed like a binary,
|
|
10
|
+
gaps are filled with 0x00 (like ``objcopy -O binary``) and the result is written as a contiguous HEX.
|
|
11
|
+
"""
|
|
12
|
+
import datetime
|
|
13
|
+
import struct
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Optional
|
|
17
|
+
|
|
18
|
+
_POLYNOM = 0x04C11DB7
|
|
19
|
+
_MASK = 0xFFFFFFFF
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _make_table():
|
|
23
|
+
table = []
|
|
24
|
+
for i in range(256):
|
|
25
|
+
value = i << 24
|
|
26
|
+
for _ in range(8):
|
|
27
|
+
value = ((value << 1) ^ _POLYNOM if value & 0x80000000 else value << 1) & _MASK
|
|
28
|
+
table.append(value)
|
|
29
|
+
return table
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
_TABLE = _make_table()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def crc32(data: bytes, crc: int = _MASK) -> int:
|
|
36
|
+
"""
|
|
37
|
+
CRC of the STM32 hardware CRC unit over 32-bit little-endian words
|
|
38
|
+
:param data: Data, its length must be a multiple of 4
|
|
39
|
+
:param crc: Initial value
|
|
40
|
+
:return: CRC
|
|
41
|
+
"""
|
|
42
|
+
if len(data) % 4:
|
|
43
|
+
raise ValueError("data length must be a multiple of 4")
|
|
44
|
+
table = _TABLE
|
|
45
|
+
for word in struct.unpack(f"<{len(data) // 4}I", data):
|
|
46
|
+
crc ^= word
|
|
47
|
+
crc = ((crc << 8) & _MASK) ^ table[crc >> 24]
|
|
48
|
+
crc = ((crc << 8) & _MASK) ^ table[crc >> 24]
|
|
49
|
+
crc = ((crc << 8) & _MASK) ^ table[crc >> 24]
|
|
50
|
+
crc = ((crc << 8) & _MASK) ^ table[crc >> 24]
|
|
51
|
+
return crc
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(frozen=True)
|
|
55
|
+
class Target:
|
|
56
|
+
"""
|
|
57
|
+
Layout of the FW information block
|
|
58
|
+
:param offset: Offset of the block in the binary, None = prepended to the file (LeCloud)
|
|
59
|
+
:param decimal: Version and build date are stored as decimal numbers (otherwise BCD hex)
|
|
60
|
+
:param clear_from: Bytes of the 32 B block from this index are taken as 0xFF in the CRC (None = nothing)
|
|
61
|
+
:param description: Description for the command line help
|
|
62
|
+
"""
|
|
63
|
+
offset: Optional[int]
|
|
64
|
+
decimal: bool
|
|
65
|
+
clear_from: Optional[int]
|
|
66
|
+
description: str
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
TARGETS = {
|
|
70
|
+
1: Target(None, False, None, "beginning of the file for LeCloud server (JLT products)"),
|
|
71
|
+
2: Target(512, False, None, "512 B offset in the FW (FG320)"),
|
|
72
|
+
3: Target(512, False, None, "512 B offset in the FW (VMS)"),
|
|
73
|
+
4: Target(192, False, None, "192 B offset in the FW (small alarm), device ID in the block"),
|
|
74
|
+
5: Target(1024, True, None, "1024 B offset in the FW, decimal information (SBD)"),
|
|
75
|
+
6: Target(512, True, 16, "512 B offset in the FW, decimal information (SIR1)"),
|
|
76
|
+
7: Target(512, True, 16, "512 B offset in the FW, decimal information (Fixture)"),
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
_HEX_MAX_SIZE = 16 * 1024 * 1024 # protection against huge gaps (e.g. option bytes far away from the FW)
|
|
80
|
+
_HEX_RECORD = 16
|
|
81
|
+
|
|
82
|
+
_LEGACY_OFFSET = 512 # target 1 checks the same free space as the targets with a 512 B offset
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def build(data: bytes, target: int, version: str, dev_id: str = "", date: Optional[datetime.date] = None) -> tuple:
|
|
86
|
+
"""
|
|
87
|
+
Stamp the FW information block into a binary
|
|
88
|
+
:param data: Binary content (padded with zeros to a multiple of 4)
|
|
89
|
+
:param target: Target number, see ``TARGETS``
|
|
90
|
+
:param version: FW version, hexadecimal (BCD) like in BinBuild
|
|
91
|
+
:param dev_id: Device ID (max 4 characters), used by target 4 only
|
|
92
|
+
:param date: Build date (default today)
|
|
93
|
+
:return: Tuple (new binary content, CRC)
|
|
94
|
+
"""
|
|
95
|
+
if target not in TARGETS:
|
|
96
|
+
raise ValueError(f"unknown target {target}, valid: {', '.join(map(str, TARGETS))}")
|
|
97
|
+
layout = TARGETS[target]
|
|
98
|
+
version_hex = int(version, 16)
|
|
99
|
+
try:
|
|
100
|
+
version_dec = int(version, 10)
|
|
101
|
+
except ValueError:
|
|
102
|
+
version_dec = 0 # BinBuild stores 0 when the version is not a decimal number
|
|
103
|
+
assembly = (date or datetime.date.today()).strftime("%Y%m%d")
|
|
104
|
+
if target == 4 and (len(dev_id) > 4 or not dev_id.isascii()):
|
|
105
|
+
raise ValueError(f"device ID '{dev_id}' must be at most 4 ASCII characters")
|
|
106
|
+
|
|
107
|
+
binary = bytearray(data)
|
|
108
|
+
binary += bytes(-len(binary) % 4)
|
|
109
|
+
size = len(binary)
|
|
110
|
+
|
|
111
|
+
offset = layout.offset if layout.offset is not None else _LEGACY_OFFSET
|
|
112
|
+
if target == 4:
|
|
113
|
+
free = [(offset, offset + 32, 0x00)]
|
|
114
|
+
else:
|
|
115
|
+
length = 16 if target >= 6 else 32
|
|
116
|
+
free = [(offset - 32, offset, 0x00), (offset, offset + length, 0xFF)]
|
|
117
|
+
for start, end, value in free:
|
|
118
|
+
if end > size or any(b != value for b in binary[start:end]):
|
|
119
|
+
raise ValueError("there must be free space for the FW information block "
|
|
120
|
+
f"(bytes {start}..{end - 1} must be 0x{value:02X})")
|
|
121
|
+
|
|
122
|
+
crc_data = bytearray(binary)
|
|
123
|
+
if layout.clear_from is not None:
|
|
124
|
+
crc_data[offset + layout.clear_from:offset + 32] = bytes([0xFF]) * (32 - layout.clear_from)
|
|
125
|
+
crc = crc32(bytes(crc_data))
|
|
126
|
+
|
|
127
|
+
if target == 4:
|
|
128
|
+
info = struct.pack("<I4sIII", version_hex, dev_id.encode("ascii"), size, crc, int(assembly, 16))
|
|
129
|
+
elif layout.decimal:
|
|
130
|
+
info = struct.pack("<IIII", version_dec, int(assembly), crc, size)
|
|
131
|
+
else:
|
|
132
|
+
info = struct.pack("<IIII", version_hex, int(assembly, 16), crc, size)
|
|
133
|
+
|
|
134
|
+
if layout.offset is None:
|
|
135
|
+
return info + bytes(binary), crc
|
|
136
|
+
stamped = bytes(binary)
|
|
137
|
+
return stamped[:offset] + info + stamped[offset + len(info):], crc
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def read_hex(text: str) -> tuple:
|
|
141
|
+
"""
|
|
142
|
+
Parse an Intel HEX file into a contiguous image, gaps are filled with 0x00
|
|
143
|
+
:param text: HEX file content
|
|
144
|
+
:return: Tuple (base address, image, start address record line or None)
|
|
145
|
+
"""
|
|
146
|
+
chunks = {}
|
|
147
|
+
upper = 0
|
|
148
|
+
start = None
|
|
149
|
+
for number, line in enumerate(text.splitlines(), 1):
|
|
150
|
+
line = line.strip()
|
|
151
|
+
if not line:
|
|
152
|
+
continue
|
|
153
|
+
try:
|
|
154
|
+
if line[0] != ":":
|
|
155
|
+
raise ValueError
|
|
156
|
+
record = bytes.fromhex(line[1:])
|
|
157
|
+
except ValueError:
|
|
158
|
+
raise ValueError(f"line {number}: not an Intel HEX record") from None
|
|
159
|
+
if len(record) < 5 or len(record) != record[0] + 5:
|
|
160
|
+
raise ValueError(f"line {number}: wrong record length")
|
|
161
|
+
if sum(record) & 0xFF:
|
|
162
|
+
raise ValueError(f"line {number}: wrong checksum")
|
|
163
|
+
rtype, payload = record[3], record[4:-1]
|
|
164
|
+
if rtype == 0x00:
|
|
165
|
+
address = upper + int.from_bytes(record[1:3], "big")
|
|
166
|
+
chunks[address] = chunks.get(address, b"") + payload
|
|
167
|
+
elif rtype == 0x01:
|
|
168
|
+
break
|
|
169
|
+
elif rtype == 0x02:
|
|
170
|
+
upper = int.from_bytes(payload, "big") << 4
|
|
171
|
+
elif rtype == 0x04:
|
|
172
|
+
upper = int.from_bytes(payload, "big") << 16
|
|
173
|
+
elif rtype in (0x03, 0x05):
|
|
174
|
+
start = line
|
|
175
|
+
else:
|
|
176
|
+
raise ValueError(f"line {number}: unknown record type {rtype:02X}")
|
|
177
|
+
if not chunks:
|
|
178
|
+
raise ValueError("no data in the HEX file")
|
|
179
|
+
base = min(chunks)
|
|
180
|
+
size = max(address + len(payload) for address, payload in chunks.items()) - base
|
|
181
|
+
if size > _HEX_MAX_SIZE:
|
|
182
|
+
raise ValueError(f"HEX image is too large ({size} B from 0x{base:08X}), check for data at distant addresses")
|
|
183
|
+
image = bytearray(size)
|
|
184
|
+
for address, payload in chunks.items():
|
|
185
|
+
image[address - base:address - base + len(payload)] = payload
|
|
186
|
+
return base, bytes(image), start
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _hex_record(rtype: int, address: int, payload: bytes) -> str:
|
|
190
|
+
record = bytes([len(payload)]) + address.to_bytes(2, "big") + bytes([rtype]) + payload
|
|
191
|
+
return ":" + (record + bytes([-sum(record) & 0xFF])).hex().upper()
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def write_hex(base: int, image: bytes, start: Optional[str] = None) -> str:
|
|
195
|
+
"""
|
|
196
|
+
Write a contiguous image as an Intel HEX file
|
|
197
|
+
:param base: Address of the image
|
|
198
|
+
:param image: Image content
|
|
199
|
+
:param start: Start address record line (from ``read_hex``)
|
|
200
|
+
:return: HEX file content
|
|
201
|
+
"""
|
|
202
|
+
lines = []
|
|
203
|
+
upper = None
|
|
204
|
+
address = base
|
|
205
|
+
end = base + len(image)
|
|
206
|
+
while address < end:
|
|
207
|
+
if address >> 16 != upper:
|
|
208
|
+
upper = address >> 16
|
|
209
|
+
lines.append(_hex_record(0x04, 0, upper.to_bytes(2, "big")))
|
|
210
|
+
length = min(_HEX_RECORD, end - address, 0x10000 - (address & 0xFFFF))
|
|
211
|
+
lines.append(_hex_record(0x00, address & 0xFFFF, image[address - base:address - base + length]))
|
|
212
|
+
address += length
|
|
213
|
+
if start:
|
|
214
|
+
lines.append(start)
|
|
215
|
+
lines.append(_hex_record(0x01, 0, b""))
|
|
216
|
+
return "\n".join(lines) + "\n"
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def build_file(target: int, path, version: str, suffix: str = "", out_dir=None,
|
|
220
|
+
date: Optional[datetime.date] = None) -> tuple:
|
|
221
|
+
"""
|
|
222
|
+
Stamp the FW information block into a binary or Intel HEX (``.hex``) file, the result is written next to it
|
|
223
|
+
(or to ``out_dir``) as ``<name>[-<suffix>]-<yyyymmdd>-<version>.bin`` (``.hex``)
|
|
224
|
+
:param target: Target number, see ``TARGETS``
|
|
225
|
+
:param path: Binary or Intel HEX file
|
|
226
|
+
:param version: FW version, hexadecimal (BCD)
|
|
227
|
+
:param suffix: Suffix of the output name, also used as the device ID of target 4
|
|
228
|
+
:param out_dir: Output directory (default directory of the input file)
|
|
229
|
+
:param date: Build date (default today)
|
|
230
|
+
:return: Tuple (output path, CRC)
|
|
231
|
+
"""
|
|
232
|
+
path = Path(path)
|
|
233
|
+
date = date or datetime.date.today()
|
|
234
|
+
is_hex = path.suffix.lower() == ".hex"
|
|
235
|
+
if is_hex:
|
|
236
|
+
if TARGETS.get(target) is not None and TARGETS[target].offset is None:
|
|
237
|
+
raise ValueError(f"target {target} prepends the block to the file, it is not supported for HEX files")
|
|
238
|
+
base, image, start = read_hex(path.read_text(encoding="ascii"))
|
|
239
|
+
result, crc = build(image, target, version, suffix, date)
|
|
240
|
+
content = write_hex(base, result, start).encode("ascii")
|
|
241
|
+
else:
|
|
242
|
+
content, crc = build(path.read_bytes(), target, version, suffix, date)
|
|
243
|
+
name = f"{path.stem}{'-' + suffix if suffix else ''}-{date:%Y%m%d}-{int(version, 16):X}"
|
|
244
|
+
name += ".hex" if is_hex else ".bin"
|
|
245
|
+
out = Path(out_dir) / name if out_dir else path.with_name(name)
|
|
246
|
+
out.write_bytes(content)
|
|
247
|
+
return out, crc
|
|
@@ -1,75 +1,89 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Command line jump-start for lecore, see ``python -m lecore --help``
|
|
3
|
-
"""
|
|
4
|
-
import argparse
|
|
5
|
-
import sys
|
|
6
|
-
import warnings
|
|
7
|
-
|
|
8
|
-
from .Scaffold import guide, new_device, generate_regs, generate_tests
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
proto
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
p.add_argument("--
|
|
31
|
-
p.add_argument("--
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
p.add_argument("--
|
|
35
|
-
p.add_argument("--
|
|
36
|
-
p.add_argument("--
|
|
37
|
-
p.add_argument("--
|
|
38
|
-
p.add_argument("--
|
|
39
|
-
p.add_argument("--
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
p.add_argument("--
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
p.
|
|
48
|
-
p.add_argument("
|
|
49
|
-
p.add_argument("
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
1
|
+
"""
|
|
2
|
+
Command line jump-start for lecore, see ``python -m lecore --help``
|
|
3
|
+
"""
|
|
4
|
+
import argparse
|
|
5
|
+
import sys
|
|
6
|
+
import warnings
|
|
7
|
+
|
|
8
|
+
from .Scaffold import guide, new_device, generate_regs, generate_tests
|
|
9
|
+
from .FwBuild import TARGETS, build_file
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _show_warning(message, category, filename, lineno, file=None, line=None):
|
|
13
|
+
print(f"warning: {message}", file=sys.stderr)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def main(argv=None) -> int:
|
|
17
|
+
warnings.showwarning = _show_warning
|
|
18
|
+
parser = argparse.ArgumentParser(prog="lecore", description="Logic Elements core utilities")
|
|
19
|
+
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
20
|
+
|
|
21
|
+
sub.add_parser("guide", help="print quick guide for creating devices with lecore")
|
|
22
|
+
|
|
23
|
+
p = sub.add_parser("new-device", help="create device class and missing configuration files next to the "
|
|
24
|
+
"register map")
|
|
25
|
+
p.add_argument("name", help="device class name, e.g., MyDevice")
|
|
26
|
+
proto = p.add_mutually_exclusive_group()
|
|
27
|
+
proto.add_argument("--modbus", dest="protocol", action="store_const", const="modbus",
|
|
28
|
+
help="Modbus device (default: detected from register map)")
|
|
29
|
+
proto.add_argument("--lebin", dest="protocol", action="store_const", const="lebin", help="LeBin device")
|
|
30
|
+
p.add_argument("--dir", default=".", help="directory with register map (default: current)")
|
|
31
|
+
p.add_argument("--reg-map", help="register map JSON in the directory (default: the single one found)")
|
|
32
|
+
p.add_argument("--template", action="store_true",
|
|
33
|
+
help="create minimal register map template and *Regs.py when no register map is present")
|
|
34
|
+
p.add_argument("--gen-regs", action="store_true", help="generate missing *Regs.py from the register map")
|
|
35
|
+
p.add_argument("--slave", type=int, default=1, help="default Modbus slave address")
|
|
36
|
+
p.add_argument("--comport", default="COM1", help="default COM port")
|
|
37
|
+
p.add_argument("--baud-rate", type=int, default=19200, help="default baud rate")
|
|
38
|
+
p.add_argument("--parity", default="E", choices=("N", "E", "O"), help="default parity")
|
|
39
|
+
p.add_argument("--usb", nargs=3, metavar=("VID", "PID", "SN"), help="LeBin USB identification for search_com()")
|
|
40
|
+
p.add_argument("--force", action="store_true", help="overwrite existing device module and configuration files")
|
|
41
|
+
|
|
42
|
+
p = sub.add_parser("new-tests", help="create missing communication test files (test_modbus.py, test_lebin.py) "
|
|
43
|
+
"for device classes in a directory")
|
|
44
|
+
p.add_argument("--dir", default=".", help="directory with device modules (default: current)")
|
|
45
|
+
p.add_argument("--force", action="store_true", help="overwrite existing test files")
|
|
46
|
+
|
|
47
|
+
p = sub.add_parser("regs", help="generate class with register name constants from a register map")
|
|
48
|
+
p.add_argument("reg_map", help="register map JSON (Modbus or LeBin)")
|
|
49
|
+
p.add_argument("--class", dest="class_name", default="Regs", help="generated class name")
|
|
50
|
+
p.add_argument("-o", "--output", help="output Python file (default: print)")
|
|
51
|
+
|
|
52
|
+
p = sub.add_parser("binbuild", help="stamp the FW information block (CRC, version, size) into a binary or HEX "
|
|
53
|
+
"(replaces BinBuild.exe)",
|
|
54
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
55
|
+
epilog="targets:\n" + "\n".join(f" {n} - {t.description}" for n, t in TARGETS.items()))
|
|
56
|
+
p.add_argument("target", type=int, choices=sorted(TARGETS), help="FW information block layout")
|
|
57
|
+
p.add_argument("file", help="binary or Intel HEX (.hex) file")
|
|
58
|
+
p.add_argument("version", help="FW version, hexadecimal (BCD)")
|
|
59
|
+
p.add_argument("--suffix", default="", help="suffix of the output name, device ID for target 4")
|
|
60
|
+
p.add_argument("--out-dir", help="output directory (default: directory of the binary)")
|
|
61
|
+
|
|
62
|
+
args = parser.parse_args(argv)
|
|
63
|
+
try:
|
|
64
|
+
if args.cmd == "guide":
|
|
65
|
+
print(guide())
|
|
66
|
+
elif args.cmd == "new-device":
|
|
67
|
+
usb = (int(args.usb[0], 0), int(args.usb[1], 0), args.usb[2]) if args.usb else None
|
|
68
|
+
result = new_device(args.name, args.protocol, args.dir, args.reg_map, args.template, args.gen_regs,
|
|
69
|
+
args.slave, args.comport, args.baud_rate, args.parity, usb, args.force)
|
|
70
|
+
for path, status in result.items():
|
|
71
|
+
print(f"{status:12}{path}")
|
|
72
|
+
elif args.cmd == "new-tests":
|
|
73
|
+
for path, status in generate_tests(args.dir, args.force).items():
|
|
74
|
+
print(f"{status:12}{path}")
|
|
75
|
+
elif args.cmd == "regs":
|
|
76
|
+
src = generate_regs(args.reg_map, args.class_name, args.output)
|
|
77
|
+
if args.output is None:
|
|
78
|
+
print(src, end="")
|
|
79
|
+
elif args.cmd == "binbuild":
|
|
80
|
+
out, crc = build_file(args.target, args.file, args.version, args.suffix, args.out_dir)
|
|
81
|
+
print(f"{out}\nNew File created with CRC value {crc:08X}")
|
|
82
|
+
except (ValueError, OSError) as e:
|
|
83
|
+
print(f"error: {e}", file=sys.stderr)
|
|
84
|
+
return 1
|
|
85
|
+
return 0
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
if __name__ == "__main__":
|
|
89
|
+
sys.exit(main())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lecore
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.7
|
|
4
4
|
Summary: Logic Elements core utilities
|
|
5
5
|
Author-email: Jan Bartovský <jan.bartovsky@logicelements.cz>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -81,6 +81,7 @@ New device class (in the directory with the register map): `python -m lecore new
|
|
|
81
81
|
- [Device API](doc/api.md) - Modbus and LeBin device: read, write, firmware upgrade
|
|
82
82
|
- [User interfaces](doc/user-interfaces.md) - graphical and terminal register browser
|
|
83
83
|
- [Devices](doc/devices.md) - RTD Emulator, VMS-1502 RTD Meter, Phase Detector, FG-320
|
|
84
|
+
- [Firmware post-build](doc/fwbuild.md) - FW information block and CRC (replaces BinBuild.exe)
|
|
84
85
|
- [Build and release](doc/build.md)
|
|
85
86
|
- [Looger](doc/looger.md)
|
|
86
87
|
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|