Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
72ada2f
Merge branch 'feature/event-driven-comunication' into feat/multiple-a…
markoceri Apr 7, 2026
c836e7a
Merge branch 'feature/websocket-real-time-events' into feat/multiple-…
markoceri Apr 7, 2026
f9dbf2a
style: improve string formatting in logging messages across multiple …
markoceri Apr 7, 2026
f8565f8
feat: add FORECAST_PROVIDER to ConfigurationUpdatedEventType enum
markoceri Apr 7, 2026
aa406f2
feat: implement miner aggregate root and feature management, add moni…
markoceri Apr 7, 2026
8069e6a
fix: update import path for Miner to use aggregate_roots module
markoceri Apr 7, 2026
c473aa8
refactor: remove unused model update logic in OptimizationService
markoceri Apr 7, 2026
e3f5d77
feat: enhance miner feature management by integrating feature ports a…
markoceri Apr 7, 2026
4633456
feat: add miner_features table and update repositories to manage mine…
markoceri Apr 7, 2026
00db188
feat: update miner controllers to implement multiple feature ports fo…
markoceri Apr 7, 2026
30741bf
feat: enhance miner controller management by adding unlink API functi…
markoceri Apr 7, 2026
427f427
feat: refactor miner controller handling in CLI commands to support l…
markoceri Apr 7, 2026
4c37f7f
feat: add adapter_service parameter to configure_dependencies for imp…
markoceri Apr 7, 2026
1dcc17f
chore: add missing import for sqlalchemy in migration script
markoceri Apr 7, 2026
63d5264
feat: add API endpoint to retrieve miner features and update controll…
markoceri Apr 8, 2026
5d8b398
Merge remote-tracking branch 'origin/dev' into feat/multiple-miner-co…
markoceri Apr 8, 2026
c269a01
Merge branch 'fix/unable-update-external-service-frontend-32' into fe…
markoceri Apr 8, 2026
ba362eb
Merge branch 'dev' into feat/multiple-miner-controllers
markoceri Apr 8, 2026
51d3550
Merge branch 'dev' into feat/multiple-miner-controllers
markoceri Apr 8, 2026
da6d93b
feat: update miner controller methods to support asynchronous operations
markoceri Apr 8, 2026
fcb18a5
feat: update miner feature port methods to support asynchronous opera…
markoceri Apr 8, 2026
831c2d9
feat: add chip temperature and internal fan speed monitoring to miner…
markoceri Apr 8, 2026
7a602e7
feat: update temperature retrieval method in PyASICMinerController to…
markoceri Apr 8, 2026
da8c3cc
feat: add MaxPowerDetectionPort and MaxHashrateDetectionPort for enha…
markoceri Apr 8, 2026
c915ae4
feat: implement DeviceInfoPort and MinerInfo value object for enhance…
markoceri Apr 9, 2026
7023e6c
feat: add data integrity validation and cleanup for unknown miner fea…
markoceri Apr 9, 2026
413670a
feat: update internal fan speed methods to return a list of FanSpeed …
markoceri Apr 9, 2026
1db53c5
feat: add hashboard, chip, and fan count to MinerInfo value object an…
markoceri Apr 9, 2026
50f9cf5
feat: add schemas for temperature, fan speed, voltage, and frequency …
markoceri Apr 9, 2026
c2050d8
feat: update pyasic dependency to version 0.78.10 in pyproject.toml a…
markoceri Apr 9, 2026
fb5daf1
feat: implement hasboard monitoring and update related schemas, ports…
markoceri Apr 9, 2026
742e16a
feat: add sync_miner_features method to AdapterService for feature re…
markoceri Apr 9, 2026
643fa18
feat: add operational monitoring port and related methods for blocks …
markoceri Apr 10, 2026
2f81edf
feat: enhance miner info retrieval with hashboards and internal fan s…
markoceri Apr 10, 2026
833d576
feat: add enable, disable, and set priority methods for miner feature…
markoceri Apr 10, 2026
ec9c8ef
feat: update version to 0.1.0-rev2 and document new features and chan…
markoceri Apr 10, 2026
a589ab9
feat: add endpoint to retrieve miner device information and correspon…
markoceri Apr 10, 2026
82e0aaf
feat: add miner limits retrieval with max power and hash rate detection
markoceri Apr 12, 2026
0157e4a
feat: add firmware type to MinerInfo and related schemas for enhanced…
markoceri Apr 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,108 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.0-rev2]

