feat: add tactility controls and notes CRUD

This commit is contained in:
Adolfo Reyna
2026-08-17 13:19:36 -04:00
parent c445093186
commit 1996a123d9
7 changed files with 426 additions and 18 deletions
+113
View File
@@ -0,0 +1,113 @@
import json
from pathlib import Path
import httpx
from typer.testing import CliRunner
from reyna_cli.cli import app
from reyna_cli.config import Device, Registry, get_device, resolve_device_host
from reyna_cli.tactility import TactilityClient
runner = CliRunner()
def test_friendly_name_lookup_is_case_insensitive():
registry = Registry(devices=[Device(id="kidsos_5c5c", display_name="Grace's 2.8-inch Tactility Board")])
assert get_device("Grace", registry).id == "kidsos_5c5c"
assert get_device("grace", registry).id == "kidsos_5c5c"
def test_resolve_device_host_prefers_live_mdns(monkeypatch):
monkeypatch.setattr("reyna_cli.config.socket.gethostbyname", lambda host: "192.168.68.141")
device = Device(id="kidsos_1234", host="kidsos-1234.local", reserved_ip="192.168.68.99")
assert resolve_device_host(device) == "192.168.68.141"
def test_resolve_device_host_falls_back_when_mdns_is_offline(monkeypatch):
def fail(_host):
raise OSError("offline")
monkeypatch.setattr("reyna_cli.config.socket.gethostbyname", fail)
device = Device(id="kidsos_1234", host="kidsos-1234.local", reserved_ip="192.168.68.99")
assert resolve_device_host(device) == "192.168.68.99"
def test_tactility_client_uses_web_api(monkeypatch, tmp_path):
requests = []
def handler(request: httpx.Request):
requests.append(request)
if request.url.path == "/api/sysinfo":
return httpx.Response(200, json={"version": "0.8.0-dev"})
if request.url.path == "/api/apps":
return httpx.Response(200, json={"apps": [{"id": "one.tactility.demo"}]})
if request.url.path == "/fs/list":
return httpx.Response(200, json={"path": "/sdcard", "entries": []})
return httpx.Response(200, json={"ok": True})
client = TactilityClient("http://kidsos-1234.local", transport=httpx.MockTransport(handler))
assert client.sysinfo()["version"] == "0.8.0-dev"
assert client.apps()["apps"][0]["id"] == "one.tactility.demo"
assert client.fs_list("/sdcard")["path"] == "/sdcard"
assert requests[0].url.host == "kidsos-1234.local"
def test_tactility_install_and_run_requests(tmp_path):
requests = []
def handler(request: httpx.Request):
requests.append(request)
return httpx.Response(200, json={"ok": True})
app_file = tmp_path / "demo.app"
app_file.write_bytes(b"APP")
client = TactilityClient("http://192.168.68.99", transport=httpx.MockTransport(handler))
client.install_app(app_file)
client.run_app("one.tactility.demo")
assert requests[0].method == "PUT"
assert requests[0].url.path == "/api/apps/install"
assert requests[1].url.path == "/api/apps/run"
assert requests[1].url.params["id"] == "one.tactility.demo"
def test_tactility_screen_text_clears_before_write():
requests = []
def handler(request: httpx.Request):
requests.append(request)
return httpx.Response(200, json={"ok": True})
client = TactilityClient("http://192.168.68.99", transport=httpx.MockTransport(handler))
result = client.screen_text("Grace", 20, 10, clear_first=True)
assert result["clear_first"] is True
assert [request.url.path for request in requests] == ["/api/screen/raw", "/api/screen/raw"]
assert requests[0].content == bytes(20 * 10 * 2)
assert len(requests[1].content) == 20 * 10 * 2
def test_tactility_cli_has_commands(monkeypatch, tmp_path):
registry_path = tmp_path / "devices.yaml"
registry_path.write_text(
"""
devices:
kidsos_1234:
type: esp32-s3-tactility
host: kidsos-1234.local
reserved_ip: 192.168.68.99
""",
encoding="utf-8",
)
monkeypatch.setenv("REYNA_DEVICES_REGISTRY", str(registry_path))
result = runner.invoke(app, ["tactility", "--help"])
assert result.exit_code == 0
for command in ("discover", "sysinfo", "apps", "install", "run", "report", "fs", "screen"):
assert command in result.stdout
def test_robot_arm_is_not_a_cli_surface():
result = runner.invoke(app, ["devices", "--help"])
assert result.exit_code == 0
assert "arm" not in result.stdout.lower()