summaryrefslogtreecommitdiff
path: root/api_layers
diff options
context:
space:
mode:
authorChristian Kolset <christian.kolset@gmail.com>2026-04-21 22:58:30 -0600
committerChristian Kolset <christian.kolset@gmail.com>2026-04-21 22:58:30 -0600
commit4db119fb9c04428e7aef6f1a278c1639aa867baf (patch)
treee0e0645559637e593f1bbfb9d713256028668527 /api_layers
parent97e15f69b835af03b071c4eeb4547c65a02e6f80 (diff)
Major update to Output control
Including: - Control panel - Change status of simulation after devices creation. - Arduino serial conflict with multiple channels. - Claude instructions. - Update to arduino on board code.
Diffstat (limited to 'api_layers')
-rw-r--r--api_layers/__pycache__/__init__.cpython-312.pycbin322 -> 309 bytes
-rw-r--r--api_layers/__pycache__/arduino_layer.cpython-312.pycbin30786 -> 31077 bytes
-rw-r--r--api_layers/__pycache__/nidaqmx_layer.cpython-312.pycbin8512 -> 8492 bytes
-rw-r--r--api_layers/__pycache__/port_registry.cpython-312.pycbin0 -> 5972 bytes
-rw-r--r--api_layers/arduino_layer.py12
-rw-r--r--api_layers/port_registry.py124
6 files changed, 133 insertions, 3 deletions
diff --git a/api_layers/__pycache__/__init__.cpython-312.pyc b/api_layers/__pycache__/__init__.cpython-312.pyc
index 8aa354b..3c56ecd 100644
--- a/api_layers/__pycache__/__init__.cpython-312.pyc
+++ b/api_layers/__pycache__/__init__.cpython-312.pyc
Binary files differ
diff --git a/api_layers/__pycache__/arduino_layer.cpython-312.pyc b/api_layers/__pycache__/arduino_layer.cpython-312.pyc
index f6c3ff9..a0191ad 100644
--- a/api_layers/__pycache__/arduino_layer.cpython-312.pyc
+++ b/api_layers/__pycache__/arduino_layer.cpython-312.pyc
Binary files differ
diff --git a/api_layers/__pycache__/nidaqmx_layer.cpython-312.pyc b/api_layers/__pycache__/nidaqmx_layer.cpython-312.pyc
index de2c034..93a5ef2 100644
--- a/api_layers/__pycache__/nidaqmx_layer.cpython-312.pyc
+++ b/api_layers/__pycache__/nidaqmx_layer.cpython-312.pyc
Binary files differ
diff --git a/api_layers/__pycache__/port_registry.cpython-312.pyc b/api_layers/__pycache__/port_registry.cpython-312.pyc
new file mode 100644
index 0000000..44a52e8
--- /dev/null
+++ b/api_layers/__pycache__/port_registry.cpython-312.pyc
Binary files differ
diff --git a/api_layers/arduino_layer.py b/api_layers/arduino_layer.py
index fb09a16..fed54f9 100644
--- a/api_layers/arduino_layer.py
+++ b/api_layers/arduino_layer.py
@@ -551,7 +551,7 @@ const int DIGITAL_IN[] = {2, 3, 4};
const int DIGITAL_OUT[] = {5, 6, 7, 9, 10, 11}; // 9,10,11 are PWM-capable
const int N_ANALOG = 1; // ← set to how many analog pins you use
const int N_DIG_IN = 0; // ← set to how many digital inputs you use
-const int N_DIG_OUT = 0; // ← set to how many digital outputs you use
+const int N_DIG_OUT = 6; // ← set to how many digital outputs you use
const int SEND_INTERVAL = 50; // ms between data frames (50 = 20 Hz)
const long BAUD_RATE = 115200;
@@ -666,16 +666,22 @@ void sendData() {
void sendFrame() {
String out = "";
+ bool first = true;
// Analog inputs — converted to 0.0–5.0 V
for (int i = 0; i < N_ANALOG; i++) {
float v = analogRead(ANALOG_PINS[i]) * (5.0 / 1023.0);
- if (i > 0) out += ",";
+ if (!first) out += ",";
out += "A" + String(i) + ":" + String(v, 3);
+ first = false;
}
// Digital inputs (INPUT_PULLUP — invert so pressed=1)
+ // These share the same output line as analog — one line per frame.
for (int i = 0; i < N_DIG_IN; i++) {
- out += ",D" + String(DIGITAL_IN[i]) + ":" + String(!digitalRead(DIGITAL_IN[i]));
+ if (!first) out += ",";
+ out += "D" + String(DIGITAL_IN[i]) + ":" + String(!digitalRead(DIGITAL_IN[i]));
+ first = false;
}
Serial.println(out);
+ // Example combined output: A0:3.142,D2:0,D3:1
}
"""
diff --git a/api_layers/port_registry.py b/api_layers/port_registry.py
new file mode 100644
index 0000000..e3c85fd
--- /dev/null
+++ b/api_layers/port_registry.py
@@ -0,0 +1,124 @@
+"""
+api_layers/port_registry.py
+
+PortRegistry — ensures one ArduinoLayer per physical serial port.
+
+Problem: if both an AnalogInputDevice and a DigitalIODevice are
+configured for the same Arduino (/dev/ttyUSB0), they would each
+create their own ArduinoLayer and both try to open the same serial
+port → "device reports readiness to read but returned no data".
+
+Solution: all devices call PortRegistry.get_layer(port, baud) instead
+of constructing ArduinoLayer directly. The registry returns an
+existing layer if one is already open on that port, or creates a new
+one. All reads/writes go through the single shared layer.
+
+The registry also merges analog_pins across devices so the layer
+caches values for all channels on the Arduino.
+
+Usage (handled automatically by AnalogInputDevice and DigitalIODevice):
+
+ from api_layers.port_registry import port_registry
+ layer = port_registry.get_layer("/dev/ttyUSB0", 115200, simulate=False)
+ layer.connect_if_needed()
+"""
+
+from __future__ import annotations
+import threading
+from typing import Dict, List, Optional, Tuple
+
+
+class PortRegistry:
+ """
+ Global registry of shared ArduinoLayer instances.
+ Thread-safe singleton accessed via the module-level `port_registry`.
+ """
+
+ def __init__(self):
+ self._lock: threading.Lock = threading.Lock()
+ self._layers: Dict[Tuple[str, int], object] = {} # (port, baud) → ArduinoLayer
+ self._ref_counts: Dict[Tuple[str, int], int] = {}
+
+ def get_layer(self, port: str, baud: int,
+ simulate: bool = False,
+ extra_pins: List[str] = None) -> "ArduinoLayer": # type: ignore
+ """
+ Return a shared ArduinoLayer for (port, baud).
+ Creates one if it doesn't exist yet.
+ If simulate=True, a simulation layer is returned (always separate,
+ so simulated devices don't share state with each other).
+ """
+ from api_layers.arduino_layer import ArduinoLayer
+
+ if simulate:
+ # Simulated devices get their own independent layer
+ layer = ArduinoLayer(port=port, baud=baud, simulate=True)
+ if extra_pins:
+ layer.analog_pins = list(extra_pins)
+ return layer
+
+ key = (port, baud)
+ with self._lock:
+ if key not in self._layers:
+ layer = ArduinoLayer(port=port, baud=baud, simulate=False)
+ if extra_pins:
+ layer.analog_pins = list(extra_pins)
+ self._layers[key] = layer
+ self._ref_counts[key] = 0
+ else:
+ layer = self._layers[key]
+ # Merge any new pin names so the shared cache covers them
+ if extra_pins:
+ existing = set(layer.analog_pins)
+ for p in extra_pins:
+ if p not in existing:
+ layer.analog_pins.append(p)
+ self._ref_counts[key] += 1
+ return layer
+
+ def release(self, port: str, baud: int) -> None:
+ """
+ Decrement ref count for a port. Disconnects and removes the
+ layer when no devices are using it any more.
+ """
+ key = (port, baud)
+ with self._lock:
+ if key not in self._ref_counts:
+ return
+ self._ref_counts[key] -= 1
+ if self._ref_counts[key] <= 0:
+ layer = self._layers.pop(key, None)
+ self._ref_counts.pop(key, None)
+ if layer:
+ try:
+ layer.disconnect()
+ except Exception:
+ pass
+
+ def connect_all(self) -> None:
+ """Connect any layers that haven't been connected yet."""
+ with self._lock:
+ layers = list(self._layers.values())
+ for layer in layers:
+ if not layer.is_connected:
+ layer.connect()
+
+ def all_layers(self):
+ with self._lock:
+ return list(self._layers.values())
+
+ def clear(self) -> None:
+ """Disconnect and remove all managed layers."""
+ with self._lock:
+ layers = list(self._layers.values())
+ self._layers.clear()
+ self._ref_counts.clear()
+ for layer in layers:
+ try:
+ layer.disconnect()
+ except Exception:
+ pass
+
+
+# Module-level singleton
+port_registry = PortRegistry()