### Added
- **Miner Aggregate Root** (`edge_mining/domain/miner/aggregate_roots.py`):
- Promotes `Miner` to a full aggregate root with feature management capabilities
- Feature CRUD: `add_feature()`, `remove_feature()`, `remove_features_by_controller()`
- Feature queries: `get_active_feature()`, `get_features_by_controller()`, `get_features_by_type()`, `get_controller_ids()`, `has_feature()`
- Feature configuration: `enable_feature()`, `disable_feature()`, `set_feature_priority()`

- **Miner Feature System** (`edge_mining/domain/miner/ports.py`):
- `MinerFeature` value object with identity based on `(feature_type, controller_id)` pair and configurable priority/enabled state
- `MinerFeatureType` enum with 17 values across 4 categories: monitoring (9), control (4), detection (3)
- `MinerFeaturePort` abstract base with MRO-based introspection via `get_supported_features()` class method
- **Monitoring Ports**: `HashrateMonitorPort`, `PowerMonitorPort`, `StatusMonitorPort`, `HashboardMonitorPort`, `InletTemperatureMonitorPort`, `OutletTemperatureMonitorPort`, `InternalFanSpeedMonitorPort`, `ExternalFanSpeedMonitorPort`, `OperationalMonitorPort`
- **Control Ports**: `MiningControlPort`, `PowerControlPort`, `InternalFanControlPort`, `ExternalFanControlPort`
- **Detection Ports**: `MaxPowerDetectionPort`, `MaxHashrateDetectionPort`, `DeviceInfoPort`

- **New Value Objects** (`edge_mining/domain/miner/value_objects.py`):
- Measurement types: `Temperature`, `FanSpeed`, `Voltage`, `Frequency` frozen dataclasses with value and unit
- `MinerInfo`: Device information with model, serial number, firmware type (Stock, BOS+, VNish, etc.), firmware version, MAC address, hostname, hashboard/chip/fan count
- `MinerLimit`: Miner limits with optional `max_power` (Watts) and `max_hash_rate` (HashRate)
- `HashboardSnapshot`: Per-board metrics (chip/board temperature, voltage, frequency, hash rate, nominal hash rate, hash rate error)
- Extended `MinerStateSnapshot` with: `inlet_temperature`, `outlet_temperature`, `internal_fan_speed` (list), `hashboards` (list), and convenience properties (`max_chip_temperature`, `avg_board_temperature`, etc.)

- **New Pydantic Schemas** (`edge_mining/adapters/domain/miner/schemas.py`):
- `TemperatureSchema`, `FanSpeedSchema`, `VoltageSchema`, `FrequencySchema` with unit validation
- `HashboardSnapshotSchema`, `MinerInfoSchema`, `MinerFeatureSchema`, `FeaturePrioritySchema`
- `MinerLimitSchema` with validation, `from_model()`/`to_model()` conversion for `MinerLimit` value object

- **`miner_features` Database Table** (`edge_mining/adapters/domain/miner/tables.py`):
- Columns: `id`, `miner_id` (FK), `controller_id` (FK), `feature_type`, `priority` (default 50), `enabled` (default True)
- Helper functions: `load_features_for_miner()`, `save_features_for_miner()`

- **New API Endpoints** (`edge_mining/adapters/domain/miner/fast_api/router.py`):
- `GET /miners/{miner_id}/info` — Get miner device information (model, serial number, firmware version, etc.)
- `GET /miners/{miner_id}/limits` — Get miner limits (max power, max hash rate) via `MaxPowerDetectionPort` and `MaxHashrateDetectionPort`
- `GET /miners/{miner_id}/features` — List miner features
- `POST /miners/{miner_id}/features/{controller_id}/{feature_type}/enable` — Enable a feature
- `POST /miners/{miner_id}/features/{controller_id}/{feature_type}/disable` — Disable a feature
- `PUT /miners/{miner_id}/features/{controller_id}/{feature_type}/priority` — Set feature priority
- `POST /miners/{miner_id}/link-controller/{controller_id}` — Link controller and auto-create features
- `POST /miners/{miner_id}/unlink-controller` — Remove all features from a controller

### Changed
- **Full Async Refactoring**:
- All `MinerActionServiceInterface` methods are now `async`: `start_miner()`, `stop_miner()`, `get_miner_status()`, `get_miner_consumption()`, `get_miner_hashrate()`, `get_miner_info()`, `sync_all_miners()`
- All `ConfigurationServiceInterface` miner management methods are now `async`: `add_miner()`, `update_miner()`, `remove_miner()`, `activate_miner()`, `deactivate_miner()`, `add_miner_controller()`, `update_miner_controller()`, `remove_miner_controller()`
- Miner controller adapters, energy providers, forecast providers, and external services refactored to support asynchronous operations
- Miner feature port methods updated to `async`
- `OptimizationService` methods `get_decisional_context()` and `test_rules()` are now `async`

