vention-barcode-scanner 0.1.0__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.
- vention_barcode_scanner-0.1.0/.gitignore +24 -0
- vention_barcode_scanner-0.1.0/PKG-INFO +162 -0
- vention_barcode_scanner-0.1.0/README.md +153 -0
- vention_barcode_scanner-0.1.0/config/config.local.yaml.example +17 -0
- vention_barcode_scanner-0.1.0/config/config.yaml +25 -0
- vention_barcode_scanner-0.1.0/docs/planning/current-state.md +28 -0
- vention_barcode_scanner-0.1.0/project.json +30 -0
- vention_barcode_scanner-0.1.0/pyproject.toml +50 -0
- vention_barcode_scanner-0.1.0/src/barcode_scanner/__init__.py +8 -0
- vention_barcode_scanner-0.1.0/src/barcode_scanner/__main__.py +201 -0
- vention_barcode_scanner-0.1.0/src/barcode_scanner/_compat.py +22 -0
- vention_barcode_scanner-0.1.0/src/barcode_scanner/config.py +124 -0
- vention_barcode_scanner-0.1.0/src/barcode_scanner/log_codes.py +124 -0
- vention_barcode_scanner-0.1.0/src/barcode_scanner/logger.py +85 -0
- vention_barcode_scanner-0.1.0/src/barcode_scanner/models.py +218 -0
- vention_barcode_scanner-0.1.0/src/barcode_scanner/scanners/__init__.py +22 -0
- vention_barcode_scanner-0.1.0/src/barcode_scanner/scanners/base.py +112 -0
- vention_barcode_scanner-0.1.0/src/barcode_scanner/scanners/evdev.py +247 -0
- vention_barcode_scanner-0.1.0/src/barcode_scanner/scanners/keyence.py +720 -0
- vention_barcode_scanner-0.1.0/src/barcode_scanner/scanners/mock.py +78 -0
- vention_barcode_scanner-0.1.0/src/barcode_scanner/service.py +392 -0
- vention_barcode_scanner-0.1.0/tests/__init__.py +0 -0
- vention_barcode_scanner-0.1.0/tests/test_config.py +110 -0
- vention_barcode_scanner-0.1.0/tests/test_keyence_scanner.py +662 -0
- vention_barcode_scanner-0.1.0/tests/test_logger.py +93 -0
- vention_barcode_scanner-0.1.0/tests/test_mock_scanner.py +81 -0
- vention_barcode_scanner-0.1.0/tests/test_models.py +171 -0
- vention_barcode_scanner-0.1.0/tests/test_service.py +297 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Node / Nx
|
|
2
|
+
node_modules/
|
|
3
|
+
dist/
|
|
4
|
+
.nx/
|
|
5
|
+
tmp/
|
|
6
|
+
coverage/
|
|
7
|
+
|
|
8
|
+
# Python / uv
|
|
9
|
+
.venv/
|
|
10
|
+
**/.venv/
|
|
11
|
+
__pycache__/
|
|
12
|
+
*.pyc
|
|
13
|
+
.pytest_cache/
|
|
14
|
+
.ruff_cache/
|
|
15
|
+
.coverage
|
|
16
|
+
coverage.xml
|
|
17
|
+
htmlcov/
|
|
18
|
+
|
|
19
|
+
# Editor / OS
|
|
20
|
+
.DS_Store
|
|
21
|
+
*.log
|
|
22
|
+
|
|
23
|
+
# Claude Code (local, per-developer)
|
|
24
|
+
.claude/
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vention-barcode-scanner
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Async barcode scanner fleet manager for Vention industrial automation. Supports Keyence TCP, USB evdev, and mock scanners.
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: pydantic>=2.0
|
|
7
|
+
Requires-Dist: pyyaml>=6.0
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# vention-barcode-scanner
|
|
11
|
+
|
|
12
|
+
Async barcode scanner fleet manager for Vention industrial automation. Unified `Scanner` ABC over Keyence TCP, USB HID (evdev), and mock backends.
|
|
13
|
+
|
|
14
|
+
## Scanner Types
|
|
15
|
+
|
|
16
|
+
| Type | Protocol | Platform | Use Case |
|
|
17
|
+
|------|----------|----------|----------|
|
|
18
|
+
| `KeyenceScanner` | Async TCP (port 9004) | Any | Keyence SR-series (SR-1000, SR-2000, SR-5000) |
|
|
19
|
+
| `EvdevScanner` | Linux USB (evdev) | Linux | USB keyboard-emulating barcode readers |
|
|
20
|
+
| `MockScanner` | In-memory | Any | Simulation and testing |
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from barcode_scanner.models import ScannerConfig, ScannerFleetConfig
|
|
26
|
+
from barcode_scanner.service import ScannerService
|
|
27
|
+
|
|
28
|
+
config = ScannerFleetConfig(
|
|
29
|
+
scanners=[
|
|
30
|
+
ScannerConfig(id="s1", host="192.168.7.100", location_id="station-a"),
|
|
31
|
+
ScannerConfig(id="s2", host="192.168.7.101", location_id="station-b"),
|
|
32
|
+
],
|
|
33
|
+
)
|
|
34
|
+
service = ScannerService.from_config(config, mode="real", scanner_type="keyence")
|
|
35
|
+
|
|
36
|
+
await service.connect_all()
|
|
37
|
+
result = await service.scan("station-a")
|
|
38
|
+
await service.disconnect_all()
|
|
39
|
+
|
|
40
|
+
result.status # ScanStatus.OK | NO_READ | TIMEOUT | ERROR
|
|
41
|
+
result.label_detected # True / False
|
|
42
|
+
result.label_string # "GT-205" or None
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Label Validation
|
|
46
|
+
|
|
47
|
+
Optional callback to reject invalid labels at the scanner layer:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
def validate_label(label: str) -> bool:
|
|
51
|
+
return label.startswith("GT-") and len(label) <= 20
|
|
52
|
+
|
|
53
|
+
service = ScannerService.from_config(config, label_validator=validate_label)
|
|
54
|
+
# Labels that fail validation are downgraded to NO_READ
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Metrics
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
metrics = service.get_metrics()["s1"]
|
|
61
|
+
metrics.total_scans # 1234
|
|
62
|
+
metrics.success_rate # 0.89
|
|
63
|
+
metrics.avg_scan_ms # 45.2
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Tuning and Diagnostics
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
await service.auto_tune("station-a") # One-shot FTUNE + SAVE
|
|
70
|
+
await service.tune("station-a", bank=0) # Interactive TUNE per bank + TQUIT
|
|
71
|
+
status = await service.get_scanner_status("station-a")
|
|
72
|
+
# {"busy": 0, "last_cmd": 0, "error": 0} # BUSYSTAT / CMDSTAT / ERRSTAT
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Structured Logging
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from barcode_scanner.logger import set_log_callback
|
|
79
|
+
|
|
80
|
+
def on_scanner_log(code, source, level, message):
|
|
81
|
+
mqtt_publish("scanner/logs", {"code": code, "source": source, "message": message})
|
|
82
|
+
|
|
83
|
+
set_log_callback(on_scanner_log)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Keyence Protocol
|
|
87
|
+
|
|
88
|
+
TCP port 9004. ASCII commands terminated with `\r`.
|
|
89
|
+
|
|
90
|
+
**Scan sequence:** `LON\r` (open scan window) -> dwell -> `LOFF\r` (close window) -> read response. The scanner only sends data after LOFF.
|
|
91
|
+
|
|
92
|
+
| Command | Response | Purpose |
|
|
93
|
+
|---------|----------|---------|
|
|
94
|
+
| `LON\r` | barcode or `ERROR\r` | Open scan window (default bank) |
|
|
95
|
+
| `LON,01\r` | barcode or `ERROR\r` | Open scan window (bank 1) |
|
|
96
|
+
| `LOFF\r` | -- | Close scan window |
|
|
97
|
+
| `BCLR\r` | `OK\r` | Clear read buffer |
|
|
98
|
+
| `RESET\r` | `OK\r` | Reset scanner (reboots, drops TCP) |
|
|
99
|
+
| `FTUNE\r` | `OK,FTUNE\r` then result | One-shot auto-focus calibration |
|
|
100
|
+
| `TUNE,00\r` | `OK\r` | Start interactive tuning (bank 0) |
|
|
101
|
+
| `TQUIT\r` | `OK\r` | Stop tuning and save |
|
|
102
|
+
| `SAVE\r` | `OK\r` | Persist settings across power cycles |
|
|
103
|
+
| `BUSYSTAT\r` | `0`/`1`/`2` | Query busy state (idle/reading/tuning) |
|
|
104
|
+
| `CMDSTAT\r` | `0`/`1`/`2` | Query last command result |
|
|
105
|
+
| `ERRSTAT\r` | `0`/`1`/`2` | Query hardware error state |
|
|
106
|
+
|
|
107
|
+
## API
|
|
108
|
+
|
|
109
|
+
### ScannerService
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
class ScannerService:
|
|
113
|
+
@classmethod
|
|
114
|
+
def from_config(cls, config, mode="real", scanner_type="keyence",
|
|
115
|
+
label_validator=None) -> ScannerService
|
|
116
|
+
|
|
117
|
+
async def scan(self, location_id, timeout=None, bank=None) -> ScanResult
|
|
118
|
+
async def connect_all(self) -> dict[str, bool]
|
|
119
|
+
async def disconnect_all(self) -> None
|
|
120
|
+
async def reset(self, location_id) -> bool
|
|
121
|
+
async def reset_all(self) -> dict[str, bool]
|
|
122
|
+
async def auto_tune(self, location_id) -> bool
|
|
123
|
+
async def tune(self, location_id, bank=0) -> bool
|
|
124
|
+
async def get_scanner_status(self, location_id) -> dict
|
|
125
|
+
def get_metrics(self) -> dict[str, ScanMetrics]
|
|
126
|
+
def get_metrics_summary(self) -> dict
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### ScanResult
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
class ScanResult:
|
|
133
|
+
status: ScanStatus # OK | NO_READ | TIMEOUT | ERROR
|
|
134
|
+
label_detected: bool
|
|
135
|
+
label_string: str | None
|
|
136
|
+
scanner_id: str
|
|
137
|
+
error: str | None
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Scanner ABC
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
class Scanner(ABC):
|
|
144
|
+
async def scan(self, timeout=None, bank=None) -> ScanResult
|
|
145
|
+
async def connect(self) -> bool
|
|
146
|
+
async def disconnect(self) -> None
|
|
147
|
+
async def reset(self) -> bool
|
|
148
|
+
async def auto_tune(self) -> bool
|
|
149
|
+
async def tune(self, bank=0) -> bool
|
|
150
|
+
async def get_status(self) -> dict
|
|
151
|
+
connected: bool # property
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Development
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
cd barcode-scanner
|
|
158
|
+
uv sync
|
|
159
|
+
make test # 115 tests
|
|
160
|
+
make lint # ruff check + format
|
|
161
|
+
make type-check # pyright
|
|
162
|
+
```
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# vention-barcode-scanner
|
|
2
|
+
|
|
3
|
+
Async barcode scanner fleet manager for Vention industrial automation. Unified `Scanner` ABC over Keyence TCP, USB HID (evdev), and mock backends.
|
|
4
|
+
|
|
5
|
+
## Scanner Types
|
|
6
|
+
|
|
7
|
+
| Type | Protocol | Platform | Use Case |
|
|
8
|
+
|------|----------|----------|----------|
|
|
9
|
+
| `KeyenceScanner` | Async TCP (port 9004) | Any | Keyence SR-series (SR-1000, SR-2000, SR-5000) |
|
|
10
|
+
| `EvdevScanner` | Linux USB (evdev) | Linux | USB keyboard-emulating barcode readers |
|
|
11
|
+
| `MockScanner` | In-memory | Any | Simulation and testing |
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from barcode_scanner.models import ScannerConfig, ScannerFleetConfig
|
|
17
|
+
from barcode_scanner.service import ScannerService
|
|
18
|
+
|
|
19
|
+
config = ScannerFleetConfig(
|
|
20
|
+
scanners=[
|
|
21
|
+
ScannerConfig(id="s1", host="192.168.7.100", location_id="station-a"),
|
|
22
|
+
ScannerConfig(id="s2", host="192.168.7.101", location_id="station-b"),
|
|
23
|
+
],
|
|
24
|
+
)
|
|
25
|
+
service = ScannerService.from_config(config, mode="real", scanner_type="keyence")
|
|
26
|
+
|
|
27
|
+
await service.connect_all()
|
|
28
|
+
result = await service.scan("station-a")
|
|
29
|
+
await service.disconnect_all()
|
|
30
|
+
|
|
31
|
+
result.status # ScanStatus.OK | NO_READ | TIMEOUT | ERROR
|
|
32
|
+
result.label_detected # True / False
|
|
33
|
+
result.label_string # "GT-205" or None
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Label Validation
|
|
37
|
+
|
|
38
|
+
Optional callback to reject invalid labels at the scanner layer:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
def validate_label(label: str) -> bool:
|
|
42
|
+
return label.startswith("GT-") and len(label) <= 20
|
|
43
|
+
|
|
44
|
+
service = ScannerService.from_config(config, label_validator=validate_label)
|
|
45
|
+
# Labels that fail validation are downgraded to NO_READ
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Metrics
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
metrics = service.get_metrics()["s1"]
|
|
52
|
+
metrics.total_scans # 1234
|
|
53
|
+
metrics.success_rate # 0.89
|
|
54
|
+
metrics.avg_scan_ms # 45.2
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Tuning and Diagnostics
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
await service.auto_tune("station-a") # One-shot FTUNE + SAVE
|
|
61
|
+
await service.tune("station-a", bank=0) # Interactive TUNE per bank + TQUIT
|
|
62
|
+
status = await service.get_scanner_status("station-a")
|
|
63
|
+
# {"busy": 0, "last_cmd": 0, "error": 0} # BUSYSTAT / CMDSTAT / ERRSTAT
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Structured Logging
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from barcode_scanner.logger import set_log_callback
|
|
70
|
+
|
|
71
|
+
def on_scanner_log(code, source, level, message):
|
|
72
|
+
mqtt_publish("scanner/logs", {"code": code, "source": source, "message": message})
|
|
73
|
+
|
|
74
|
+
set_log_callback(on_scanner_log)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Keyence Protocol
|
|
78
|
+
|
|
79
|
+
TCP port 9004. ASCII commands terminated with `\r`.
|
|
80
|
+
|
|
81
|
+
**Scan sequence:** `LON\r` (open scan window) -> dwell -> `LOFF\r` (close window) -> read response. The scanner only sends data after LOFF.
|
|
82
|
+
|
|
83
|
+
| Command | Response | Purpose |
|
|
84
|
+
|---------|----------|---------|
|
|
85
|
+
| `LON\r` | barcode or `ERROR\r` | Open scan window (default bank) |
|
|
86
|
+
| `LON,01\r` | barcode or `ERROR\r` | Open scan window (bank 1) |
|
|
87
|
+
| `LOFF\r` | -- | Close scan window |
|
|
88
|
+
| `BCLR\r` | `OK\r` | Clear read buffer |
|
|
89
|
+
| `RESET\r` | `OK\r` | Reset scanner (reboots, drops TCP) |
|
|
90
|
+
| `FTUNE\r` | `OK,FTUNE\r` then result | One-shot auto-focus calibration |
|
|
91
|
+
| `TUNE,00\r` | `OK\r` | Start interactive tuning (bank 0) |
|
|
92
|
+
| `TQUIT\r` | `OK\r` | Stop tuning and save |
|
|
93
|
+
| `SAVE\r` | `OK\r` | Persist settings across power cycles |
|
|
94
|
+
| `BUSYSTAT\r` | `0`/`1`/`2` | Query busy state (idle/reading/tuning) |
|
|
95
|
+
| `CMDSTAT\r` | `0`/`1`/`2` | Query last command result |
|
|
96
|
+
| `ERRSTAT\r` | `0`/`1`/`2` | Query hardware error state |
|
|
97
|
+
|
|
98
|
+
## API
|
|
99
|
+
|
|
100
|
+
### ScannerService
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
class ScannerService:
|
|
104
|
+
@classmethod
|
|
105
|
+
def from_config(cls, config, mode="real", scanner_type="keyence",
|
|
106
|
+
label_validator=None) -> ScannerService
|
|
107
|
+
|
|
108
|
+
async def scan(self, location_id, timeout=None, bank=None) -> ScanResult
|
|
109
|
+
async def connect_all(self) -> dict[str, bool]
|
|
110
|
+
async def disconnect_all(self) -> None
|
|
111
|
+
async def reset(self, location_id) -> bool
|
|
112
|
+
async def reset_all(self) -> dict[str, bool]
|
|
113
|
+
async def auto_tune(self, location_id) -> bool
|
|
114
|
+
async def tune(self, location_id, bank=0) -> bool
|
|
115
|
+
async def get_scanner_status(self, location_id) -> dict
|
|
116
|
+
def get_metrics(self) -> dict[str, ScanMetrics]
|
|
117
|
+
def get_metrics_summary(self) -> dict
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### ScanResult
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
class ScanResult:
|
|
124
|
+
status: ScanStatus # OK | NO_READ | TIMEOUT | ERROR
|
|
125
|
+
label_detected: bool
|
|
126
|
+
label_string: str | None
|
|
127
|
+
scanner_id: str
|
|
128
|
+
error: str | None
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Scanner ABC
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
class Scanner(ABC):
|
|
135
|
+
async def scan(self, timeout=None, bank=None) -> ScanResult
|
|
136
|
+
async def connect(self) -> bool
|
|
137
|
+
async def disconnect(self) -> None
|
|
138
|
+
async def reset(self) -> bool
|
|
139
|
+
async def auto_tune(self) -> bool
|
|
140
|
+
async def tune(self, bank=0) -> bool
|
|
141
|
+
async def get_status(self) -> dict
|
|
142
|
+
connected: bool # property
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Development
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
cd barcode-scanner
|
|
149
|
+
uv sync
|
|
150
|
+
make test # 115 tests
|
|
151
|
+
make lint # ruff check + format
|
|
152
|
+
make type-check # pyright
|
|
153
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Local development overrides for barcode scanner service
|
|
2
|
+
# Copy to config.local.yaml and customize for your environment.
|
|
3
|
+
# This file is gitignored and will not be committed.
|
|
4
|
+
#
|
|
5
|
+
# Only include values you want to override — everything else
|
|
6
|
+
# falls through to config.yaml defaults.
|
|
7
|
+
|
|
8
|
+
# Force mock mode for local development (no hardware needed)
|
|
9
|
+
scanner_type: "mock"
|
|
10
|
+
|
|
11
|
+
# Increase mock label probability for easier testing
|
|
12
|
+
mock_label_probability: 0.5
|
|
13
|
+
mock_labels: ["LABEL-001", "LABEL-002", "LABEL-003", "LABEL-EMPTY"]
|
|
14
|
+
|
|
15
|
+
# Override fleet with localhost scanners (for Keyence simulator testing)
|
|
16
|
+
# fleet:
|
|
17
|
+
# - { id: "scanner-1", host: "127.0.0.1", port: 9004, location_id: "station-1" }
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# vention-barcode-scanner — Example Fleet Configuration
|
|
2
|
+
#
|
|
3
|
+
# Each scanner needs a unique ID, its IP address,
|
|
4
|
+
# and a location_id that the host application uses to trigger scans.
|
|
5
|
+
#
|
|
6
|
+
# Local overrides: config/config.local.yaml (gitignored)
|
|
7
|
+
|
|
8
|
+
scanner_type: "keyence"
|
|
9
|
+
|
|
10
|
+
default_timeout: 2.0
|
|
11
|
+
reconnect_delay: 5.0
|
|
12
|
+
max_reconnect_attempts: 0 # 0 = unlimited
|
|
13
|
+
max_scan_retries: 2
|
|
14
|
+
scan_retry_delay: 0.3
|
|
15
|
+
keepalive_interval: 10.0 # BCLR ping every 10s to detect dead TCP connections
|
|
16
|
+
|
|
17
|
+
mock_label_probability: 0.3
|
|
18
|
+
mock_labels: ["LABEL-001", "LABEL-002", "LABEL-003"]
|
|
19
|
+
|
|
20
|
+
fleet:
|
|
21
|
+
- { id: "scanner-1", host: "192.168.1.100", port: 9004, location_id: "zone-a" }
|
|
22
|
+
- { id: "scanner-2", host: "192.168.1.101", port: 9004, location_id: "zone-b" }
|
|
23
|
+
|
|
24
|
+
# Disabled scanner (skipped during fleet init)
|
|
25
|
+
# - { id: "scanner-3", host: "192.168.1.102", port: 9004, location_id: "zone-c", enabled: false }
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Current state — barcode-scanner
|
|
2
|
+
|
|
3
|
+
What's actually built in `barcode-scanner` **now** — the fastest orientation for
|
|
4
|
+
a new dev or agent. Read this first. Distinct from siblings:
|
|
5
|
+
|
|
6
|
+
- **Repo-wide state** — [`../../../../docs/planning/current-state.md`](../../../../docs/planning/current-state.md).
|
|
7
|
+
- `backlog.md` / `open-questions.md` / `tech-debt.md` — seeded here if/when this
|
|
8
|
+
lib has its own; it has none today (no empty stubs).
|
|
9
|
+
|
|
10
|
+
It **grows by pruning, not accretion** — superseded state is rewritten, not
|
|
11
|
+
appended. Last updated: 2026-06-03.
|
|
12
|
+
|
|
13
|
+
## What's working
|
|
14
|
+
|
|
15
|
+
- Async **scanner fleet manager** — a `Scanner` ABC + `KeyenceScanner` (TCP),
|
|
16
|
+
`EvdevScanner` (USB, Linux), `MockScanner`, behind `ScannerService`
|
|
17
|
+
(location-id → scanner). Config loader, log codes, structured logger.
|
|
18
|
+
- **118 tests, 86% coverage**, ruff clean. Lifted from The Feed; unchanged in
|
|
19
|
+
behavior.
|
|
20
|
+
- `evdev` is Linux/USB-only (omitted from coverage; exercised on CI).
|
|
21
|
+
|
|
22
|
+
## Recently landed
|
|
23
|
+
|
|
24
|
+
- 2026-06-02 — lifted into delivery-products as `scope:device`; `pytest-cov` wired.
|
|
25
|
+
|
|
26
|
+
<!-- No backlog.md: no open lib-specific work. Repo-wide CI coverage gate
|
|
27
|
+
(≥80%) is tracked in ../../../../docs/planning/backlog.md — barcode-scanner
|
|
28
|
+
is at 86%. -->
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "barcode-scanner",
|
|
3
|
+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"projectType": "library",
|
|
5
|
+
"sourceRoot": "libs/barcode-scanner/src",
|
|
6
|
+
"tags": ["scope:device", "type:backend"],
|
|
7
|
+
"release": {
|
|
8
|
+
"version": { "generator": "@nxlv/python:release-version" }
|
|
9
|
+
},
|
|
10
|
+
"targets": {
|
|
11
|
+
"nx-release-publish": {
|
|
12
|
+
"executor": "nx:run-commands",
|
|
13
|
+
"options": {
|
|
14
|
+
"command": "bash tools/publish-codeartifact.sh --projectRoot={projectRoot} --dryRun={args.dryRun}"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"lint": {
|
|
18
|
+
"executor": "nx:run-commands",
|
|
19
|
+
"options": { "command": "uv run ruff check .", "cwd": "libs/barcode-scanner" }
|
|
20
|
+
},
|
|
21
|
+
"test": {
|
|
22
|
+
"executor": "nx:run-commands",
|
|
23
|
+
"options": { "command": "uv run pytest -q --cov=barcode_scanner --cov-report=term-missing", "cwd": "libs/barcode-scanner" }
|
|
24
|
+
},
|
|
25
|
+
"check": {
|
|
26
|
+
"executor": "nx:run-commands",
|
|
27
|
+
"options": { "command": "uv run python -c \"import barcode_scanner\"", "cwd": "libs/barcode-scanner" }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "vention-barcode-scanner"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Async barcode scanner fleet manager for Vention industrial automation. Supports Keyence TCP, USB evdev, and mock scanners."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"pydantic>=2.0",
|
|
9
|
+
"pyyaml>=6.0",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[build-system]
|
|
13
|
+
requires = ["hatchling"]
|
|
14
|
+
build-backend = "hatchling.build"
|
|
15
|
+
|
|
16
|
+
[tool.hatch.build.targets.wheel]
|
|
17
|
+
packages = ["src/barcode_scanner"]
|
|
18
|
+
|
|
19
|
+
[dependency-groups]
|
|
20
|
+
dev = [
|
|
21
|
+
"pytest>=8.3.4",
|
|
22
|
+
"pytest-asyncio>=0.24.0",
|
|
23
|
+
"pytest-cov>=5.0",
|
|
24
|
+
"ruff>=0.8.0",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[tool.ruff]
|
|
28
|
+
line-length = 100
|
|
29
|
+
target-version = "py310"
|
|
30
|
+
|
|
31
|
+
[tool.ruff.lint]
|
|
32
|
+
select = ["E", "F", "I", "W"]
|
|
33
|
+
|
|
34
|
+
[tool.pytest.ini_options]
|
|
35
|
+
asyncio_mode = "auto"
|
|
36
|
+
testpaths = ["tests"]
|
|
37
|
+
|
|
38
|
+
[tool.coverage.run]
|
|
39
|
+
source = ["barcode_scanner"]
|
|
40
|
+
omit = [
|
|
41
|
+
"src/barcode_scanner/__main__.py", # CLI dev tool, not library code
|
|
42
|
+
"src/barcode_scanner/scanners/evdev.py", # Linux-only, tested on CI
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[tool.coverage.report]
|
|
46
|
+
show_missing = true
|
|
47
|
+
# CI coverage gate (backlog item, now wired): a device lib asserts full coverage,
|
|
48
|
+
# so a drop below 80% fails the build. Currently ≈86%. The `test` target collects
|
|
49
|
+
# coverage and CI runs it (affected + the Python matrix), so fail_under is enforced.
|
|
50
|
+
fail_under = 80
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Vention barcode scanner library.
|
|
2
|
+
|
|
3
|
+
Unified async abstraction over different barcode scanner hardware.
|
|
4
|
+
This is a library, not a service — no HTTP server, no separate process.
|
|
5
|
+
Host applications import it and call service.scan(location_id) in-process.
|
|
6
|
+
|
|
7
|
+
Supports Keyence (TCP), Evdev (USB), and Mock scanner types.
|
|
8
|
+
"""
|