Skip to content
10 changes: 6 additions & 4 deletions packages/modules/chargepoints/openwb_pro/chargepoint_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from modules.common.fault_state import ComponentInfo, FaultState
from modules.common.hardware_check import check_meter_values
from modules.common.store import get_chargepoint_value_store
from modules.common.component_state import ChargepointState
from modules.common.component_state import ChargepointState, CounterState
from modules.common import req
from modules.internal_chargepoint_handler.internal_chargepoint_handler_config import InternalChargepoint

Expand Down Expand Up @@ -82,9 +82,11 @@ def request_values(self) -> ChargepointState:
)

if json_rsp.get("voltages"):
meter_msg = check_meter_values(json_rsp["voltages"])
if meter_msg:
self.fault_state.warning(meter_msg)
check_meter_values(CounterState(voltages=json_rsp["voltages"],
currents=json_rsp["currents"],
powers=json_rsp["powers"],
power=json_rsp["power_all"]),
self.fault_state)
chargepoint_state.voltages = json_rsp["voltages"]
if json_rsp.get("soc_value"):
chargepoint_state.soc = json_rsp["soc_value"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ def __init__(self, config: OpenWBseries2Satellit) -> None:
f"openWB/set/chargepoint/{self.config.id}/get/error_timestamp", CP_ERROR, hide_exception=True)
self._create_client()
self._validate_version()
self.max_evse_current = self._client.evse_client.get_max_current()

def delay_second_cp(self, delay: float):
if self.config.configuration.duo_num == 0:
Expand Down Expand Up @@ -79,23 +78,24 @@ def get_values(self) -> None:
try:
self.delay_second_cp(self.CP1_DELAY)
with self._client.client, self.client_error_context:
self._client.check_hardware(self.fault_state)
evse_state, counter_state = self._client.request_and_check_hardware(self.fault_state)
if self.version is False:
self._validate_version()
currents = self._client.meter_client.get_currents()

currents = counter_state.currents
phases_in_use = sum(1 for current in currents if current > 3)
plug_state, charge_state, _ = self._client.evse_client.get_plug_charge_state()

