Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
61 changes: 61 additions & 0 deletions packages/modules/devices/kostal/kostal_piko/bat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env python3
import logging
from typing import Any, List, Tuple, TypedDict

from modules.common import req
from modules.common.abstract_device import AbstractBat
from modules.common.component_state import BatState
from modules.common.component_type import ComponentDescriptor
from modules.common.fault_state import ComponentInfo, FaultState
from modules.common.simcount import SimCounter
from modules.common.store import get_bat_value_store
from modules.devices.kostal.kostal_piko.config import KostalPikoBatSetup
from modules.common.utils.peak_filter import PeakFilter
from modules.common.component_type import ComponentType

log = logging.getLogger(__name__)


class KwargsDict(TypedDict):
device_id: int
ip_address: str


class KostalPikoBat(AbstractBat):
def __init__(self, component_config: KostalPikoBatSetup, **kwargs: Any) -> None:
self.component_config = component_config
self.kwargs: KwargsDict = kwargs

def initialize(self) -> None:
self.__device_id: int = self.kwargs['device_id']
self.ip_address: str = self.kwargs['ip_address']
self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher")
self.store = get_bat_value_store(self.component_config.id)
self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config))
self.peak_filter = PeakFilter(ComponentType.BAT, self.component_config.id, self.fault_state)

def get_values(self) -> Tuple[float, List[float]]:
# Bat Current, Bat Voltage, Bat SoC
params = (('dxsEntries', ['33556225', '33556226', '33556229']),)
resp = req.get_http_session().get('http://'+self.ip_address+'/api/dxs.json',
params=params,
timeout=3).json()["dxsEntries"]
power = float(resp[0]["value"]) * float(resp[1]["value"])
soc = float(resp[2]["value"])
return power, soc
Comment on lines +37 to +45
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_values is annotated to return Tuple[float, List[float]], but it actually returns (power, soc) where soc is a float. This mismatch makes the API misleading and will be flagged by type checkers; adjust the return type (and related imports) to match the real return value.

Copilot uses AI. Check for mistakes.

def update(self):
power, soc = self.get_values()

self.peak_filter.check_values(power)
imported, exported = self.sim_counter.sim_count(power)
bat_state = BatState(
imported=imported,
exported=exported,
power=power,
soc=soc
)
self.store.set(bat_state)
Comment on lines +37 to +58
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New battery integration adds parsing of /api/dxs.json responses and calculation of power/SoC, but there are no unit tests covering the JSON parsing and sign/units behavior. Add a pytest similar to other device battery tests (mocking the HTTP response and SimCounter) to lock in expected BatState.power and BatState.soc values.

Copilot uses AI. Check for mistakes.


component_descriptor = ComponentDescriptor(configuration_factory=KostalPikoBatSetup)
14 changes: 14 additions & 0 deletions packages/modules/devices/kostal/kostal_piko/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,17 @@ def __init__(self,
id: int = 0,
configuration: KostalPikoInverterConfiguration = None) -> None:
super().__init__(name, type, id, configuration or KostalPikoInverterConfiguration())


class KostalPikoBatConfiguration:
def __init__(self):
pass


class KostalPikoBatSetup(ComponentSetup[KostalPikoBatConfiguration]):
def __init__(self,
name: str = "Kostal Piko Speicher",
type: str = "bat",
id: int = 0,
configuration: KostalPikoBatConfiguration = None) -> None:
super().__init__(name, type, id, configuration or KostalPikoBatConfiguration())
10 changes: 9 additions & 1 deletion packages/modules/devices/kostal/kostal_piko/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
from modules.common.abstract_device import DeviceDescriptor
from modules.devices.kostal.kostal_piko import counter
from modules.devices.kostal.kostal_piko import inverter
from modules.devices.kostal.kostal_piko.config import KostalPiko, KostalPikoCounterSetup, KostalPikoInverterSetup
from modules.devices.kostal.kostal_piko import bat
from modules.devices.kostal.kostal_piko.config import (KostalPiko, KostalPikoCounterSetup,
KostalPikoInverterSetup, KostalPikoBatSetup)

log = logging.getLogger(__name__)

Expand All @@ -20,11 +22,17 @@ def create_inverter_component(component_config: KostalPikoInverterSetup):
return inverter.KostalPikoInverter(component_config,
ip_address=device_config.configuration.ip_address)

def create_bat_component(component_config: KostalPikoBatSetup):
return bat.KostalPikoBat(component_config,
device_id=device_config.id,
ip_address=device_config.configuration.ip_address)

return ConfigurableDevice(
device_config=device_config,
component_factory=ComponentFactoryByType(
counter=create_counter_component,
inverter=create_inverter_component,
bat=create_bat_component
),
component_updater=IndependentComponentUpdater(lambda component: component.update())
)
Expand Down
Loading