- **`AdapterService`** (`edge_mining/application/services/adapter_service.py`):
- New methods: `get_miner_controller_adapter()`, `get_miner_feature_port()` for dynamic port-based adapter resolution
- `sync_miner_features()` method for reconciling stored vs. actual controller features
- Async initialization of external services with instance caching

- **`ConfigurationService`** (`edge_mining/application/services/configuration_service.py`):
- New methods: `set_miner_controller()`, `unlink_controller_from_miner()`, `unlink_miner_controller()`, `enable_miner_feature()`, `disable_miner_feature()`, `set_miner_feature_priority()`

- **`MinerActionService`** (`edge_mining/application/services/miner_action_service.py`):
- Uses `AdapterService` to dynamically resolve feature ports instead of direct controller access
- New `get_miner_info()` method using `DeviceInfoPort`
- New `get_miner_limits()` method using `MaxPowerDetectionPort` and `MaxHashrateDetectionPort`

- **CLI Commands** (`edge_mining/adapters/domain/miner/cli/commands.py`):
- Refactored miner controller handling to support linking after creation
- New `unlink_controller_from_miner()` command
- Uses `run_async_func()` for async service calls

- **Dependencies**: Updated `pyasic` to version `0.78.10`

### Fixed
- Fixed data integrity validation and cleanup for unknown miner features in database

## [0.1.0-rev1]

### Added
- **Event-Driven Architecture**:
- `InMemoryEventBus` (`edge_mining/adapters/infrastructure/event_bus/in_memory_event_bus.py`): Dual delivery mode event bus supporting blocking and fire-and-forget handlers via `asyncio.create_task()`
- `ConfigurationUpdatedEvent` (`edge_mining/application/events/configuration_events.py`): Application-level event for cache invalidation with `ConfigurationUpdatedEventType` and `ConfigurationAction` enums
- `MinerStateChangedEvent` (`edge_mining/domain/miner/events.py`): Emitted on miner start/stop with old and new status
- `EnergyStateSnapshotUpdatedEvent` (`edge_mining/domain/energy/events.py`): Emitted when energy state is read
- `RuleEngagedEvent` (`edge_mining/domain/optimization_unit/events.py`): Emitted when a policy rule produces a mining decision
- `DecisionalContextUpdatedEvent` (`edge_mining/domain/policy/events.py`): Emitted when decisional context is composed

- **WebSocket Infrastructure**:
- `WebSocketManager` (`edge_mining/adapters/infrastructure/websocket/manager.py`): Real-time event broadcasting to connected clients with wildcard topic subscriptions (e.g. `energy.*`, `miner.state`)
- `WebSocketEventHandler` base class and 5 domain handlers: `MinerWebSocketHandler`, `EnergyWebSocketHandler`, `PolicyWebSocketHandler`, `OptimizationUnitWebSocketHandler`, `ConfigurationWebSocketHandler`
- Available topics: `config.updated`, `energy.state`, `miner.state`, `policy.context`, `rule.engaged`
- `WebSocketMessage` NamedTuple and `WebSocketEventRegistration` dataclass for type-safe event routing

- **Testing**:
- Unit tests for all 5 domain events (`tests/unit/application/events/`)
- Unit tests for `DomainEvent` base class (`tests/unit/domain/test_events.py`)
- Unit tests for `WebSocketManager` (`tests/unit/adapters/infrastructure/websocket/test_websocket_manager.py`): lifecycle, subscriptions, wildcard matching, broadcast
- Unit tests for `InMemoryEventBus` (`tests/unit/adapters/infrastructure/test_in_memory_event_bus.py`)
- Integration tests for configuration event flow (`tests/unit/application/services/test_configuration_event_flow.py`)

- **Documentation**:
- `docs/architecture/event_bus.md` — Event Bus architecture design
- `docs/WEBSOCKET.md` — WebSocket client guide and architecture

- **`MinerStateSnapshot` Value Object** (`edge_mining/domain/miner/value_objects.py`):
- New frozen dataclass representing the runtime operational state of a miner
- Fields: `status` (MinerStatus), `hash_rate` (Optional[HashRate]), `power_consumption` (Optional[Watts])
Expand Down Expand Up @@ -49,10 +148,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `GET /miners/{miner_id}/status`: Now returns `MinerStateSnapshotSchema` instead of `MinerSchema`
- `GET /miner-controllers/{controller_id}/miner-details`: Now returns `MinerStateSnapshotSchema`