chargepoint_state = ChargepointState(
power=self._client.meter_client.get_power()[1],
power=counter_state.power,
currents=currents,
imported=self._client.meter_client.get_imported(),
imported=counter_state.imported,
exported=0,
voltages=self._client.meter_client.get_voltages(),
plug_state=plug_state,
charge_state=charge_state,
voltages=counter_state.voltages,
plug_state=evse_state.plug_state,
charge_state=evse_state.charge_state,
phases_in_use=phases_in_use,
serial_number=self._client.meter_client.get_serial_number(),
max_evse_current=self.max_evse_current
serial_number=counter_state.serial_number,
max_evse_current=evse_state.max_current
)
self.store.set(chargepoint_state)
self.client_error_context.reset_error_counter()
Expand All @@ -119,7 +119,6 @@ def set_current(self, current: float) -> None:
try:
self.delay_second_cp(self.CP1_DELAY)
with self._client.client:
self._client.check_hardware(self.fault_state)
if self.version:
self._client.evse_client.set_current(int(current))
else:
Expand All @@ -134,7 +133,6 @@ def switch_phases(self, phases_to_use: int, duration: int) -> None:
with self.client_error_context:
try:
with self._client.client:
self._client.check_hardware(self.fault_state)
if phases_to_use == 1:
self._client.client.delegate.write_register(
0x0001, 256, unit=self.ID_PHASE_SWITCH_UNIT)
Expand Down
10 changes: 10 additions & 0 deletions packages/modules/common/component_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def __init__(
powers: Optional[List[Optional[float]]] = None,
power_factors: Optional[List[Optional[float]]] = None,
frequency: float = 50,
serial_number: str = "",
):
"""Args:
imported: total imported energy in Wh
Expand All @@ -107,6 +108,7 @@ def __init__(
self.exported = exported
self.power = power
self.frequency = frequency
self.serial_number = serial_number


@auto_str
Expand Down Expand Up @@ -234,3 +236,11 @@ def __init__(self, analog_input: Dict[str, float] = None,
self.digital_input = digital_input
self.analog_output = analog_output
self.digital_output = digital_output


class EvseState:
def __init__(self, plug_state: bool, charge_state: bool, set_current: int, max_current: int) -> None:
self.plug_state = plug_state
self.charge_state = charge_state
self.set_current = set_current
self.max_current = max_current
37 changes: 26 additions & 11 deletions packages/modules/common/evse.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
from helpermodules.logger import ModifyLoglevelContext

from modules.common import modbus
from modules.common.component_state import EvseState
from modules.common.modbus import ModbusDataType

log = logging.getLogger(__name__)


class EvseState(IntEnum):
class EvseStatusCode(IntEnum):
READY = (1, False, False)
EV_PRESENT = (2, True, False)
CHARGING = (3, True, True)
Expand All @@ -32,6 +33,19 @@ class Evse:
def __init__(self, modbus_id: int, client: modbus.ModbusSerialClient_) -> None:
self.client = client
self.id = modbus_id
with client:
time.sleep(0.1)
self.version = self.client.read_holding_registers(1005, ModbusDataType.UINT_16, unit=self.id)
time.sleep(0.1)
self.max_current = self.client.read_holding_registers(2007, ModbusDataType.UINT_16, unit=self.id)
with ModifyLoglevelContext(log, logging.DEBUG):
log.debug(f"Firmware-Version der EVSE: {self.version}")
if self.version < 17:
self._precise_current = False
else:
if self.is_precise_current_active() is False:
self.activate_precise_current()
self._precise_current = self.is_precise_current_active()

def get_plug_charge_state(self) -> Tuple[bool, bool, float]:
time.sleep(0.1)
Expand All @@ -41,8 +55,8 @@ def get_plug_charge_state(self) -> Tuple[bool, bool, float]:
set_current = int(set_current)
log.debug("Gesetzte Stromstärke EVSE: "+str(set_current) +
", Status: "+str(state_number)+", Modbus-ID: "+str(self.id))
state = EvseState(state_number)
if state == EvseState.FAILURE:
state = EvseStatusCode(state_number)
if state == EvseStatusCode.FAILURE:
raise ValueError("Unbekannter Zustand der EVSE: State " +
str(state)+", Soll-Stromstärke: "+str(set_current))
plugged = state.plugged
Expand All @@ -52,9 +66,15 @@ def get_plug_charge_state(self) -> Tuple[bool, bool, float]:
return plugged, charging, set_current

def get_firmware_version(self) -> int:
time.sleep(0.1)
version = self.client.read_holding_registers(1005, ModbusDataType.UINT_16, unit=self.id)
return version
return self.version

def get_evse_state(self) -> EvseState:
plugged, charging, set_current = self.get_plug_charge_state()
state = EvseState(plug_state=plugged,
charge_state=charging,
set_current=set_current,
max_current=self.max_current)
return state

def is_precise_current_active(self) -> bool:
time.sleep(0.1)
Expand Down Expand Up @@ -92,8 +112,3 @@ def deactivate_precise_current(self) -> None:
def set_current(self, current: int) -> None:
time.sleep(0.1)
self.client.write_registers(1000, current, unit=self.id)

def get_max_current(self) -> int:
time.sleep(0.1)
current = self.client.read_holding_registers(2007, ModbusDataType.UINT_16, unit=self.id)
return current
66 changes: 45 additions & 21 deletions packages/modules/common/hardware_check.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pymodbus
from typing import Any, List, Optional, Protocol, Tuple, Union
from typing import Any, Optional, Protocol, Tuple, Union

from modules.common.component_state import CounterState, EvseState
from modules.common.evse import Evse
from modules.common.fault_state import FaultState
from modules.common.modbus import ModbusSerialClient_, ModbusTcpClient_
Expand All @@ -15,25 +16,36 @@
LAN_ADAPTER_BROKEN = (f"{RS485_ADAPTER_BROKEN.format('der LAN-Konverter abgestürzt,')} "
"Bitte den openWB series2 satellit stromlos machen.")
METER_PROBLEM = "Der Zähler konnte nicht ausgelesen werden. Vermutlich ist der Zähler falsch konfiguriert oder defekt."
METER_BROKEN = "Die Spannungen des Zählers konnten nicht korrekt ausgelesen werden: {}V Der Zähler ist defekt."
METER_BROKEN_VOLTAGES = "Die Spannungen des Zählers konnten nicht korrekt ausgelesen werden: {}V Der Zähler ist defekt."
METER_NO_SERIAL_NUMBER = ("Die Seriennummer des Zählers für das Ladelog kann nicht ausgelesen werden. Wenn Sie die "
"Seriennummer für Abrechnungszwecke benötigen, wenden Sie sich bitte an unseren Support. Die "
"Funktionalität wird dadurch nicht beeinträchtigt!")
EVSE_BROKEN = ("Auslesen der EVSE nicht möglich. Vermutlich ist die EVSE defekt oder hat eine unbekannte Modbus-ID. "
"(Fehlermeldung nur relevant, wenn diese auf der Startseite oder im Status angezeigt wird.)")
METER_IMPLAUSIBLE_VALUE = ("Der Zähler hat einen unplausiblen Wert zurückgegeben: Leistungen {}W, Ströme {}A, "
"Spannungen {}V.")


def check_meter_values(voltages: List[float]) -> Optional[str]:
def check_meter_values(counter_state: CounterState, fault_state: Optional[FaultState] = None) -> None:
meter_msg = _check_meter_values(counter_state)
if fault_state and meter_msg:
fault_state.warning(meter_msg)


def _check_meter_values(counter_state: CounterState) -> Optional[str]:
def valid_voltage(voltage) -> bool:
return 200 < voltage < 260
if ((valid_voltage(voltages[0]) and voltages[1] == 0 and voltages[2] == 0) or
return 200 < voltage < 250
voltages = counter_state.voltages
if not ((valid_voltage(voltages[0]) and voltages[1] == 0 and voltages[2] == 0) or
# Zoe lädt einphasig an einphasiger Wallbox und erzeugt Spannung auf L2 (ca 126V)
(valid_voltage(voltages[0]) and 115 < voltages[1] < 135 and voltages[2] == 0) or
(valid_voltage(voltages[0]) and valid_voltage(voltages[1]) and voltages[2] == 0) or
(valid_voltage(voltages[0]) and valid_voltage(voltages[1]) and valid_voltage((voltages[2])))):
return None
else:
return METER_BROKEN.format(voltages)
return METER_BROKEN_VOLTAGES.format(voltages)
interdependent_values = [sum(counter_state.currents), counter_state.power]
if not (all(v == 0 for v in interdependent_values) or all(v != 0 for v in interdependent_values)):
return METER_IMPLAUSIBLE_VALUE.format(counter_state.powers, counter_state.currents, counter_state.voltages)
return None


class ClientHandlerProtocol(Protocol):
Expand All @@ -49,6 +61,12 @@ def evse_client(self) -> Evse: ...
def meter_client(self) -> Any: ...
@property
def read_error(self) -> int: ...
@property
def handle_exception(self, exception: Exception) -> bool: ...
@property
def request_and_check_hardware(self, fault_state: FaultState) -> Tuple[EvseState, CounterState]: ...
@property
def check_meter(self) -> Tuple[bool, Optional[str], CounterState]: ...


class SeriesHardwareCheckMixin:
Expand All @@ -63,16 +81,20 @@ def handle_exception(self: ClientHandlerProtocol, exception: Exception):
else:
return False

def check_hardware(self: ClientHandlerProtocol, fault_state: FaultState):

def request_and_check_hardware(self: ClientHandlerProtocol,
fault_state: FaultState) -> Tuple[EvseState, CounterState]:
try:
if self.evse_client.get_firmware_version() > EVSE_MIN_FIRMWARE:
evse_check_passed = True
else:
evse_check_passed = False
with self.client:
evse_state = self.evse_client.get_evse_state()
evse_check_passed = True
except Exception as e:
evse_check_passed = self.handle_exception(e)
meter_check_passed, meter_error_msg = self.check_meter()
meter_check_passed, meter_error_msg, counter_state = self.check_meter()
if meter_check_passed is False and evse_check_passed is False:
if isinstance(self.client, ModbusTcpClient_):
raise Exception(LAN_ADAPTER_BROKEN)
else:
raise Exception(USB_ADAPTER_BROKEN)
if meter_check_passed is False:
if evse_check_passed is False:
if isinstance(self.client, ModbusTcpClient_):
Expand All @@ -90,12 +112,14 @@ def check_hardware(self: ClientHandlerProtocol, fault_state: FaultState):
raise Exception(EVSE_BROKEN + " " + meter_error_msg + OPEN_TICKET)
else:
raise Exception(EVSE_BROKEN + OPEN_TICKET)
return evse_state, counter_state

def check_meter(self: ClientHandlerProtocol) -> Tuple[bool, Optional[str]]:
def check_meter(self: ClientHandlerProtocol) -> Tuple[bool, Optional[str], CounterState]:
try:
serial_number = self.meter_client.get_serial_number()
if serial_number == "0" or serial_number is None:
return True, METER_NO_SERIAL_NUMBER
return True, check_meter_values(self.meter_client.get_voltages())
with self.client:
counter_state = self.meter_client.get_counter_state()
if counter_state.serial_number == "0" or counter_state.serial_number is None:
return True, METER_NO_SERIAL_NUMBER, counter_state
return True, _check_meter_values(counter_state), counter_state
except Exception:
return False, METER_PROBLEM
return False, METER_PROBLEM, None
Loading