Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature shelly pro3em #1670

Merged
merged 5 commits into from
Jun 17, 2024
Merged
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
59 changes: 59 additions & 0 deletions packages/modules/devices/shelly/bat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env python3
import logging
from typing import Optional
from modules.common import req
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.store import get_bat_value_store
from modules.common.simcount._simcounter import SimCounter
from modules.devices.shelly.config import ShellyBatSetup

log = logging.getLogger(__name__)


class ShellyBat:

def __init__(self,
device_id: int,
component_config: ShellyBatSetup,
address: str,
generation: Optional[int]) -> None:
self.component_config = component_config
self.sim_counter = SimCounter(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.address = address
self.generation = generation

def total_power_from_shelly(self) -> int:
total = 0
if self.generation == 1:
status_url = "http://" + self.address + "/status"
else:
status_url = "http://" + self.address + "/rpc/Shelly.GetStatus"
status = req.get_http_session().get(status_url, timeout=3).json()
try:
if self.generation == 1:
meters = status['emeters'] # shelly3EM
for meter in meters:
total = total + meter['power']
else:
total = status['em:0']['total_act_power'] # shelly Pro3EM
except KeyError:
log.exception("unsupported shelly device?")
finally:
return int(total)

def update(self) -> None:
bat = self.total_power_from_shelly() * -1
imported, exported = self.sim_counter.sim_count(bat)
bat_state = BatState(
power=bat,
imported=imported,
exported=exported
)
self.store.set(bat_state)


component_descriptor = ComponentDescriptor(configuration_factory=ShellyBatSetup)
32 changes: 32 additions & 0 deletions packages/modules/devices/shelly/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,22 @@ def __init__(self,
self.configuration = configuration or ShellyConfiguration()


@auto_str
class ShellyCounterConfiguration:
def __init__(self) -> None:
pass


@auto_str
class ShellyCounterSetup(ComponentSetup[ShellyCounterConfiguration]):
def __init__(self,
name: str = "Shelly Zähler",
type: str = "counter",
id: int = 0,
configuration: ShellyCounterConfiguration = None) -> None:
super().__init__(name, type, id, configuration or ShellyCounterConfiguration())


@auto_str
class ShellyInverterConfiguration:
def __init__(self) -> None:
Expand All @@ -37,3 +53,19 @@ def __init__(self,
id: int = 0,
configuration: ShellyInverterConfiguration = None) -> None:
super().__init__(name, type, id, configuration or ShellyInverterConfiguration())


@auto_str
class ShellyBatConfiguration:
def __init__(self) -> None:
pass


@auto_str
class ShellyBatSetup(ComponentSetup[ShellyBatConfiguration]):
def __init__(self,
name: str = "Shelly Speicher",
type: str = "bat",
id: int = 0,
configuration: ShellyBatConfiguration = None) -> None:
super().__init__(name, type, id, configuration or ShellyBatConfiguration())
72 changes: 72 additions & 0 deletions packages/modules/devices/shelly/counter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env python3
import logging
from typing import Optional
from modules.common import req
from modules.common.component_state import CounterState
from modules.common.component_type import ComponentDescriptor
from modules.common.fault_state import ComponentInfo, FaultState
from modules.common.store import get_counter_value_store
from modules.common.simcount._simcounter import SimCounter
from modules.devices.shelly.config import ShellyCounterSetup

log = logging.getLogger(__name__)


class ShellyCounter:

def __init__(self,
device_id: int,
component_config: ShellyCounterSetup,
address: str,
generation: Optional[int]) -> None:
self.component_config = component_config
self.sim_counter = SimCounter(device_id, self.component_config.id, prefix="bezug")
self.store = get_counter_value_store(self.component_config.id)
self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config))
self.address = address
self.generation = generation

def update(self) -> None:
power = 0
if self.generation == 1:
status_url = "http://" + self.address + "/status"
else:
status_url = "http://" + self.address + "/rpc/Shelly.GetStatus"
status = req.get_http_session().get(status_url, timeout=3).json()
try:
if self.generation == 1: # shelly3EM
meters = status['emeters']
# shelly3EM has three meters:
for meter in meters:
power = power + meter['power']
power = power * -1

voltages = [status['emeters'][i]['voltage'] for i in range(0, 3)]
currents = [status['emeters'][i]['current'] for i in range(0, 3)]
powers = [status['emeters'][i]['power'] for i in range(0, 3)]
power_factors = [status['emeters'][i]['pf'] for i in range(0, 3)]
imported, exported = self.sim_counter.sim_count(power)
else:
# shelly Pro3EM
voltages = [status['em:0'][f'{i}_voltage'] for i in 'abc']
currents = [status['em:0'][f'{i}_current'] for i in 'abc']
powers = [status['em:0'][f'{i}_act_power'] for i in 'abc']
power_factors = [status['em:0'][f'{i}_pf'] for i in 'abc']
power = status['em:0']['total_act_power'] * -1
imported, exported = self.sim_counter.sim_count(power)

counter_state = CounterState(
voltages=voltages,
currents=currents,
powers=powers,
power_factors=power_factors,
imported=imported,
exported=exported,
power=power
)
self.store.set(counter_state)
except KeyError:
log.exception("unsupported shelly device?")


component_descriptor = ComponentDescriptor(configuration_factory=ShellyCounterSetup)
16 changes: 15 additions & 1 deletion packages/modules/devices/shelly/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@
from modules.common.abstract_device import DeviceDescriptor
from modules.common.configurable_device import (ConfigurableDevice, ComponentFactoryByType, IndependentComponentUpdater)
from modules.devices.shelly.inverter import ShellyInverter
from modules.devices.shelly.bat import ShellyBat
from modules.devices.shelly.counter import ShellyCounter
from modules.devices.shelly.config import Shelly, ShellyConfiguration
from modules.devices.shelly.config import ShellyInverterSetup, ShellyInverterConfiguration
from modules.devices.shelly.config import ShellyBatSetup
from modules.devices.shelly.config import ShellyCounterSetup


log = logging.getLogger(__name__)
Expand All @@ -25,17 +29,27 @@ def get_device_generation(address: str) -> int:


def create_device(device_config: Shelly) -> ConfigurableDevice:
def create_counter_component(component_config: ShellyCounterSetup) -> ShellyCounter:
return ShellyCounter(device_config.id, component_config, device_config.configuration.ip_address,
device_config.configuration.generation)

def create_inverter_component(component_config: ShellyInverterSetup) -> ShellyInverter:
return ShellyInverter(device_config.id, component_config, device_config.configuration.ip_address,
device_config.configuration.generation)

def create_bat_component(component_config: ShellyBatSetup) -> ShellyBat:
return ShellyBat(device_config.id, component_config, device_config.configuration.ip_address,
device_config.configuration.generation)

if device_config.configuration.generation is None and device_config.configuration.ip_address is not None:
device_config.configuration.generation = get_device_generation(device_config.configuration.ip_address)

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