- **`AdapterService`** (`edge_mining/application/services/adapter_service.py`):
- Event subscription for `ConfigurationUpdatedEvent` to invalidate service caches

- **`ConfigurationService`** (`edge_mining/application/services/configuration_service.py`):
- Publishes `ConfigurationUpdatedEvent` on all configuration changes

- **`MinerActionService`** (`edge_mining/application/services/miner_action_service.py`):
- `start_miner()`/`stop_miner()` now publish `MinerStateChangedEvent` via event bus

- **`bootstrap.py`**: Instantiates `InMemoryEventBus` and injects it into all services; adds `init_websocket_dependencies()` call at startup; runs `sync_all_miners()` on application start

- **CLI Commands** (`edge_mining/adapters/domain/miner/cli/commands.py`):
- Removed status display from `list_miners()` and `print_miner_details()`

- **Application Interfaces** (`edge_mining/application/interfaces.py`):
- New `get_miner_limits()` abstract method in `MinerActionServiceInterface`
- `get_miner_status()`: Return type changed from `Optional[MinerStatus]` to `Optional[MinerStateSnapshot]`
- `get_miner_details_from_controller()`: Return type changed from `Optional[Miner]` to `Optional[MinerStateSnapshot]`
- `add_miner()`: Removed `status` parameter
Expand All @@ -72,8 +183,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Removed columns `status`, `hash_rate`, `power_consumption` from `miners` table definition
- Removed `MinerStatusType()` reference (custom type no longer exists)

- **WebSocket event handlers**: Refactored to use topic strings and return `WebSocketMessage` payloads; `broadcast_message()` updated to use `WebSocketMessage` type

- **`FORECAST_PROVIDER`** added to `ConfigurationUpdatedEventType` enum

### Fixed
- Fixed unterminated string literal in `MinerActionServiceInterface` docstring
- Fixed connection error handling for Home Assistant API with client reset and improved logging
- Fixed external service update logic to handle missing configuration classes
- Fixed database directory creation for SQLite connections when using SQLAlchemy persistence adapter
- Fixed critical error handling replaced with warnings for missing energy values in Home Assistant monitors
- Fixed missing import for sqlalchemy in migration script

## [0.1.0]

Expand Down
64 changes: 64 additions & 0 deletions alembic/versions/a1b2c3d4e5f6_add_miner_features_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""Add miner_features table and remove controller_id from miners

Revision ID: a1b2c3d4e5f6
Revises: 4e55fe6113c7
Create Date: 2026-01-24 10:00:00.000000

"""

from typing import Sequence, Union

import sqlalchemy as sa

from alembic import op

# revision identifiers, used by Alembic.
revision: str = "a1b2c3d4e5f6"
down_revision: Union[str, Sequence[str], None] = "4e55fe6113c7"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
"""Add miner_features table and migrate controller_id data."""
# Create miner_features table
op.create_table(
"miner_features",
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("miner_id", sa.String(), nullable=False),
sa.Column("controller_id", sa.String(), nullable=False),
sa.Column("feature_type", sa.String(), nullable=False),
sa.Column("priority", sa.Integer(), nullable=False, server_default="50"),
sa.Column("enabled", sa.Boolean(), nullable=False, server_default="1"),
sa.ForeignKeyConstraint(
["miner_id"],
["miners.id"],
),
sa.ForeignKeyConstraint(
["controller_id"],
["miner_controllers.id"],
),
sa.PrimaryKeyConstraint("id"),
)

# Remove controller_id column from miners table
# Note: SQLite doesn't support DROP COLUMN directly in older versions,
# but Alembic handles this with batch mode on SQLite.
with op.batch_alter_table("miners") as batch_op:
batch_op.drop_column("controller_id")


def downgrade() -> None:
"""Remove miner_features table and restore controller_id on miners."""
# Re-add controller_id to miners
with op.batch_alter_table("miners") as batch_op:
batch_op.add_column(sa.Column("controller_id", sa.String(), nullable=True))
batch_op.create_foreign_key(
"fk_miners_controller_id",
"miner_controllers",
["controller_id"],
["id"],
)

# Drop miner_features table
op.drop_table("miner_features")
2 changes: 1 addition & 1 deletion edge_mining/__version__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Edge Mining version information."""

__version__ = "0.1.0-rev1"
__version__ = "0.1.0-rev2"
__version_info__ = tuple(str(x) for x in __version__.split("."))
Loading