Skip to content

Commit e460abc

Browse files
MarkoVcodeclaude
andcommitted
Add DriverBase abstract class to eliminate code duplication
Created abstract base class that all drivers inherit from, providing automatic transport management, built-in caching, USB TMC detection, and common helper methods. Migrated all 6 drivers (TenmaPSU, OWONXDM, RigolDHO800, OWONSPM, OwonDGE, OwonOEL) to new architecture, eliminating ~168 lines of duplicate code while maintaining full backward compatibility. Drivers now require no constructor and only implement device-specific methods. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent e9fec53 commit e460abc

File tree

12 files changed

+1171
-208
lines changed

12 files changed

+1171
-208
lines changed

CLAUDE.md

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -338,33 +338,41 @@ BenchMesh stores all user data in `~/.benchmesh/` to ensure configurations, Node
338338
## Adding a New Driver
339339

340340
1. Create driver package: `benchmesh-serial-service/src/benchmesh_service/drivers/<driver_name>/`
341-
2. Create `driver.py` with a class exposing:
342-
- `query_identify()` → returns IDN string
343-
- `poll_status()` → returns status dict
344-
- Device-specific control methods following naming convention:
341+
2. Create `driver.py` with a class that **inherits from DriverBase**:
342+
- Import: `from ..base import DriverBase`
343+
- Inherit: `class MyDriver(DriverBase):`
344+
- **No `__init__` needed** - DriverBase handles transport and cache setup
345+
- Implement required abstract methods:
346+
- `query_identify()` → returns IDN string
347+
- `poll_status(channel: int)` → returns status dict
348+
- Add device-specific control methods following naming convention:
345349
- Read methods: `query_voltage()`, `query_current()`, `query_status()`, etc.
346350
- Write methods: `set_voltage()`, `set_current()`, `set_mode()`, etc.
347351
3. Create `manifest.json` defining models, classes, polling config, and EOL characters
348352
- **Optional**: Add `"enabled": false` at root level to hide work-in-progress drivers from configuration UI
349353
- Drivers default to enabled if flag is omitted
350354
- Example: `{"vendor": "OWON", "family": "DGE", "version": "1.0.0", "enabled": false, "models": {...}}`
351355
4. Update `drivers/classes.json` if adding new 3-letter class codes
352-
5. Add driver instantiation logic to `driver_factory.py` if needed
353-
6. Create tests in `tests/` using pytest and mock serial communication
356+
5. Create tests in `tests/` using pytest and mock serial communication
354357

355358
**Driver Naming Convention:**
356359
- **Query methods** (read): prefix with `query_` (e.g., `query_voltage`, `query_current`)
357360
- **Setter methods** (write): prefix with `set_` (e.g., `set_voltage`, `set_current`)
358361
- This enables the API's smart resolution: GET `/voltage` → `query_voltage()`, POST `/current/2.5` → `set_current()`
359362

360-
Driver should accept `transport: Transport` in constructor and use it for all communication. The Transport interface supports multiple physical transports (SerialTransport for RS232/USB-Serial, with USB TMC and TCP/IP support planned).
361-
362-
**Optional Caching:**
363-
Drivers can use `SimpleCache` to minimize redundant SCPI calls between polling and API requests:
364-
- Import: `from ...cache import SimpleCache`
365-
- Initialize in `__init__()`: `self.cache = SimpleCache()`
366-
- Use in `poll_status()`: Check `self.cache.get(key)` before querying, call `self.cache.set(key, value)` after query
367-
- Invalidate in `set_*` methods: Call `self.cache.invalidate(key)` when device state changes
363+
**DriverBase Inheritance:**
364+
All drivers inherit from `DriverBase` which provides:
365+
- **Automatic transport management**: Access via `self.t` (no constructor needed)
366+
- **Built-in caching**: Use `self.cache` (SimpleCache instance automatically created)
367+
- **Common methods**: `close()`, `is_connected()`, `set_reset()` (with USB TMC auto-detection)
368+
- **Transport delegation**: `write()`, `read()`, `write_line()`, `read_until_reol()`
369+
- **Helper methods**: `_parse_numeric()`, `_clean_response()`, `_is_usb_tmc()`
370+
371+
**Caching (Built-in):**
372+
Every driver automatically has `self.cache` (SimpleCache instance) available:
373+
- Use in `poll_status()`: `value = self.cache.get_or_set("key", self.query_method, channel)`
374+
- Invalidate in `set_*` methods: `self.cache.invalidate("key")` when device state changes
375+
- No initialization needed - handled by DriverBase
368376
- See `drivers/owon_oel/driver.py` for reference implementation
369377
- See `CACHE_DESIGN.md` for complete caching guide and migration steps
370378
- Typical performance gain: 3x speedup in polling (owon_oel measured)
@@ -376,6 +384,11 @@ Drivers can use `SimpleCache` to minimize redundant SCPI calls between polling a
376384
- `driver_factory.py`: Instantiates driver classes from string names and device configs
377385
- `poll_worker.py`: DeviceWorker runs per-device polling loop in dedicated thread
378386
- `registry.py`: DeviceRegistry thread-safe storage for device IDN and status
387+
- `drivers/base.py`: DriverBase abstract class - all drivers inherit from this
388+
- Provides automatic transport management, built-in caching, USB TMC detection
389+
- Common methods: `close()`, `is_connected()`, `set_reset()`, transport delegation
390+
- Helper methods: `_parse_numeric()`, `_clean_response()`, `_is_usb_tmc()`
391+
- Abstract methods drivers must implement: `query_identify()`, `poll_status()`
379392
- `transport/`: Abstract transport layer with multiple implementations
380393
- `transport/base.py`: Transport ABC defining interface for all transports
381394
- `transport/serial.py`: SerialTransport for RS232/USB-Serial with pyserial

0 commit comments

Comments
 (0)