Skip to content

Commit

Permalink
fems: support multiple and single segment regex rqeuests (openWB#1397)
Browse files Browse the repository at this point in the history
* fems: support multiple and single segment regex rqeuests

* fix
  • Loading branch information
LKuemmel authored and MartinRinas committed Feb 2, 2024
1 parent b6074a1 commit 6f581a8
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 73 deletions.
81 changes: 58 additions & 23 deletions packages/modules/devices/fems/bat.py
Original file line number Diff line number Diff line change
@@ -1,46 +1,81 @@
import logging
from requests import Session
from helpermodules.scale_metric import scale_metric
from modules.devices.fems.config import FemsBatSetup
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.devices.fems.version import FemsVersion, get_version
log = logging.getLogger(__name__)


class FemsBat:
def __init__(self, ip_address: str, component_config: FemsBatSetup) -> None:
def __init__(self, ip_address: str, component_config: FemsBatSetup, session: Session) -> None:
self.ip_address = ip_address
self.component_config = component_config
self.session = session
self.store = get_bat_value_store(self.component_config.id)
self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config))

def update(self, session: Session) -> None:
if self.component_config.configuration.num == 1:
data = "ess0"
self._data = "ess0"
else:
data = "ess2"
response = session.get(
"http://" + self.ip_address + ":8084/rest/channel/(" + data + "|_sum)/" +
"(Soc|DcChargeEnergy|DcDischargeEnergy|GridActivePower|ProductionActivePower|ConsumptionActivePower)",
self._data = "ess2"
self.version = get_version(self.get_data_by_multiple_segement_regex_query)
log.debug(f"{self.component_config.name} unterstützt {self.version.value}")

def get_data_by_multiple_segement_regex_query(self):
return self.session.get(
(f"http://{self.ip_address}:8084/rest/channel/({self._data}|_sum)/"
"(Soc|DcChargeEnergy|DcDischargeEnergy|GridActivePower|ProductionActivePower|ConsumptionActivePower)"),
timeout=2).json()
for singleValue in response:
address = singleValue["address"]
if (address == data+"/Soc"):
soc = singleValue["value"]
elif address == data+"/DcChargeEnergy":
imported = scale_metric(singleValue['value'], singleValue.get('unit'), 'Wh')
elif address == data+"/DcDischargeEnergy":
exported = scale_metric(singleValue['value'], singleValue.get('unit'), 'Wh')
elif address == "_sum/GridActivePower":
grid = scale_metric(singleValue['value'], singleValue.get('unit'), 'W')
elif address == "_sum/ProductionActivePower":
pv = scale_metric(singleValue['value'], singleValue.get('unit'), 'W')
elif address == "_sum/ConsumptionActivePower":
haus = scale_metric(singleValue['value'], singleValue.get('unit'), 'W')

def update(self) -> None:
if self.version == FemsVersion.MULTIPLE_SEGMENT_REGEX_QUERY:
response = self.get_data_by_multiple_segement_regex_query()
for singleValue in response:
address = singleValue["address"]
if (address == self._data+"/Soc"):
soc = singleValue["value"]
elif address == self._data+"/DcChargeEnergy":
imported = scale_metric(singleValue['value'], singleValue.get('unit'), 'Wh')
elif address == self._data+"/DcDischargeEnergy":
exported = scale_metric(singleValue['value'], singleValue.get('unit'), 'Wh')
elif address == "_sum/GridActivePower":
grid = scale_metric(singleValue['value'], singleValue.get('unit'), 'W')
elif address == "_sum/ProductionActivePower":
pv = scale_metric(singleValue['value'], singleValue.get('unit'), 'W')
elif address == "_sum/ConsumptionActivePower":
haus = scale_metric(singleValue['value'], singleValue.get('unit'), 'W')
else:
response = self.session.get(
f"http://{self.ip_address}:8084/rest/channel/{self.data}/(Soc|DcChargeEnergy|DcDischargeEnergy)",
timeout=2).json()
for singleValue in response:
address = singleValue["address"]
if (address == self.data+"/Soc"):
soc = singleValue["value"]
elif address == self.data+"/DcChargeEnergy":
imported = scale_metric(singleValue['value'], singleValue.get('unit'), 'Wh')
elif address == self.data+"/DcDischargeEnergy":
exported = scale_metric(singleValue['value'], singleValue.get('unit'), 'Wh')
elif address == "_sum/GridActivePower":
grid = scale_metric(singleValue['value'], singleValue.get('unit'), 'W')

response = self.session.get(
(f"http://{self.ip_address}:8084/rest/channel/_sum/(GridActivePower|ProductionActivePower|"
"ConsumptionActivePower)"),
timeout=2).json()
for singleValue in response:
address = singleValue["address"]
if (address == "_sum/GridActivePower"):
grid = scale_metric(singleValue['value'], singleValue.get('unit'), 'W')
elif address == "_sum/ProductionActivePower":
pv = scale_metric(singleValue['value'], singleValue.get('unit'), 'W')
elif address == "_sum/ConsumptionActivePower":
haus = scale_metric(singleValue['value'], singleValue.get('unit'), 'W')
# keine Berechnung im Gerät, da grid nicht der Leistung aus der Zählerkomponente entspricht.
power = grid + pv - haus

bat_state = BatState(
power=power,
soc=soc,
Expand Down
117 changes: 82 additions & 35 deletions packages/modules/devices/fems/counter.py
Original file line number Diff line number Diff line change
@@ -1,55 +1,100 @@
import logging
from requests import Session
from helpermodules.scale_metric import scale_metric
from modules.devices.fems.config import FemsCounterSetup
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.devices.fems.version import FemsVersion, get_version

log = logging.getLogger(__name__)


class FemsCounter:
def __init__(self, ip_address: str, component_config: FemsCounterSetup) -> None:
def __init__(self, ip_address: str, component_config: FemsCounterSetup, session: Session) -> None:
self.ip_address = ip_address
self.component_config = component_config
self.session = session
self.store = get_counter_value_store(self.component_config.id)
self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config))
self.version = get_version(self.get_data_by_multiple_segement_regex_query)
log.debug(f"{self.component_config.name} unterstützt {self.version.value}")

