nextion-tools 0.1.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.
- nextion_tools/__init__.py +14 -0
- nextion_tools/crc.py +26 -0
- nextion_tools/emu/__init__.py +16 -0
- nextion_tools/emu/__main__.py +55 -0
- nextion_tools/emu/clock.py +23 -0
- nextion_tools/emu/devices.py +29 -0
- nextion_tools/emu/display.py +1163 -0
- nextion_tools/emu/flow.py +199 -0
- nextion_tools/emu/model.py +94 -0
- nextion_tools/emu/protocol.py +150 -0
- nextion_tools/emu/script.py +690 -0
- nextion_tools/emu/transport.py +226 -0
- nextion_tools/emu/upload.py +174 -0
- nextion_tools/emu/viewer.py +295 -0
- nextion_tools/emu/waiting.py +25 -0
- nextion_tools/fonts.py +150 -0
- nextion_tools/hmi.py +467 -0
- nextion_tools/images.py +22 -0
- nextion_tools/render.py +474 -0
- nextion_tools/tft.py +91 -0
- nextion_tools-0.1.0.dist-info/METADATA +258 -0
- nextion_tools-0.1.0.dist-info/RECORD +25 -0
- nextion_tools-0.1.0.dist-info/WHEEL +5 -0
- nextion_tools-0.1.0.dist-info/licenses/LICENSE +21 -0
- nextion_tools-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Tools for Nextion HMI displays: the Editor's project files and the display itself.
|
|
2
|
+
|
|
3
|
+
- `nextion_tools.hmi`: .HMI projects: pages, components, ids, geometry, event code, Program.s;
|
|
4
|
+
numeric edits and re-stamping
|
|
5
|
+
- `nextion_tools.fonts`: .zi fonts: glyph bitmaps and metrics (numpy)
|
|
6
|
+
- `nextion_tools.images`: pictures as RGB arrays, RGB565 (numpy, Pillow)
|
|
7
|
+
- `nextion_tools.tft`: a compiled .tft's picture table, checksums and orientation
|
|
8
|
+
- `nextion_tools.crc`: the CRC behind TFT checksums and HMI stamps
|
|
9
|
+
- `nextion_tools.render`: draw a page as the display shows it (numpy, Pillow)
|
|
10
|
+
- `nextion_tools.emu`: an emulated display, driven by an HMI, for testing MCU firmware, with a
|
|
11
|
+
browser viewer (`nextion_tools.emu.viewer`)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
__version__ = "0.1.0"
|
nextion_tools/crc.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""The CRC behind TFT checksums and HMI page stamps.
|
|
2
|
+
|
|
3
|
+
Both are CRC-32/MPEG-2 (poly 0x04C11DB7, MSB first, init 0xFFFFFFFF, no final XOR) over the
|
|
4
|
+
data with each byte widened to a big-endian 32-bit word, which is how an STM32's CRC unit
|
|
5
|
+
checksums a byte stream. See https://github.com/UNUF/TFTTool for the TFT side.
|
|
6
|
+
"""
|
|
7
|
+
import zlib
|
|
8
|
+
|
|
9
|
+
_REVERSED = bytes(int(f'{i:08b}'[::-1], 2) for i in range(256))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def crc32_mpeg2(data):
|
|
13
|
+
"""CRC-32/MPEG-2. zlib computes the bit-reflected CRC-32 with the same polynomial, so this is
|
|
14
|
+
zlib's CRC of the bit-reversed input, bit-reversed, without zlib's final XOR."""
|
|
15
|
+
return int(f'{zlib.crc32(data.translate(_REVERSED)) ^ 0xFFFFFFFF:032b}'[::-1], 2)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def widen(data):
|
|
19
|
+
"""Each byte as a big-endian 32-bit word."""
|
|
20
|
+
words = bytearray(4 * len(data))
|
|
21
|
+
words[3::4] = data
|
|
22
|
+
return bytes(words)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def nextion(data):
|
|
26
|
+
return crc32_mpeg2(widen(data))
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""An emulated Nextion display, for testing the firmware that drives one. See display.py."""
|
|
2
|
+
|
|
3
|
+
from .clock import ManualClock
|
|
4
|
+
from .devices import NX4832T035, Device
|
|
5
|
+
from .display import COMOK, REDRAW_S, Display
|
|
6
|
+
from .flow import FlowModel
|
|
7
|
+
from .model import Screen
|
|
8
|
+
from .script import ScriptError, ScriptSyntaxError, UnsupportedCode
|
|
9
|
+
from .transport import NotConnected, Pty, Serial, SocketTransport, TcpServer, Transport
|
|
10
|
+
from .upload import Upload
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"COMOK", "REDRAW_S", "Device", "Display", "NX4832T035", "FlowModel", "ManualClock", "NotConnected", "Screen", "ScriptError",
|
|
14
|
+
"ScriptSyntaxError", "UnsupportedCode", "Pty", "Serial", "SocketTransport",
|
|
15
|
+
"TcpServer", "Transport", "Upload",
|
|
16
|
+
]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Run an emulated display and log what it is sent.
|
|
2
|
+
|
|
3
|
+
python -m nextion_tools.emu --tcp 4000 # listen; e.g. qemu ... -serial tcp:127.0.0.1:4000
|
|
4
|
+
python -m nextion_tools.emu --pty # prints the pty to point a program at
|
|
5
|
+
python -m nextion_tools.emu --serial /dev/ttyUSB0
|
|
6
|
+
python -m nextion_tools.emu --tcp 4000 --hmi gui.HMI # the project's pages and code
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import logging
|
|
11
|
+
import time
|
|
12
|
+
|
|
13
|
+
from .display import Display
|
|
14
|
+
from ..hmi import Project
|
|
15
|
+
from .protocol import TERMINATOR
|
|
16
|
+
from .transport import Pty, Serial, TcpServer
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main(argv=None) -> None:
|
|
20
|
+
parser = argparse.ArgumentParser(prog="python -m nextion_tools.emu", description=__doc__.split("\n")[0])
|
|
21
|
+
where = parser.add_mutually_exclusive_group(required=True)
|
|
22
|
+
where.add_argument("--tcp", type=int, metavar="PORT", help="listen on 127.0.0.1:PORT (0 picks one)")
|
|
23
|
+
where.add_argument("--pty", action="store_true", help="create a pseudo-terminal")
|
|
24
|
+
where.add_argument("--serial", metavar="DEVICE", help="use a serial port (needs pyserial)")
|
|
25
|
+
parser.add_argument("--baud", type=int, default=115200)
|
|
26
|
+
parser.add_argument("--flow", action="store_true", help="model the input buffer and stalls")
|
|
27
|
+
parser.add_argument("--banner", default="", help="a frame the TFT prints when it starts, e.g. NSPM")
|
|
28
|
+
parser.add_argument("--hmi", help="the project's .HMI: its pages, ids and code")
|
|
29
|
+
args = parser.parse_args(argv)
|
|
30
|
+
|
|
31
|
+
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(name)s %(message)s")
|
|
32
|
+
if args.tcp is not None:
|
|
33
|
+
transport = TcpServer(port=args.tcp)
|
|
34
|
+
print("listening on %s:%d" % (transport.host, transport.port), flush=True)
|
|
35
|
+
elif args.pty:
|
|
36
|
+
transport = Pty()
|
|
37
|
+
print("pty: %s" % transport.path, flush=True)
|
|
38
|
+
else:
|
|
39
|
+
transport = Serial(args.serial, args.baud)
|
|
40
|
+
banner = args.banner.encode() + TERMINATOR if args.banner else b""
|
|
41
|
+
display = Display(transport, flow_model=args.flow, baud=args.baud, banner=banner,
|
|
42
|
+
hmi=Project.load(args.hmi) if args.hmi else None)
|
|
43
|
+
try:
|
|
44
|
+
# Power on (the banner, Program.s) once the MCU is there to hear it.
|
|
45
|
+
while not display.connected:
|
|
46
|
+
time.sleep(0.1)
|
|
47
|
+
display.boot()
|
|
48
|
+
while True:
|
|
49
|
+
time.sleep(1)
|
|
50
|
+
except KeyboardInterrupt:
|
|
51
|
+
display.close()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
if __name__ == "__main__":
|
|
55
|
+
main()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""A clock a test moves by hand, for timers that must fire exactly when the test says.
|
|
2
|
+
|
|
3
|
+
clock = ManualClock()
|
|
4
|
+
display = Display(..., hmi=hmi, clock=clock)
|
|
5
|
+
clock.advance(0.05)
|
|
6
|
+
display.tick() # runs whatever is now due
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import threading
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ManualClock:
|
|
13
|
+
def __init__(self, start: float = 1000.0):
|
|
14
|
+
self._now = start
|
|
15
|
+
self._lock = threading.Lock()
|
|
16
|
+
|
|
17
|
+
def __call__(self) -> float:
|
|
18
|
+
with self._lock:
|
|
19
|
+
return self._now
|
|
20
|
+
|
|
21
|
+
def advance(self, seconds: float) -> None:
|
|
22
|
+
with self._lock:
|
|
23
|
+
self._now += seconds
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""What differs between Nextion display models, as measured on each.
|
|
2
|
+
|
|
3
|
+
A Device holds the numbers the emulator needs that belong to the display, not to the project it
|
|
4
|
+
runs or the MCU that drives it. Only one model has been measured so far. Another gets its own
|
|
5
|
+
profile once someone measures it: replay the probes in tests/emu/test_calibration.py against
|
|
6
|
+
the real display and record what it does.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class Device:
|
|
14
|
+
model: str
|
|
15
|
+
comok: bytes # the reply to `connect`, without its terminator
|
|
16
|
+
flash_size: int # bytes, for TFT uploads
|
|
17
|
+
buffer_capacity: int # bytes of input the display holds before it drops what arrives
|
|
18
|
+
redraw_s: float # how long a full-page redraw (`page`, `ref`) blocks the next instruction
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# The Sonoff NSPanel's display, measured on a panel ("Office") 2026-10-23/24: the input buffer
|
|
22
|
+
# held 1080-1092 bytes, and a page change blocked the next instruction for about 400 ms.
|
|
23
|
+
NX4832T035 = Device(
|
|
24
|
+
model="NX4832T035_011R",
|
|
25
|
+
comok=b"comok 2,30614-0,NX4832T035_011R,163,61488,D264B8204F0E1828,16777216",
|
|
26
|
+
flash_size=16777216,
|
|
27
|
+
buffer_capacity=1088,
|
|
28
|
+
redraw_s=0.4,
|
|
29
|
+
)
|