Initializare dupa curatare folder
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
"""End-to-end: a real card read must drive the LED on and off.
|
||||
|
||||
Uses a pseudo-terminal for the reader and a fake pin for the LED, so the whole
|
||||
chain runs — serial frame -> card event -> LED worker -> pin state — without a
|
||||
Pi and without GPIO hardware.
|
||||
"""
|
||||
|
||||
import os
|
||||
import pty
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
PROJECT = os.path.dirname(HERE)
|
||||
VENV_PY = os.path.join(PROJECT, ".venv", "bin", "python")
|
||||
PYTHON = VENV_PY if os.path.exists(VENV_PY) else sys.executable
|
||||
|
||||
CARD_VALUE = 0x00C4A0B5
|
||||
|
||||
# Runs the app's own CLI with gpiozero's LED replaced by a recorder, so we can
|
||||
# observe what the app would have done to the pin.
|
||||
DRIVER = r'''
|
||||
import os, sys, threading, time, json
|
||||
|
||||
sys.path.insert(0, os.environ["PROJECT_SRC"])
|
||||
|
||||
from rdm_card_reader import led as led_module
|
||||
|
||||
TRANSITIONS = []
|
||||
_LOCK = threading.Lock()
|
||||
|
||||
|
||||
class RecordingLED:
|
||||
def __init__(self, pin=None, active_high=True):
|
||||
self.pin = pin
|
||||
self._state = False
|
||||
|
||||
def _record(self, value):
|
||||
self._state = value
|
||||
with _LOCK:
|
||||
TRANSITIONS.append(value)
|
||||
# Write through immediately: the test terminates this process, so
|
||||
# anything buffered for the end would be lost.
|
||||
sys.stderr.write("TRANSITION=" + value + "\n")
|
||||
sys.stderr.flush()
|
||||
|
||||
def on(self):
|
||||
self._record("on")
|
||||
|
||||
def off(self):
|
||||
self._record("off")
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
|
||||
class FakeGPIOLED(led_module.GPIOLED):
|
||||
def __init__(self, pin, active_high=True):
|
||||
self.pin = pin
|
||||
self._led = RecordingLED(pin, active_high)
|
||||
self._is_on = False
|
||||
|
||||
def on(self):
|
||||
self._led.on()
|
||||
self._is_on = True
|
||||
|
||||
def off(self):
|
||||
self._led.off()
|
||||
self._is_on = False
|
||||
|
||||
def close(self):
|
||||
self._led.close()
|
||||
|
||||
|
||||
led_module.GPIOLED = FakeGPIOLED
|
||||
|
||||
from rdm_card_reader.cli import main
|
||||
|
||||
code = main(["--port", os.environ["SERIAL_PORT"], "--heartbeat", "0.3",
|
||||
"--led-mode", os.environ.get("LED_MODE", "error"), "--json"])
|
||||
sys.exit(code)
|
||||
'''
|
||||
|
||||
|
||||
def em4100_frame(value, card_type=0x0F):
|
||||
id_bytes = struct.pack(">I", value)
|
||||
checksum = card_type
|
||||
for b in id_bytes:
|
||||
checksum ^= b
|
||||
frame = (bytes([card_type]) + id_bytes + bytes([checksum])).hex().upper()
|
||||
assert len(frame) == 12, frame
|
||||
return frame.encode("ascii")
|
||||
|
||||
|
||||
def pump(stream, sink):
|
||||
"""Read a stream on its own thread.
|
||||
|
||||
Text-mode pipes buffer, so select() would not report this data.
|
||||
"""
|
||||
for line in stream:
|
||||
sink.append(line.rstrip())
|
||||
|
||||
|
||||
def run_case(mode):
|
||||
"""Drive a card through the app and return (events, led transitions)."""
|
||||
master, slave = pty.openpty()
|
||||
serial_port = os.ttyname(slave)
|
||||
|
||||
env = dict(os.environ)
|
||||
env["SERIAL_PORT"] = serial_port
|
||||
env["PROJECT_SRC"] = os.path.join(PROJECT, "src")
|
||||
env["LED_MODE"] = mode
|
||||
env.pop("PYTHONPATH", None)
|
||||
|
||||
proc = subprocess.Popen(
|
||||
[PYTHON, "-u", "-c", DRIVER],
|
||||
cwd=PROJECT, env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
text=True, bufsize=1,
|
||||
)
|
||||
|
||||
stdout, stderr = [], []
|
||||
t1 = threading.Thread(target=pump, args=(proc.stdout, stdout), daemon=True)
|
||||
t2 = threading.Thread(target=pump, args=(proc.stderr, stderr), daemon=True)
|
||||
t1.start()
|
||||
t2.start()
|
||||
|
||||
frame = em4100_frame(CARD_VALUE)
|
||||
try:
|
||||
time.sleep(2.0) # let the reader open the port
|
||||
for _ in range(6): # card present
|
||||
os.write(master, b"\x02" + frame + b"\x03")
|
||||
time.sleep(0.1)
|
||||
time.sleep(1.2) # stop writing -> card removed
|
||||
finally:
|
||||
try:
|
||||
proc.terminate()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
proc.wait(timeout=5)
|
||||
except Exception:
|
||||
proc.kill()
|
||||
for t in (t1, t2):
|
||||
t.join(timeout=3)
|
||||
try:
|
||||
os.close(master)
|
||||
os.close(slave)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
transitions = [
|
||||
line.split("=", 1)[1]
|
||||
for line in stderr
|
||||
if line.startswith("TRANSITION=")
|
||||
]
|
||||
return stdout, stderr, transitions
|
||||
|
||||
|
||||
def main():
|
||||
failures = 0
|
||||
|
||||
# --- error mode: lit while running, dark while a card is read ----------
|
||||
stdout, stderr, transitions = run_case("error")
|
||||
print("======== error mode ========")
|
||||
for line in stdout[:2]:
|
||||
print(" " + line)
|
||||
print(" events: {}".format(
|
||||
[l.split('"event": "')[1].split('"')[0]
|
||||
for l in stdout if '"event"' in l]))
|
||||
print(" transitions: {}".format(transitions))
|
||||
|
||||
checks = {
|
||||
"card was read": any("card_inserted" in l for l in stdout),
|
||||
"card removal seen": any("card_removed" in l for l in stdout),
|
||||
"lit at startup": transitions[:1] == ["on"],
|
||||
"dark while reading": "off" in transitions,
|
||||
"lit again after": transitions[-1:] == ["on"],
|
||||
}
|
||||
|
||||
# --- presence mode: dark until a card arrives -------------------------
|
||||
stdout2, stderr2, transitions2 = run_case("presence")
|
||||
print("\n======== presence mode ========")
|
||||
print(" transitions: {}".format(transitions2))
|
||||
checks.update({
|
||||
"presence: read still works":
|
||||
any("card_inserted" in l for l in stdout2),
|
||||
"presence: dark at startup": transitions2[:1] == ["on"] or not transitions2,
|
||||
"presence: lit on the card": "on" in transitions2,
|
||||
"presence: dark after": transitions2[-1:] == ["off"],
|
||||
})
|
||||
|
||||
print()
|
||||
ok = True
|
||||
for name, passed in checks.items():
|
||||
print(" [{}] {}".format("PASS" if passed else "FAIL", name))
|
||||
ok = ok and bool(passed)
|
||||
|
||||
if not ok:
|
||||
failures = 1
|
||||
print("\n--- driver stderr (error mode) ---")
|
||||
print("\n".join(stderr)[:1200])
|
||||
return failures
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user