def update(self, session: Session) -> None:
try:
# Grid meter values and grid total energy sums
response = session.get('http://' + self.ip_address +
':8084/rest/channel/(meter0|_sum)/' +
'(ActivePower.*|VoltageL.|Frequency|Grid.+ActiveEnergy)',
timeout=6).json()
def get_data_by_multiple_segement_regex_query(self):
return self.session.get('http://' + self.ip_address +
':8084/rest/channel/(meter0|_sum)/' +
'(ActivePower.*|VoltageL.|Frequency|Grid.+ActiveEnergy)',
timeout=6).json()

def update(self) -> None:
try:
# ATTENTION: Recent FEMS versions started using the "unit" field (see example response below) and
# kind-of arbitrarily return either Volts, Kilowatthours or Hz or Millivolts, Watthours or
# Millihertz
# Others units (kW, kV) have not yet been observed but are coded just to be future-proof.
powers, voltages = [0]*3, [0]*3
for singleValue in response:
address = singleValue['address']
if (address == 'meter0/Frequency'):
frequency = scale_metric(singleValue['value'], singleValue.get('unit'), 'Hz')
elif (address == 'meter0/ActivePower'):
power = scale_metric(singleValue['value'], singleValue.get('unit'), 'W')
elif (address == 'meter0/ActivePowerL1'):
powers[0] = scale_metric(singleValue['value'], singleValue.get('unit'), 'W')
elif (address == 'meter0/ActivePowerL2'):
powers[1] = scale_metric(singleValue['value'], singleValue.get('unit'), 'W')
elif (address == 'meter0/ActivePowerL3'):
powers[2] = scale_metric(singleValue['value'], singleValue.get('unit'), 'W')
elif (address == 'meter0/VoltageL1'):
voltages[0] = scale_metric(singleValue['value'], singleValue.get('unit'), 'V')
elif (address == 'meter0/VoltageL2'):
voltages[1] = scale_metric(singleValue['value'], singleValue.get('unit'), 'V')
elif (address == 'meter0/VoltageL3'):
voltages[2] = scale_metric(singleValue['value'], singleValue.get('unit'), 'V')
elif (address == '_sum/GridBuyActiveEnergy'):
imported = scale_metric(singleValue['value'], singleValue.get('unit'), 'Wh')
elif (address == '_sum/GridSellActiveEnergy'):
exported = scale_metric(singleValue['value'], singleValue.get('unit'), 'Wh')

