Skip to content

Feature-based architecture for miners — multiple controllers per miner#80

Open
markoceri wants to merge 39 commits intodevfrom
feat/multiple-miner-controllers
Open

Feature-based architecture for miners — multiple controllers per miner#80
markoceri wants to merge 39 commits intodevfrom
feat/multiple-miner-controllers

Conversation

@markoceri
Copy link
Copy Markdown
Collaborator

@markoceri markoceri commented Apr 8, 2026

Summary

Replaces the single Miner.controller_id design with a feature-based architecture where each miner aggregates multiple MinerFeature value objects, each provided by a different controller.

This PR closes #79 #56 #54

Architecture

Before

Miner.controller_id → 1 MinerController → 1 adapter implementing all MinerControlPort methods

After

Miner (Aggregate Root)
  ├── MinerFeature(HASHRATE_MONITORING,  controller=pyasic,     priority=50)
  ├── MinerFeature(POWER_MONITORING,     controller=smart_plug,  priority=80)  ← more accurate
  ├── MinerFeature(POWER_MONITORING,     controller=pyasic,      priority=40)  ← fallback
  ├── MinerFeature(STATUS_MONITORING,    controller=pyasic,      priority=50)
  ├── MinerFeature(MINING_CONTROL,       controller=pyasic,      priority=50)
  ├── MinerFeature(POWER_CONTROL,        controller=smart_plug,  priority=80)
  └── ...

The system resolves the best provider for each capability at runtime: filter by type → filter enabled → sort by priority (descending) → take first.

Design principles applied

  • Interface Segregation: The monolithic MinerControlPort (6 methods) is split into 16 granular ports, one per feature type. Adapters implement only the ports they support.
  • DDD Aggregate Root: Miner becomes a proper aggregate root managing the MinerFeature collection with invariant enforcement (uniqueness per type+controller, priority range 1–100).
  • MRO-based feature discovery: Each port carries a feature_type: ClassVar[MinerFeatureType]. When a controller is associated with a miner, the system walks the adapter class's MRO to discover which features it supports — no static maps needed.
  • Auto-binding: Calling set_miner_controller(controller_id, miner_id) automatically creates MinerFeature entries for every feature the controller's adapter supports.

Changes

Domain layer

File Change
domain/miner/common.py Added MinerFeatureType enum (16 values across monitoring and control categories)
domain/miner/value_objects.py Added MinerFeature frozen dataclass (feature_type, controller_id, priority, enabled). Added Temperature, FanSpeed, Voltage, Frequency VOs. Enriched MinerStateSnapshot with 8 new optional fields
domain/miner/ports.py Replaced MinerControlPort with MinerFeaturePort base + 16 concrete feature ports (HashrateMonitorPort, PowerMonitorPort, MiningControlPort, etc.)
domain/miner/aggregate_roots.py New file. Miner moved here, now derives from AggregateRoot. Manages features collection with 11 methods (add/remove/enable/disable/set_priority/get_active_feature/etc.)
domain/miner/entities.py Miner removed. Only MinerController remains

Adapter layer

File Change
adapters/.../pyasic.py Implements 13 feature ports (hashrate, power, status, 4 temperatures, internal fan, voltage, frequency, mining control, model detection)
adapters/.../generic_socket_home_assistant_api.py Implements 3 feature ports (power monitoring, status monitoring, power control)
adapters/.../dummy.py Implements 5 feature ports (hashrate, power, status, mining control, model detection)

Persistence layer

File Change
adapters/.../tables.py New miner_features table (id, miner_id, controller_id, feature_type, priority, enabled). Removed controller_id from miners table. Helper functions for loading/saving features outside ORM (frozen VO cannot be imperatively mapped)
adapters/.../repositories.py All 3 implementations (InMemory, SQLite, SQLAlchemy) updated. get_by_controller_id now queries via features. CRUD methods load/save features alongside miner
alembic/versions/a1b2c3d4e5f6_... Migration: creates miner_features table, drops controller_id from miners

Application layer

File Change
application/interfaces.py AdapterServiceInterface: new get_miner_feature_port(miner, feature_type) and get_miner_controller_adapter(miner, controller_id). ConfigurationServiceInterface: removed controller_id from add_miner/update_miner, added unlink_controller_from_miner
application/services/adapter_service.py New get_miner_feature_port() resolves active feature → controller → adapter → verifies support via MRO
application/services/configuration_service.py set_miner_controller auto-creates features via MRO discovery. New unlink_controller_from_miner. Injected adapter_service dependency
application/services/optimization_service.py Queries individual feature ports instead of monolithic controller
application/services/miner_action_service.py Start/stop uses MINING_CONTROL with POWER_CONTROL fallback. Each metric queried via its own feature port

API layer

File Change
adapters/.../schemas.py New MinerFeatureSchema. MinerSchema exposes features list and computed controller_ids instead of controller_id. Removed controller_id from create/update schemas
adapters/.../router.py Updated set-controller endpoint. New POST /miners/{id}/unlink-controller endpoint
adapters/.../cli/commands.py All miner.controller_id references replaced with feature-based equivalents

Breaking changes

  • Miner.controller_id field removed. Replaced by Miner.features: List[MinerFeature].
  • MinerControlPort interface removed. Replaced by 16 granular feature ports.
  • add_miner and update_miner no longer accept controller_id. Use set_miner_controller to associate controllers.
  • API response shape for miners changed: controller_id field replaced by features array and controller_ids array.
  • Database migration required (new miner_features table, controller_id column dropped from miners).

Testing

All 259 existing tests pass. The migration has been validated against the Alembic test suite (9 tests).

markoceri added 16 commits April 7, 2026 18:14
…nd updating adapter service methods into application layer
@markoceri markoceri modified the milestones: 0.2.0, 0.1.0 Apr 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Miner can only have one controller — limits real-world setups Add miner hashboard management Add temperature information to the data miner model

1 participant