aj090-hw-tools 0.0.4__tar.gz → 0.0.5__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.

Potentially problematic release.


This version of aj090-hw-tools might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aj090-hw-tools
3
- Version: 0.0.4
3
+ Version: 0.0.5
4
4
  Project-URL: Documentation, https://github.com/Vasencheg/aj090-hw-tools#readme
5
5
  Project-URL: Issues, https://github.com/Vasencheg/aj090-hw-tools/issues
6
6
  Project-URL: Source, https://github.com/Vasencheg/aj090-hw-tools
@@ -22,7 +22,7 @@ Requires-Dist: aiofile>=3.9.0
22
22
  Requires-Dist: aiohttp>=3.11.14
23
23
  Requires-Dist: colorama>=0.4.6
24
24
  Requires-Dist: configargparse>=1.7
25
- Requires-Dist: esp-idf-nvs-partition-gen==0.1.8
25
+ Requires-Dist: esp-idf-nvs-partition-gen>=0.1.8
26
26
  Requires-Dist: esptool==4.8.1
27
27
  Requires-Dist: numpy>=2.2.4
28
28
  Requires-Dist: pexpect>=4.9.0
@@ -27,7 +27,7 @@ classifiers = [
27
27
  dependencies = [
28
28
  "pyserial>=3.5",
29
29
  "esptool==4.8.1",
30
- "esp_idf_nvs_partition_gen==0.1.8",
30
+ "esp-idf-nvs-partition-gen>=0.1.8",
31
31
  "pexpect>=4.9.0",
32
32
  "configargparse>=1.7",
33
33
  "aiohttp>=3.11.14",
@@ -1,4 +1,4 @@
1
1
  # SPDX-FileCopyrightText: 2025-present Vasencheg <roman.vasilev@nobitlost.com>
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
- __version__ = "0.0.4"
4
+ __version__ = "0.0.5"
@@ -5,7 +5,8 @@ from .__about__ import __version__
5
5
  from .tools import (
6
6
  firmware,
7
7
  test,
8
- info
8
+ info,
9
+ serial
9
10
  )
10
11
 
11
12
  __all__ = [
@@ -36,11 +37,11 @@ def _main():
36
37
  parser_firmware = subparsers.add_parser('firmware', help='device firmware operations group')
37
38
  parser_firmware.set_defaults(func=firmware)
38
39
  parser_firmware.add_argument('operation', type=str, choices=['write', 'erase'], help='firmware operations')
39
- parser_firmware.add_argument('--bin_file', type=str, help='firmware bin file')
40
+ parser_firmware.add_argument('-f', '--bin_file', type=str, help='firmware bin file')
40
41
 
41
42
  # Info
42
- parser_firmware = subparsers.add_parser('info', help='device info')
43
- parser_firmware.set_defaults(func=info)
43
+ parser_info = subparsers.add_parser('info', help='device info')
44
+ parser_info.set_defaults(func=info)
44
45
 
45
46
  # Test
46
47
  parser_test = subparsers.add_parser('test', help='device tests')
@@ -48,6 +49,9 @@ def _main():
48
49
 
49
50
  # Serial
50
51
  parser_serial = subparsers.add_parser('serial', help='device serial number operations')
52
+ parser_serial.set_defaults(func=serial)
53
+ parser_serial.add_argument('operation', type=str, choices=['write', 'read'], help='serial operations')
54
+ parser_serial.add_argument('-s', '--serial', type=str, required='write' in sys.argv, help='serial number')
51
55
 
52
56
  # Factory
53
57
  # parser_factory = subparsers.add_parser('factory_mode', help='device factory mode control')
@@ -0,0 +1 @@
1
+ SERIAL_NUM_PATTERN = r'\d\d{2}\d{2}\d\d{4}'
@@ -4,6 +4,7 @@ import pexpect
4
4
  import pexpect.spawnbase
5
5
  import time
6
6
  import esptool
7
+ import re
7
8
 
8
9
  from rich.console import Console
9
10
  from rich.table import Table
@@ -11,6 +12,8 @@ from pexpect.fdpexpect import fdspawn
11
12
  from typing import Optional, Type, NamedTuple
12
13
  from esptool.cmds import detect_chip
13
14
 
15
+ from ..common.const import SERIAL_NUM_PATTERN
16
+
14
17
  __all__ = [
15
18
  'info'
16
19
  ]
@@ -40,7 +43,7 @@ class DeviceInfo(NamedTuple):
40
43
  sn: str
41
44
 
42
45
  async def board_type_get(dut: DUT) -> Optional[str]:
43
- result = await dut.expect(['MAIN: Starting (?:\w+ )?(CELL|SHELF) CONTROLLER application', pexpect.TIMEOUT], timeout=5.0, async_=True)
46
+ result = await dut.expect([r'MAIN: Starting (?:\w+ )?(CELL|SHELF) CONTROLLER application', pexpect.TIMEOUT], timeout=5.0, async_=True)
44
47
  if result:
45
48
  raise GatherInfoError('Unknown type of board or device has not been flashed yet')
46
49
  board_type = dut.match.group(1).decode('utf-8').lower()
@@ -53,8 +56,10 @@ async def device_info_get(dut: DUT) -> Optional[DeviceInfo]:
53
56
  # sha256: 3c050c5d0ea4aea33643eba2b7e840c607af710176e4387b3b22f183c920f09e
54
57
  # hw version: 1.0
55
58
  # MAC: 64:E8:33:48:EF:C4
56
- # serial: AJ090_SC_XX_XXXXXXXX
57
- MATCH_PATTERN = 'DEVICE_INFO:\s+app version:\s+(\w{7})\s+compile time:\s+(\d{2}:\d{2}:\d{2})\s+sha256:\s+(\w{64})\s+hw version:\s+(\d{1}\.\d{1})\s+MAC:\s+(\w{2}:\w{2}:\w{2}:\w{2}:\w{2}:\w{2})\s+serial:\s+(AJ090_(?:SC|CC)_\w+)'
59
+ # serial: XXXXXXXX
60
+ MATCH_PATTERN = re.compile(
61
+ r'DEVICE_INFO:\s+app version:\s+(\w{{7}})\s+compile time:\s+(\d{{2}}:\d{{2}}:\d{{2}})\s+sha256:\s+(\w{{64}})\s+hw version:\s+(\d{{1}}\.\d{{1}})\s+MAC:\s+(\w{{2}}:\w{{2}}:\w{{2}}:\w{{2}}:\w{{2}}:\w{{2}})\s+serial:\s+({SERIAL_NUM_PATTERN})'.format(SERIAL_NUM_PATTERN=SERIAL_NUM_PATTERN)
62
+ )
58
63
  result = await dut.expect([MATCH_PATTERN, pexpect.TIMEOUT], timeout=5.0, async_=True)
59
64
  if result:
60
65
  raise GatherInfoError('Failed to collect information about the device')
@@ -0,0 +1,98 @@
1
+ import asyncio
2
+ import logging
3
+ import subprocess
4
+ import sys
5
+ import tempfile
6
+ import re
7
+ import esptool
8
+ import time
9
+
10
+ from esptool.cmds import detect_chip
11
+ from rich.console import Console
12
+
13
+ from ..common.const import SERIAL_NUM_PATTERN
14
+
15
+ __all__ = [
16
+ 'serial'
17
+ ]
18
+
19
+ # Logger
20
+ FORMAT = '%(name)s:%(levelname)s: %(message)s'
21
+ logging.basicConfig(level=logging.ERROR, format=FORMAT)
22
+ logger = logging.getLogger(__name__)
23
+
24
+ console = Console()
25
+
26
+ def nvs_partition_template(factory_mode: bool, hw_version: str, serial_number: str) -> str:
27
+ return """key,type,encoding,value
28
+ factory,namespace,,
29
+ factory_mode,data,u8,{factory_mode}
30
+ hw_version,data,string,{hw_version}
31
+ serial,data,string,{serial_number}
32
+ """.format(
33
+ factory_mode = int(factory_mode),
34
+ hw_version = hw_version,
35
+ serial_number = serial_number
36
+ )
37
+
38
+ async def nvs_write(device, bin_file: str, offset: int = 0x11000) -> None:
39
+ def _write():
40
+ command = ['write_flash', f'{offset}', f'{bin_file}']
41
+ logger.debug("Using command ", " ".join(command))
42
+ esptool.main(command, device)
43
+
44
+ loop = asyncio.get_running_loop()
45
+ return await loop.run_in_executor(None, _write)
46
+
47
+ async def serial_write(device, serial: str) -> int:
48
+ match = re.search(SERIAL_NUM_PATTERN, serial)
49
+ if match is None:
50
+ console.print('Wrong serial number format', style='bold red')
51
+ return -1
52
+ serial = match.group(0)
53
+ with tempfile.NamedTemporaryFile(suffix='.csv') as nvs_csv:
54
+ data = bytes(nvs_partition_template(True, '1.0', serial).encode())
55
+ nvs_csv.write(data)
56
+ nvs_csv.seek(0)
57
+ logger.debug(f'CSV file data: {nvs_csv.read()}')
58
+
59
+ with tempfile.NamedTemporaryFile(suffix='.bin') as nvs_bin:
60
+ args = ['generate', nvs_csv.name, nvs_bin.name, '0x10000']
61
+ result = subprocess.run([sys.executable, '-m', 'esp_idf_nvs_partition_gen'] + args).returncode
62
+ if not result:
63
+ await nvs_write(device, nvs_bin.name)
64
+ else:
65
+ console.print('NVS partition generate error', style='bold red')
66
+ return -1
67
+
68
+ console.print('SUCCESS', style='bold green')
69
+
70
+ async def serial_read() -> int:
71
+ return -1
72
+
73
+ def serial(argv) -> int:
74
+ port = argv.port if argv.port is not None else esptool.ESPLoader.DEFAULT_PORT
75
+ connects = 10 # NOTE: the workaround to the issue "Could not open /dev/tty..., the port is busy or doesn't exist"
76
+ for _ in range(connects):
77
+ try:
78
+ with detect_chip(port=port, connect_attempts=0) as device:
79
+ match argv.operation:
80
+ case 'write':
81
+ return asyncio.run(serial_write(device, argv.serial))
82
+ case 'read':
83
+ return asyncio.run(serial_read(device))
84
+ case _:
85
+ console.print('Unknown command', style='red bold')
86
+ return -1
87
+ except OSError:
88
+ # NOTE: we are trying to close an already closed port (in device_test()),
89
+ # thus an OSError occurs (invalid file descriptor)
90
+ return 0
91
+ except esptool.util.FatalError as err:
92
+ logger.debug(err)
93
+ time.sleep(1.0)
94
+ print("Can't connect to the device")
95
+ return -1
96
+
97
+
98
+
@@ -2,9 +2,7 @@ import asyncio
2
2
  import logging
3
3
  import pexpect
4
4
  import pexpect.spawnbase
5
- import serial
6
- import serial.rs485
7
- import serial.tools.list_ports_linux
5
+ import re
8
6
  import time
9
7
  import esptool
10
8
 
@@ -14,6 +12,8 @@ from pexpect.fdpexpect import fdspawn
14
12
  from typing import Optional, Type, NamedTuple
15
13
  from esptool.cmds import detect_chip
16
14
 
15
+ from ..common.const import SERIAL_NUM_PATTERN
16
+
17
17
  DEFAULT_BAUDRATE = 115200
18
18
  DEFAULT_SERIAL_TIMEOUT_IN_SECONDS = 2
19
19
 
@@ -50,10 +50,6 @@ def red(text) -> str:
50
50
  def green(text) -> str:
51
51
  return Style.BRIGHT + Fore.GREEN + f'{text}' + Style.RESET_ALL
52
52
 
53
- def open_serial_port(port):
54
- serInstance = serial.Serial(port, DEFAULT_BAUDRATE, timeout=DEFAULT_SERIAL_TIMEOUT_IN_SECONDS)
55
- return serInstance
56
-
57
53
  class TestError(Exception):
58
54
  def __init__(self, message, expected_output: str = None, before: str = None):
59
55
  super().__init__(message)
@@ -82,9 +78,9 @@ async def do_test(dut: DUT, unit:str, expected: str, timeout: float = 5.0) -> in
82
78
 
83
79
  async def device_boot_test(dut: DUT) -> BootInfo:
84
80
  boot_expect = [
85
- 'boot: ESP-IDF v(\d\.\d|\d\.\d+-dirty) 2nd stage bootloader',
86
- 'boot: Loaded app from partition at offset (0x\d{5,})',
87
- 'app_init: App version:\s+(\w{7})'
81
+ r'boot: ESP-IDF v(\d\.\d|\d\.\d+-dirty) 2nd stage bootloader',
82
+ r'boot: Loaded app from partition at offset (0x\d{5,})',
83
+ r'app_init: App version:\s+(\w{7})'
88
84
  ]
89
85
 
90
86
  await do_test(dut, '2nd stage bootloader', boot_expect[0])
@@ -105,16 +101,16 @@ async def device_boot_test(dut: DUT) -> BootInfo:
105
101
 
106
102
  async def common_modules_init_test(dut: DUT) -> int:
107
103
  expected_outputs = [
108
- ('Firmware manager', 'FW_MANAGER: Firmware manager has been successfully initialized'),
109
- ('File storage', 'STORAGE: Storage manager has been successfully initialized'),
110
- ('Configuration manager', 'CFG_MANAGER: Configuration manager has been successfully initialized'),
111
- ('Board', 'BOARD: Board has been successfully initialized')
104
+ ('Firmware manager', r'FW_MANAGER: Firmware manager has been successfully initialized'),
105
+ ('File storage', r'STORAGE: Storage manager has been successfully initialized'),
106
+ ('Configuration manager', r'CFG_MANAGER: Configuration manager has been successfully initialized'),
107
+ ('Board', r'BOARD: Board has been successfully initialized')
112
108
  ]
113
109
  for unit, output in expected_outputs:
114
110
  await do_test(dut, unit, output)
115
111
 
116
112
  async def board_type_test(dut: DUT) -> str:
117
- await do_test(dut, 'Board type', 'MAIN: Starting (?:\w+ )?(CELL|SHELF) CONTROLLER application')
113
+ await do_test(dut, 'Board type', r'MAIN: Starting (?:\w+ )?(CELL|SHELF) CONTROLLER application')
118
114
  board_type = dut.match.group(1).decode('utf-8').lower()
119
115
  return board_type.lower()
120
116
 
@@ -125,8 +121,10 @@ async def device_info_test(dut: DUT) -> DeviceInfo:
125
121
  # sha256: 3c050c5d0ea4aea33643eba2b7e840c607af710176e4387b3b22f183c920f09e
126
122
  # hw version: 1.0
127
123
  # MAC: 64:E8:33:48:EF:C4
128
- # serial: AJ090_SC_XX_XXXXXXXX
129
- MATCH_PATTERN = 'DEVICE_INFO:\s+app version:\s+(\w{7})\s+compile time:\s+(\d{2}:\d{2}:\d{2})\s+sha256:\s+(\w{64})\s+hw version:\s+(\d{1}\.\d{1})\s+MAC:\s+(\w{2}:\w{2}:\w{2}:\w{2}:\w{2}:\w{2})\s+serial:\s+(AJ090_(?:SC|CC)_\w+)'
124
+ # serial: XXXXXXXX
125
+ MATCH_PATTERN = re.compile(
126
+ r'DEVICE_INFO:\s+app version:\s+(\w{{7}})\s+compile time:\s+(\d{{2}}:\d{{2}}:\d{{2}})\s+sha256:\s+(\w{{64}})\s+hw version:\s+(\d{{1}}\.\d{{1}})\s+MAC:\s+(\w{{2}}:\w{{2}}:\w{{2}}:\w{{2}}:\w{{2}}:\w{{2}})\s+serial:\s+({SERIAL_NUM_PATTERN})'.format(SERIAL_NUM_PATTERN=SERIAL_NUM_PATTERN)
127
+ )
130
128
  await do_test(dut, 'Device info', MATCH_PATTERN)
131
129
  device_info = DeviceInfo(
132
130
  app_ver = dut.match.group(1).decode('utf-8').lower(),
@@ -139,24 +137,24 @@ async def device_info_test(dut: DUT) -> DeviceInfo:
139
137
  return device_info
140
138
 
141
139
  async def cell_test(dut: DUT, app_version: str) -> int:
142
- await do_test(dut, 'Cells initialization', 'CELL_CONTROLLER: initialize cells of the (\w{6,9}) board')
140
+ await do_test(dut, 'Cells initialization', r'CELL_CONTROLLER: initialize cells of the (\w{6,9}) board')
143
141
  cell_type = dut.match.group(1).decode('utf-8').lower()
144
- await do_test(dut, 'Application', 'MAIN: The application has been successfully initialized')
142
+ await do_test(dut, 'Application', r'MAIN: The application has been successfully initialized')
145
143
  match cell_type:
146
144
  case 'retail':
147
- await do_test(dut, 'Cells#0 measurements', 'CELL_CONTROLLER: cell#0 measure result', timeout=20)
148
- await do_test(dut, 'Cells#1 measurements', 'CELL_CONTROLLER: cell#1 measure result', timeout=20)
145
+ await do_test(dut, 'Cells#0 measurements', r'CELL_CONTROLLER: cell#0 measure result', timeout=20)
146
+ await do_test(dut, 'Cells#1 measurements', r'CELL_CONTROLLER: cell#1 measure result', timeout=20)
149
147
  case 'wholesale':
150
- await do_test(dut, 'Cell measurements', 'CELL_CONTROLLER: cell#0 measure result', timeout=20)
148
+ await do_test(dut, 'Cell measurements', r'CELL_CONTROLLER: cell#0 measure result', timeout=20)
151
149
  case _:
152
150
  raise TestError(f'Unknown cell type: {cell_type}')
153
151
  return 0
154
152
 
155
153
  async def shelf_test(dut: DUT, app_version: str) -> int:
156
- await do_test(dut, 'Main task start', 'SHELF_CONTROLLER: Shelf controller main task started')
157
- await do_test(dut, 'Environment sensor initialization', 'ENV_SENSOR: sensor created')
158
- await do_test(dut, 'Application', 'MAIN: The application has been successfully initialized')
159
- await do_test(dut, 'Environment sensor measurements', 'SHELF_CONTROLLER: Temperature: (\d{1,3}[.,]\d+); humudity: (\d{1,2}[.,]\d+)')
154
+ await do_test(dut, 'Main task start', r'SHELF_CONTROLLER: Shelf controller main task started')
155
+ await do_test(dut, 'Environment sensor initialization', r'ENV_SENSOR: sensor created')
156
+ await do_test(dut, 'Application', r'MAIN: The application has been successfully initialized')
157
+ await do_test(dut, 'Environment sensor measurements', r'SHELF_CONTROLLER: Temperature: (\d{1,3}[.,]\d+); humudity: (\d{1,2}[.,]\d+)')
160
158
 
161
159
  return 0
162
160
 
@@ -1,7 +0,0 @@
1
- __all__ = [
2
- 'serial'
3
- ]
4
-
5
-
6
- def serial(argv) -> int:
7
- pass
File without changes