if self.version == FemsVersion.MULTIPLE_SEGMENT_REGEX_QUERY:
# Grid meter values and grid total energy sums
response = self.get_data_by_multiple_segement_regex_query()
for singleValue in response:
address = singleValue['address']
if (address == 'meter0/Frequency'):
frequency = scale_metric(singleValue['value'], singleValue.get('unit'), 'Hz')
elif (address == 'meter0/ActivePower'):
power = scale_metric(singleValue['value'], singleValue.get('unit'), 'W')
elif (address == 'meter0/ActivePowerL1'):
powers[0] = scale_metric(singleValue['value'], singleValue.get('unit'), 'W')
elif (address == 'meter0/ActivePowerL2'):
powers[1] = scale_metric(singleValue['value'], singleValue.get('unit'), 'W')
elif (address == 'meter0/ActivePowerL3'):
powers[2] = scale_metric(singleValue['value'], singleValue.get('unit'), 'W')
elif (address == 'meter0/VoltageL1'):
voltages[0] = scale_metric(
singleValue['value'], singleValue.get('unit'), 'V')
elif (address == 'meter0/VoltageL2'):
voltages[1] = scale_metric(
singleValue['value'], singleValue.get('unit'), 'V')
elif (address == 'meter0/VoltageL3'):
voltages[2] = scale_metric(
singleValue['value'], singleValue.get('unit'), 'V')
elif (address == '_sum/GridBuyActiveEnergy'):
imported = scale_metric(singleValue['value'], singleValue.get('unit'), 'Wh')
elif (address == '_sum/GridSellActiveEnergy'):
exported = scale_metric(singleValue['value'], singleValue.get('unit'), 'Wh')
else:
# Grid meter values
response = self.session.get('http://' + self.ip_address +
':8084/rest/channel/meter0/(ActivePower.*|VoltageL.|Frequency)',
timeout=1).json()
for singleValue in response:
address = singleValue['address']
if (address == 'meter0/Frequency'):
frequency = scale_metric(singleValue['value'], singleValue.get('unit'), 'Hz')
elif (address == 'meter0/ActivePower'):
power = scale_metric(singleValue['value'], singleValue.get('unit'), 'W')
elif (address == 'meter0/ActivePowerL1'):
powers[0] = scale_metric(singleValue['value'], singleValue.get('unit'), 'W')
elif (address == 'meter0/ActivePowerL2'):
powers[1] = scale_metric(singleValue['value'], singleValue.get('unit'), 'W')
elif (address == 'meter0/ActivePowerL3'):
powers[2] = scale_metric(singleValue['value'], singleValue.get('unit'), 'W')
elif (address == 'meter0/VoltageL1'):
voltages[0] = scale_metric(singleValue['value'], singleValue.get('unit'), 'V')
elif (address == 'meter0/VoltageL2'):
voltages[1] = scale_metric(singleValue['value'], singleValue.get('unit'), 'V')
elif (address == 'meter0/VoltageL3'):
voltages[2] = scale_metric(singleValue['value'], singleValue.get('unit'), 'V')
# Grid total energy sums
response = self.session.get(
'http://'+self.ip_address+':8084/rest/channel/_sum/Grid.+ActiveEnergy',
timeout=1).json()
for singleValue in response:
address = singleValue['address']
if (address == '_sum/GridBuyActiveEnergy'):
imported = scale_metric(singleValue['value'], singleValue.get('unit'), 'Wh')
elif (address == '_sum/GridSellActiveEnergy'):
exported = scale_metric(singleValue['value'], singleValue.get('unit'), 'Wh')
counter_state = CounterState(
imported=imported,
exported=exported,
Expand All @@ -61,15 +106,17 @@ def update(self, session: Session) -> None:
except ValueError: # includes simplejson.decoder.JSONDecodeError
# nicht alle FEMS-Module unterstützen Regex-Requests
def get_value(url):
response = session.get('http://x:'+self.password+'@'+self.ip_address +
':8084/rest/channel/'+url, timeout=2).json()
response = self.session.get('http://x:'+self.password+'@'+self.ip_address +
':8084/rest/channel/'+url, timeout=2).json()
return response["value"]

power = get_value('meter0/ActivePower')
imported = get_value('_sum/GridBuyActiveEnergy')
exported = get_value('_sum/GridSellActiveEnergy')
voltages = [get_value('meter0/VoltageL1'), get_value('meter0/VoltageL2'), get_value('meter0/VoltageL3')]
currents = [get_value('meter0/CurrentL1'), get_value('meter0/CurrentL2'), get_value('meter0/CurrentL3')]
voltages = [get_value('meter0/VoltageL1'),
get_value('meter0/VoltageL2'), get_value('meter0/VoltageL3')]
currents = [get_value('meter0/CurrentL1'),
get_value('meter0/CurrentL2'), get_value('meter0/CurrentL3')]
powers = [get_value('meter0/ActivePowerL1'), get_value('meter0/ActivePowerL2'),
get_value('meter0/ActivePowerL3')]

Expand Down
13 changes: 7 additions & 6 deletions packages/modules/devices/fems/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,22 @@
def create_device(device_config: Fems):
def create_bat_component(component_config: FemsBatSetup):
return bat.FemsBat(device_config.configuration.ip_address,
component_config)
component_config, session)

