Add new daemons and debug scripts for Sigenergy and Oracle functionalities

- Implement `sigen_daemon.py` to poll Sigenergy plant metrics and store snapshots.
- Create `web_daemon.py` for serving a web interface with various endpoints.
- Add debug scripts:
  - `debug_duplicates.py` to find duplicate target times in forecast data.
  - `debug_energy_forecast.py` to print baseline energy forecast curves.
  - `debug_oracle_evaluations.py` to run the oracle evaluator.
  - `debug_sigen.py` to inspect stored Sigenergy plant snapshots.
  - `debug_weather.py` to trace resolved truth data.
  - `modbus_test.py` for exploring Sigenergy plants or inverters over Modbus TCP.
- Introduce `oracle_evaluator.py` for evaluating stored oracle predictions against actuals.
- Add TCN training scripts in `tcn` directory for training usage sequence models.
This commit is contained in:
rpotter6298
2026-04-28 08:14:00 +02:00
parent ff0c65a794
commit c8e3016fd6
55 changed files with 6385 additions and 633 deletions
+530
View File
@@ -0,0 +1,530 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Literal
from gibil.classes.sigen.modbus import RegisterKind
SigenDataType = Literal["u16", "u32", "u64", "s16", "s32", "string"]
@dataclass(frozen=True)
class SigenRegister:
name: str
kind: RegisterKind
address: int
count: int
data_type: SigenDataType
gain: float = 1
unit: str | None = None
description: str | None = None
def decode(self, registers: list[int] | list[bool]) -> int | float | str:
numeric_registers = [int(register) for register in registers[: self.count]]
if self.data_type == "string":
return self._decode_string(numeric_registers)
raw_value = self._combine(numeric_registers)
if self.data_type.startswith("s"):
bits = 16 * self.count
sign_bit = 1 << (bits - 1)
if raw_value & sign_bit:
raw_value -= 1 << bits
if self.gain == 1:
return raw_value
return raw_value / self.gain
def _combine(self, registers: list[int]) -> int:
value = 0
for register in registers:
value = (value << 16) | (register & 0xFFFF)
return value
def _decode_string(self, registers: list[int]) -> str:
raw_bytes = bytearray()
for register in registers:
raw_bytes.append((register >> 8) & 0xFF)
raw_bytes.append(register & 0xFF)
return raw_bytes.rstrip(b"\x00").decode("ascii", errors="replace").strip()
PLANT_REGISTERS: dict[str, SigenRegister] = {
"plant_system_time": SigenRegister(
name="plant_system_time",
kind="input",
address=30000,
count=2,
data_type="u32",
unit="s",
),
"plant_ems_work_mode": SigenRegister(
name="plant_ems_work_mode",
kind="input",
address=30003,
count=1,
data_type="u16",
),
"plant_grid_sensor_status": SigenRegister(
name="plant_grid_sensor_status",
kind="input",
address=30004,
count=1,
data_type="u16",
),
"plant_grid_sensor_active_power": SigenRegister(
name="plant_grid_sensor_active_power",
kind="input",
address=30005,
count=2,
data_type="s32",
gain=1000,
unit="kW",
),
"plant_ess_soc": SigenRegister(
name="plant_ess_soc",
kind="input",
address=30014,
count=1,
data_type="u16",
gain=10,
unit="%",
),
"plant_active_power": SigenRegister(
name="plant_active_power",
kind="input",
address=30031,
count=2,
data_type="s32",
gain=1000,
unit="kW",
),
"plant_sigen_photovoltaic_power": SigenRegister(
name="plant_sigen_photovoltaic_power",
kind="input",
address=30035,
count=2,
data_type="s32",
gain=1000,
unit="kW",
),
"plant_ess_power": SigenRegister(
name="plant_ess_power",
kind="input",
address=30037,
count=2,
data_type="s32",
gain=1000,
unit="kW",
),
"plant_running_state": SigenRegister(
name="plant_running_state",
kind="input",
address=30051,
count=1,
data_type="u16",
),
"plant_ess_rated_energy_capacity": SigenRegister(
name="plant_ess_rated_energy_capacity",
kind="input",
address=30083,
count=2,
data_type="u32",
gain=100,
unit="kWh",
),
"plant_ess_soh": SigenRegister(
name="plant_ess_soh",
kind="input",
address=30087,
count=1,
data_type="u16",
gain=10,
unit="%",
),
"plant_accumulated_pv_energy": SigenRegister(
name="plant_accumulated_pv_energy",
kind="input",
address=30088,
count=4,
data_type="u64",
gain=100,
unit="kWh",
),
"plant_daily_consumed_energy": SigenRegister(
name="plant_daily_consumed_energy",
kind="input",
address=30092,
count=2,
data_type="u32",
gain=100,
unit="kWh",
),
"plant_accumulated_consumed_energy": SigenRegister(
name="plant_accumulated_consumed_energy",
kind="input",
address=30094,
count=4,
data_type="u64",
gain=100,
unit="kWh",
),
"plant_general_load_power": SigenRegister(
name="plant_general_load_power",
kind="input",
address=30282,
count=2,
data_type="s32",
gain=1000,
unit="kW",
description="General load power",
),
"plant_total_load_power": SigenRegister(
name="plant_total_load_power",
kind="input",
address=30284,
count=2,
data_type="s32",
gain=1000,
unit="kW",
description="Total load power",
),
}
PLANT_PARAMETER_REGISTERS: dict[str, SigenRegister] = {
"plant_start_stop": SigenRegister(
name="plant_start_stop",
kind="holding",
address=40000,
count=1,
data_type="u16",
description="Start/Stop (0: Stop, 1: Start)",
),
"plant_active_power_fixed_target": SigenRegister(
name="plant_active_power_fixed_target",
kind="holding",
address=40001,
count=2,
data_type="s32",
gain=1000,
unit="kW",
description="Active power fixed adjustment target value",
),
"plant_reactive_power_fixed_target": SigenRegister(
name="plant_reactive_power_fixed_target",
kind="holding",
address=40003,
count=2,
data_type="s32",
gain=1000,
unit="kvar",
description="Reactive power fixed adjustment target value",
),
"plant_active_power_percentage_target": SigenRegister(
name="plant_active_power_percentage_target",
kind="holding",
address=40005,
count=1,
data_type="s16",
gain=100,
unit="%",
description="Active power percentage target. Range: -100.00 to 100.00",
),
"plant_qs_ratio_target": SigenRegister(
name="plant_qs_ratio_target",
kind="holding",
address=40006,
count=1,
data_type="s16",
gain=100,
unit="%",
description="Q/S adjustment target value",
),
"plant_power_factor_target": SigenRegister(
name="plant_power_factor_target",
kind="holding",
address=40007,
count=1,
data_type="s16",
gain=1000,
description="Power factor adjustment target value",
),
"plant_remote_ems_enable": SigenRegister(
name="plant_remote_ems_enable",
kind="holding",
address=40029,
count=1,
data_type="u16",
description="Remote EMS enable (0: disabled, 1: enabled)",
),
"plant_independent_phase_power_control_enable": SigenRegister(
name="plant_independent_phase_power_control_enable",
kind="holding",
address=40030,
count=1,
data_type="u16",
description="Independent phase power control enable (0: disabled, 1: enabled)",
),
"plant_remote_ems_control_mode": SigenRegister(
name="plant_remote_ems_control_mode",
kind="holding",
address=40031,
count=1,
data_type="u16",
description=(
"Remote EMS control mode: 0 PCS remote, 1 standby, "
"2 self-consumption, 3 charge grid first, 4 charge PV first, "
"5 discharge PV first, 6 discharge ESS first"
),
),
"plant_ess_max_charging_limit": SigenRegister(
name="plant_ess_max_charging_limit",
kind="holding",
address=40032,
count=2,
data_type="u32",
gain=1000,
unit="kW",
description="ESS max charging limit",
),
"plant_ess_max_discharging_limit": SigenRegister(
name="plant_ess_max_discharging_limit",
kind="holding",
address=40034,
count=2,
data_type="u32",
gain=1000,
unit="kW",
description="ESS max discharging limit",
),
"plant_pv_max_power_limit": SigenRegister(
name="plant_pv_max_power_limit",
kind="holding",
address=40036,
count=2,
data_type="u32",
gain=1000,
unit="kW",
description="PV max power limit",
),
"plant_grid_point_maximum_export_limitation": SigenRegister(
name="plant_grid_point_maximum_export_limitation",
kind="holding",
address=40038,
count=2,
data_type="u32",
gain=1000,
unit="kW",
description="Grid point maximum export limitation",
),
"plant_grid_maximum_import_limitation": SigenRegister(
name="plant_grid_maximum_import_limitation",
kind="holding",
address=40040,
count=2,
data_type="u32",
gain=1000,
unit="kW",
description="Grid point maximum import limitation",
),
"plant_pcs_maximum_export_limitation": SigenRegister(
name="plant_pcs_maximum_export_limitation",
kind="holding",
address=40042,
count=2,
data_type="u32",
gain=1000,
unit="kW",
description="PCS maximum export limitation",
),
"plant_pcs_maximum_import_limitation": SigenRegister(
name="plant_pcs_maximum_import_limitation",
kind="holding",
address=40044,
count=2,
data_type="u32",
gain=1000,
unit="kW",
description="PCS maximum import limitation",
),
"plant_backup_soc": SigenRegister(
name="plant_backup_soc",
kind="holding",
address=40046,
count=1,
data_type="u16",
gain=10,
unit="%",
description="ESS backup SOC. Range: 0 to 100.0",
),
"plant_charge_cut_off_soc": SigenRegister(
name="plant_charge_cut_off_soc",
kind="holding",
address=40047,
count=1,
data_type="u16",
gain=10,
unit="%",
description="ESS charge cut-off SOC. Range: 0 to 100.0",
),
"plant_discharge_cut_off_soc": SigenRegister(
name="plant_discharge_cut_off_soc",
kind="holding",
address=40048,
count=1,
data_type="u16",
gain=10,
unit="%",
description="ESS discharge cut-off SOC. Range: 0 to 100.0",
),
}
INVERTER_REGISTERS: dict[str, SigenRegister] = {
"inverter_model_type": SigenRegister(
name="inverter_model_type",
kind="input",
address=30500,
count=15,
data_type="string",
),
"inverter_serial_number": SigenRegister(
name="inverter_serial_number",
kind="input",
address=30515,
count=10,
data_type="string",
),
"inverter_machine_firmware_version": SigenRegister(
name="inverter_machine_firmware_version",
kind="input",
address=30525,
count=15,
data_type="string",
),
"inverter_rated_active_power": SigenRegister(
name="inverter_rated_active_power",
kind="input",
address=30540,
count=2,
data_type="u32",
gain=1000,
unit="kW",
),
"inverter_running_state": SigenRegister(
name="inverter_running_state",
kind="input",
address=30578,
count=1,
data_type="u16",
),
"inverter_active_power": SigenRegister(
name="inverter_active_power",
kind="input",
address=30587,
count=2,
data_type="s32",
gain=1000,
unit="kW",
),
"inverter_reactive_power": SigenRegister(
name="inverter_reactive_power",
kind="input",
address=30589,
count=2,
data_type="s32",
gain=1000,
unit="kvar",
),
"inverter_ess_charge_discharge_power": SigenRegister(
name="inverter_ess_charge_discharge_power",
kind="input",
address=30599,
count=2,
data_type="s32",
gain=1000,
unit="kW",
),
"inverter_ess_battery_soc": SigenRegister(
name="inverter_ess_battery_soc",
kind="input",
address=30601,
count=1,
data_type="u16",
gain=10,
unit="%",
),
"inverter_ess_battery_soh": SigenRegister(
name="inverter_ess_battery_soh",
kind="input",
address=30602,
count=1,
data_type="u16",
gain=10,
unit="%",
),
"inverter_pv_power": SigenRegister(
name="inverter_pv_power",
kind="input",
address=31035,
count=2,
data_type="s32",
gain=1000,
unit="kW",
),
"inverter_daily_pv_energy": SigenRegister(
name="inverter_daily_pv_energy",
kind="input",
address=31509,
count=2,
data_type="u32",
gain=100,
unit="kWh",
),
"inverter_accumulated_pv_energy": SigenRegister(
name="inverter_accumulated_pv_energy",
kind="input",
address=31511,
count=4,
data_type="u64",
gain=100,
unit="kWh",
),
}
DEFAULT_PLANT_REGISTER_NAMES = (
"plant_system_time",
"plant_ems_work_mode",
"plant_grid_sensor_status",
"plant_grid_sensor_active_power",
"plant_ess_soc",
"plant_active_power",
"plant_sigen_photovoltaic_power",
"plant_ess_power",
"plant_running_state",
"plant_ess_rated_energy_capacity",
"plant_ess_soh",
"plant_accumulated_pv_energy",
"plant_daily_consumed_energy",
"plant_accumulated_consumed_energy",
"plant_general_load_power",
"plant_total_load_power",
)
DEFAULT_INVERTER_REGISTER_NAMES = (
"inverter_model_type",
"inverter_serial_number",
"inverter_machine_firmware_version",
"inverter_rated_active_power",
"inverter_running_state",
"inverter_active_power",
"inverter_reactive_power",
"inverter_ess_charge_discharge_power",
"inverter_ess_battery_soc",
"inverter_ess_battery_soh",
"inverter_pv_power",
"inverter_daily_pv_energy",
"inverter_accumulated_pv_energy",
)