EEBUS¶
Handwritten Documentation¶
EEBUS¶
Link to the module’s reference.
This document describes the EVerest EEBUS module. This module acts as a bridge between the EVerest framework and an external EEBUS gRPC service. It implements the “Limitation of Power consumption” (LPC) use case.
Architecture¶
Below is a diagram showing the architecture of the EEBUS module and its interaction with other components.
graph TD
subgraph EVerest
energy_manager["Energy Manager<br><div style='font-size: 80%;'>EVerest Module</div><div style='font-size: 70%;'>Manages energy distribution</div>"]
eebus_module["EEBUS Module<br><div style='font-size: 80%;'>EVerest C++ Module</div><div style='font-size: 70%;'>Bridge to EEBUS service for LPC</div>"]
end
subgraph External Systems
eebus_grpc_api["eebus-grpc-api<br><div style='font-size: 80%;'>External Go binary, gRPC server/client</div>"]
eebus_service["EEBUS Service (HEMS)<br><div style='font-size: 80%;'>External System</div><div style='font-size: 70%;'>Implements EEBUS standard, e.g. LPC</div>"]
end
eebus_module -- "Publishes ExternalLimits<br><div style='font-size: 70%;'>everest-interface</div>" --> energy_manager
eebus_module -- "Connects and receives events<br><div style='font-size: 70%;'>gRPC</div>" --> eebus_grpc_api
eebus_grpc_api -- "Communicates<br><div style='font-size: 70%;'>EEBUS (SHIP)</div>" --> eebus_service
How it works¶
The module’s main class is EEBUS. Its init() method orchestrates the setup.
Configuration Validation: It starts by using
ConfigValidatorto check the module’s configuration provided in the EVerestconfig.yaml. This includes validating ports, paths to binaries and certificates, and other parameters.gRPC Binary Management: If
manage_eebus_grpc_api_binaryis enabled in the config, the module starts the externaleebus_grpc_apiGo binary in a separate thread. This binary acts as a gRPC server for the EVerest module and a client to the actual EEBUS service (e.g., a HEMS).Connection Handling: The
EebusConnectionHandlerclass is responsible for all gRPC communication. It connects to theeebus_grpc_apiservice, sends the device configuration (vendor, brand, model, etc.), and registers the “Limitation of Power Consumption” (LPC) use case.Use Case Logic: For the LPC use case, an
LpcUseCaseHandleris created. This class implements the LPC state machine as defined by the EEBUS specification.Startup Limit:
LpcUseCaseHandler::start()immediately applies the failsafe consumption limit per [LPC-901/1]. The CS shall already be operating under the failsafe limit while in Init state, before any HEMS communication has taken place.Event Handling: The main module’s event loop (m_event_handler) periodically calls the sync() method of the EebusConnectionHandler. This sync() method, in turn, runs the EebusConnectionHandler’s internal event loop (m_handler) once. This internal loop is responsible for handling all subsequent events.
Receiving gRPC Events: A UseCaseEventReader runs in the background listening for incoming events from the gRPC service. When an event is received, it invokes a callback that posts an action to the EebusConnectionHandler’s internal event loop (m_handler).
State Machine and Limit Calculation: The m_handler event loop executes the queued action, which calls the LpcUseCaseHandler to handle the event. The handler processes the event, runs its internal state machine, and determines the current power limit. The state machine is also periodically triggered by a timer within this same internal event loop.
Publishing Limits: The calculated limits are then translated into an EVerest
ExternalLimitsschedule using thelpc::translate_to_external_limitsfunction. This schedule is published to theEnergyManager(or another connected module) via theeebus_energy_sinkrequired interface.
State Machine Diagram¶
The following diagram shows the state machine of the LpcUseCaseHandler, which is responsible for the “Limitation of Power Consumption” (LPC) logic.
stateDiagram-v2
[*] --> Init
Init: Init
UnlimitedControlled: Unlimited / Controlled
Limited: Limited
Failsafe: Failsafe
UnlimitedAutonomous: Unlimited / Autonomous
Init --> Limited : on DataUpdateLimit (active) [LPC-904]
Init --> UnlimitedControlled : on DataUpdateLimit (not active) [LPC-902/905]
Init --> UnlimitedAutonomous : on 120s timeout, no limit received [LPC-906]
Limited --> UnlimitedControlled : on DataUpdateLimit (not active) or Limit expired [LPC-908/909]
Limited --> Failsafe : on Heartbeat Timeout 120s [LPC-912]
UnlimitedControlled --> Limited : on DataUpdateLimit (active) [LPC-910]
UnlimitedControlled --> Failsafe : on Heartbeat Timeout 120s [LPC-911]
UnlimitedAutonomous --> Limited : on DataUpdateLimit (active) [LPC-919]
UnlimitedAutonomous --> UnlimitedControlled : on DataUpdateLimit (not active) [LPC-918/920]
Failsafe --> Limited : on Heartbeat restored AND new active limit [LPC-916/919]
Failsafe --> UnlimitedControlled : on Heartbeat restored AND new inactive limit [LPC-918/920]
Failsafe --> UnlimitedAutonomous : on Failsafe Duration Minimum expires [LPC-922]
Failsafe --> UnlimitedAutonomous : on 120s after first post-Failsafe heartbeat, no new limit [LPC-921]
The handler processes the following events:
DataUpdateHeartbeat: A heartbeat from the EEBUS service. If it’s missing for 120 seconds fromLimitedorUnlimited/controlledstates, the handler entersFailsafestate [LPC-911/912].Unlimited/autonomoushas no heartbeat timeout — it can only be exited by receiving a new limit [LPC-918/919/920].DataUpdateLimit: A new power limit from the EEBUS service.DataUpdateFailsafeDurationMinimum: Update of the minimum failsafe duration (default 2 hours). When Failsafe is entered, this duration must expire before [LPC-922] exit is possible.DataUpdateFailsafeConsumptionActivePowerLimit: Update of the failsafe power limit value applied inInitandFailsafestates.WriteApprovalRequired: The handler needs to approve pending writes from the EEBUS service.UseCaseSupportUpdate: The EG’s use case support changed (e.g. it reconnected while the gRPC channel stayed alive). The handler re-pushes the LPC configuration to the sidecar and re-subscribes to the heartbeat.
Failsafe exit conditions are independent per the spec:
[LPC-922]: The Failsafe Duration Minimum expires — the CS MAY exit to
Unlimited/autonomousregardless of heartbeat state.[LPC-921]: A heartbeat is received after Failsafe entry, but no new limit arrives within 120 s of that first heartbeat — the CS MAY exit to
Unlimited/autonomous.[LPC-916]: A heartbeat is received and a new limit was already written after Failsafe entry — the CS transitions to
Limited(active limit) orUnlimited/controlled(inactive limit).
Note on stale limits: When entering Failsafe, the handler discards the previously received limit. After exiting Failsafe, the HEMS must explicitly resend a limit to drive transitions out of Unlimited/autonomous.
Based on its state and the received limits, the module publishes ExternalLimits to the eebus_energy_sink, which is typically connected to an EnergyManager module.
Code Flow Diagram¶
This sequence diagram illustrates the code flow when an event is received from the EEBUS service.
sequenceDiagram
participant EEBUS
participant EebusConnectionHandler
participant LpcUseCaseHandler
participant UseCaseEventReader
participant GrpcService as gRPC Service
EEBUS ->> EebusConnectionHandler: create()
activate EebusConnectionHandler
EebusConnectionHandler ->> EebusConnectionHandler: initialize_connection()
deactivate EebusConnectionHandler
EEBUS ->> EebusConnectionHandler: add_use_case(LPC)
activate EebusConnectionHandler
EebusConnectionHandler ->> LpcUseCaseHandler: create()
EebusConnectionHandler ->> GrpcService: AddUseCase()
EebusConnectionHandler ->> LpcUseCaseHandler: set_stub()
EebusConnectionHandler ->> LpcUseCaseHandler: configure_use_case()
EebusConnectionHandler ->> UseCaseEventReader: create()
EebusConnectionHandler ->> UseCaseEventReader: start()
activate UseCaseEventReader
UseCaseEventReader ->> GrpcService: SubscribeUseCaseEvents()
deactivate UseCaseEventReader
deactivate EebusConnectionHandler
EEBUS ->> EebusConnectionHandler: done_adding_use_case()
activate EebusConnectionHandler
EebusConnectionHandler ->> EebusConnectionHandler: start_service()
EebusConnectionHandler ->> GrpcService: StartService()
EebusConnectionHandler ->> LpcUseCaseHandler: start()
deactivate EebusConnectionHandler
Note over GrpcService, EebusConnectionHandler: event arrives
GrpcService ->> UseCaseEventReader: OnReadDone(event)
activate UseCaseEventReader
UseCaseEventReader ->> EebusConnectionHandler: event_callback(event)
deactivate UseCaseEventReader
activate EebusConnectionHandler
EebusConnectionHandler ->> EebusConnectionHandler: m_handler.add_action()
Note right of EebusConnectionHandler: Event is queued in the event loop
Note over EebusConnectionHandler, LpcUseCaseHandler: event loop runs
EebusConnectionHandler ->> LpcUseCaseHandler: handle_event(event)
activate LpcUseCaseHandler
LpcUseCaseHandler ->> LpcUseCaseHandler: run_state_machine()
LpcUseCaseHandler ->> LpcUseCaseHandler: apply_limit_for_current_state()
LpcUseCaseHandler ->> EEBUS: m_callbacks.update_limits_callback()
deactivate LpcUseCaseHandler
deactivate EebusConnectionHandler
Class Diagram¶
This diagram shows the main classes within the EEBUS module and their relationships.
classDiagram
class EEBUS {
+unique_ptr~external_energy_limitsIntf~ r_eebus_energy_sink
+Conf config
-thread m_eebus_grpc_api_thread
-unique_ptr~EebusConnectionHandler~ m_connection_handler
-EebusCallbacks m_callbacks
-fd_event_handler m_event_handler
-thread m_event_handler_thread
+init()
+ready()
}
class EebusConnectionHandler {
-shared_ptr~ConfigValidator~ m_config
-unique_ptr~lpc::LpcUseCaseHandler~ m_lpc_handler
-unique_ptr~UseCaseEventReader~ m_event_reader
-shared_ptr~control_service::ControlService::Stub~ m_control_service_stub
-fd_event_handler m_handler
-State m_state
-timer_fd m_state_machine_timer
-timer_fd m_reconnection_timer
-EebusUseCase m_last_use_case
-EebusCallbacks m_last_callbacks
-bool m_use_case_added
+sync()
+get_poll_fd()
+add_use_case()
+done_adding_use_case()
+stop()
-initialize_connection()
-configure_service()
-create_channel_and_stub()
-handle_event()
-reconnect()
-reset()
}
class LpcUseCaseHandler {
-EebusCallbacks m_callbacks
-shared_ptr~cs_lpc::ControllableSystemLPCControl::Stub~ m_stub
-State m_state
+start()
+set_stub()
+configure_use_case()
+handle_event()
+run_state_machine()
+process_received_limit()
-set_state()
-apply_limit_for_current_state()
}
class UseCaseEventReader {
-shared_ptr~control_service::ControlService::Stub~ m_stub
-function m_event_callback
+start()
+stop()
+OnReadDone()
+OnDone()
}
class ConfigValidator {
-Conf m_config
+validate()
}
class Conf {
// ... fields
}
EEBUS *-- EebusConnectionHandler
EEBUS ..> Conf
EebusConnectionHandler *-- LpcUseCaseHandler
EebusConnectionHandler *-- UseCaseEventReader
EebusConnectionHandler o-- ConfigValidator
LpcUseCaseHandler ..> EebusCallbacks
UseCaseEventReader ..> EebusConnectionHandler : event_callback
Robustness¶
The module includes several features to make it resilient against connection losses and process crashes.
gRPC Process Restart: If the module is configured to manage the
eebus_grpc_apibinary (viamanage_eebus_grpc_api_binary: true), it will automatically restart the binary if it crashes or exits unexpectedly. The delay between restart attempts is configurable viarestart_delay_s(default: 5 seconds).gRPC Reconnection: The
EebusConnectionHandlerwill automatically try to reconnect to the gRPC service if the connection is lost. The delay between reconnect attempts is configurable viareconnect_delay_s(default: 5 seconds). Once reconnected, it will re-establish the configured use cases.
Configuration¶
Key |
Description |
|---|---|
|
(boolean) Whether the module should manage the |
|
(integer) Port for the control service, this will be sent in the |
|
(integer) Port for gRPC control service connection. Required if |
|
(string) Comma-separated list of pre-trusted EEBUS EMS SKIs. Each entry is a 40-character lowercase SHA-1 hex digest; whitespace around entries is trimmed. Every allowlisted SKI is registered with the sidecar at startup. Default: |
|
(boolean) If |
|
(string) Path to the certificate file. If relative, it will be prefixed with |
|
(string) Path to the private key file. If relative, it will be prefixed with |
|
(string) Path to the |
|
(string, required) Vendor code for the configuration of the control service. |
|
(string, required) Device brand for the configuration of the control service. |
|
(string, required) Device model for the configuration of the control service. |
|
(string, required) Serial number for the configuration of the control service. |
|
(integer) Failsafe control limit for the LPC use case in Watts. This is also used for the default consumption limit. Default: |
|
(integer) Maximum nominal power of the charging station in Watts. This is the maximum power the CS can consume. Default: |
|
(integer) Delay in seconds before restarting the |
|
(integer) Delay in seconds before retrying a lost gRPC connection to the |
SKI allowlist and discovery¶
The module trusts peer EEBUS Energy Guards (EGs, typically HEMS-class controllers) via a combination of a static allowlist and optional runtime auto-trust.
eebus_ems_ski_allowlist¶
Comma-separated list of pre-trusted EEBUS EMS SKIs. Each entry is a 40-character lowercase SHA-1 hex digest; whitespace around entries is tolerated and trimmed.
eebus_ems_ski_allowlist: "abcdef0123456789abcdef0123456789abcdef01, aabbccddeeff00112233445566778899aabbccdd"
At startup the module iterates over the effective allowlist and calls
RegisterRemoteSki once per entry with the sidecar before StartService.
At runtime the module subscribes to discovery events from the sidecar; events
for SKIs already in the allowlist are treated as no-ops when the SKI is
already trusted by the sidecar, and trigger a re-register otherwise.
accept_unknown_ems¶
Boolean flag (default false). When true, any EG SKI that appears in a
discovery event and is neither already trusted nor in the allowlist is
auto-registered for the duration of this session and a warning is logged.
Warning
This flag is security-sensitive. Only enable it on isolated or trusted
networks where every EEBUS peer that could appear on the LAN is known to
be safe. Leave it false on production and shared networks.
The flag interacts with the allowlist as follows: allowlisted SKIs are always auto-registered at startup regardless of this flag; the flag only controls the “not in allowlist” branch of the runtime discovery classifier.
Discovery flow¶
At startup every SKI in
eebus_ems_ski_allowlistis registered with the sidecar before the service is started.The module subscribes to discovery events from the sidecar via
SubscribeDiscoveryEvents.For each
DISCOVEREDevent the module applies one of four actions, based on allowlist membership and theaccept_unknown_emsflag:Condition
Action
Log level
SKI already trusted by sidecar
no-op
debug
SKI in allowlist (not yet trusted)
register
info
SKI unknown,
accept_unknown_ems=trueregister
warning
SKI unknown,
accept_unknown_ems=falseignore
info
The sidecar initiates pairing handshakes with every trusted SKI.
Provided and required interfaces¶
Provides no interfaces.
Requires
eebus_energy_sink(external_energy_limitsinterface). This is used to publish the calculated energy limits.
Adding a python test¶
The python test suite for the EEBUS module is located in tests/eebus_tests. The tests are written using the pytest framework.
To add a new test, you can add a new test function to the TestEEBUSModule class in eebus_tests.py or add a new test file.
A new test function could look like this:
@pytest.mark.asyncio
async def test_my_new_feature(
self,
eebus_test_env: dict,
):
"""
This test verifies my new feature.
"""
# Unpack the test environment from the fixture
everest_core = eebus_test_env["everest_core"]
control_service_servicer = eebus_test_env["control_service_servicer"]
cs_lpc_control_servicer = eebus_test_env["cs_lpc_control_servicer"]
cs_lpc_control_server = eebus_test_env["cs_lpc_control_server"]
# Perform the handshake and get the probe module
probe = await perform_eebus_handshake(control_service_servicer, cs_lpc_control_servicer, cs_lpc_control_server, everest_core)
# Your test logic here
The eebus_test_env fixture provides a dictionary with the necessary components for the test:
everest_core: An instance of theEverestCoreclass, which manages the EVerest framework.control_service_servicer: A mock gRPC control service.cs_lpc_control_servicer: A mock gRPC LPC control service.cs_lpc_control_server: The gRPC server for the LPC control service.
The perform_eebus_handshake helper function can be used to perform the initial handshake between the EEBUS module and the mock gRPC services.
For new test cases you can create a new class that inherits from TestData and implement the necessary methods to provide the test data. Then, you can add your new test data to the @pytest.mark.parametrize decorator in the test_set_load_limit test function.
To run the tests, use the eebus suite of the test runner from the tests directory: ./run-tests.sh eebus.
Acknowledgment¶
This module has thankfully received support from the German Federal Ministry for Economic Affairs and Climate Action. Information on the corresponding research project can be found here (in German only): InterBDL research project