def create_counter_component(component_config: FemsCounterSetup):
return counter.FemsCounter(device_config.configuration.ip_address,
component_config)
component_config, session)

def create_inverter_component(component_config: FemsInverterSetup):
return inverter.FemsInverter(device_config.configuration.ip_address,
component_config)
component_config, session)

def update_components(components: Iterable[Union[bat.FemsBat, counter.FemsCounter, inverter.FemsInverter]]):
session = req.get_http_session()
session.auth = ("x", device_config.configuration.password)
for component in components:
component.update(session)
component.update()

session = req.get_http_session()
session.auth = ("x", device_config.configuration.password)

return ConfigurableDevice(
device_config=device_config,
Expand Down
36 changes: 27 additions & 9 deletions packages/modules/devices/fems/inverter.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,48 @@
import logging
from requests import Session
from helpermodules.scale_metric import scale_metric
from modules.devices.fems.config import FemsInverterSetup
from modules.common.component_state import InverterState
from modules.common.component_type import ComponentDescriptor
from modules.common.fault_state import ComponentInfo, FaultState
from modules.common.store import get_inverter_value_store
from modules.devices.fems.version import FemsVersion, get_version
log = logging.getLogger(__name__)


class FemsInverter:
def __init__(self, ip_address: str, component_config: FemsInverterSetup) -> None:
def __init__(self, ip_address: str, component_config: FemsInverterSetup, session: Session) -> None:
self.ip_address = ip_address
self.component_config = component_config
self.session = session
self.store = get_inverter_value_store(self.component_config.id)
self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config))
self.version = get_version(self.get_data_by_multiple_segement_regex_query)
log.debug(f"{self.component_config.name} unterstützt {self.version.value}")

def update(self, session: Session) -> None:
response = session.get(
def get_data_by_multiple_segement_regex_query(self):
return self.session.get(
'http://'+self.ip_address+':8084/rest/channel/_sum/(ProductionActivePower|ProductionActiveEnergy)',
timeout=2).json()
for singleValue in response:
address = singleValue["address"]
if address == "_sum/ProductionActivePower":
power = scale_metric(singleValue['value'], singleValue.get('unit'), 'W') * -1
elif address == "_sum/ProductionActiveEnergy":
exported = scale_metric(singleValue['value'], singleValue.get('unit'), 'Wh')

def update(self) -> None:
if self.version == FemsVersion.MULTIPLE_SEGMENT_REGEX_QUERY:
response = self.get_data_by_multiple_segement_regex_query()
for singleValue in response:
address = singleValue["address"]
if address == "_sum/ProductionActivePower":
power = scale_metric(singleValue['value'], singleValue.get('unit'), 'W') * -1
elif address == "_sum/ProductionActiveEnergy":
exported = scale_metric(singleValue['value'], singleValue.get('unit'), 'Wh')
else:
response = self.session.get(
'http://'+self.ip_address+':8084/rest/channel/_sum/ProductionActivePower',
timeout=2).json()
power = scale_metric(response["value"], response.get("unit"), 'W') * -1
response = self.session.get(
'http://'+self.ip_address+':8084/rest/channel/_sum/ProductionActiveEnergy',
timeout=2).json()
exported = scale_metric(response["value"], response.get("unit"), 'Wh')
inverter_state = InverterState(
power=power,
exported=exported
Expand Down
Loading

0 comments on commit 6f581a8

Please sign in to comment.