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
3 changes: 2 additions & 1 deletion data/config/mosquitto/local/openwb_local.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# openwb-version:21
# openwb-version:22
listener 1886 localhost
allow_anonymous true

Expand Down Expand Up @@ -38,6 +38,7 @@ topic openWB/consumer/+/get/# out 2
topic openWB/consumer/+/set/# out 2

topic openWB/internal_chargepoint/# out 2
topic openWB/internal_socket/# out 2

topic openWB/io/# out 2
topic openWB/internal_io/# out 2
Expand Down
6 changes: 6 additions & 0 deletions data/config/mosquitto/public/default-dynamic-security.json
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,12 @@
"priority": 0,
"allow": true
},
{
"acltype": "publishClientReceive",
"topic": "openWB/chargepoint/+/config",
"priority": 0,
"allow": true
},
{
"acltype": "publishClientReceive",
"topic": "openWB/consumer/+/config",
Expand Down
2 changes: 1 addition & 1 deletion packages/control/consumer/consumer_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class ConsumerConfig:
rfid_enabled: bool = False
valid_tags: List[str] = field(default_factory=empty_list_factory)
rfid_reset: RfidReset = RfidReset.AFTER_TIME
rfid_timeout: int = 300
rfid_timeout: int = 3600


@dataclass
Expand Down
5 changes: 4 additions & 1 deletion packages/control/consumer/rfid.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
from helpermodules import timecheck


RFID_SUPPORTED_MODULE_TYPES = ("mqtt", "wallbox_socket")


def rfid_supported(module) -> bool:
return getattr(module, "type", None) == "mqtt"
return getattr(module, "type", None) in RFID_SUPPORTED_MODULE_TYPES


class ConsumerRfidMixin:
Expand Down
8 changes: 5 additions & 3 deletions packages/control/consumer/rfid_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,23 @@
from control.chargemode import Chargemode
from control.consumer.consumer import Consumer
from control.consumer.consumer_data import ConsumerConfig, RfidReset
from control.consumer.rfid import RFID_SUPPORTED_MODULE_TYPES
from control.consumer.usage import ConsumerUsage
from dataclass_utils import asdict, dataclass_from_dict
from helpermodules.subdata import SubData
from helpermodules.changed_values_handler import ChangedValuesHandler


@pytest.fixture
def consumer(monkeypatch):
@pytest.fixture(params=RFID_SUPPORTED_MODULE_TYPES)
def consumer(monkeypatch, request):
data.data_init(Mock())
data.data.optional_data = optional.Optional()
data.data.optional_data.data.rfid.active = True
consumer = Consumer(1)
consumer.module = Mock()
consumer.data.module = Mock(type="mqtt")
consumer.data.module = Mock(type=request.param)
consumer.data.config.rfid_enabled = True
consumer.data.config.rfid_timeout = 300
consumer.data.config.valid_tags = ["AB??", "1234"]
consumer.data.usage.type = ConsumerUsage.SUSPENDABLE_ONOFF
consumer.data.get.voltages = [230] * 3
Expand Down
13 changes: 13 additions & 0 deletions packages/helpermodules/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,16 @@ def removeChargepoint(self, connection_id: str, payload: dict) -> None:
payload, connection_id,
f'Ladepunkt mit ID \'{cp_id}\' existiert nicht.', MessageType.ERROR)
return
for consumer in SubData.consumer_data.values():
module = consumer.data.module
if (getattr(module, "type", None) == "wallbox_socket" and
module.configuration.chargepoint_id == cp_id):
pub_user_message(
payload, connection_id,
f'Ladepunkt mit ID \'{cp_id}\' kann nicht gelöscht werden, da er der Wallbox-Steckdose '
f'\'{module.name}\' zugeordnet ist. Bitte zuerst die Zuordnung bei der Wallbox-Steckdose ändern.',
MessageType.ERROR)
return
cp_type = cp.chargepoint.data.config.type
if cp_type == "external_openwb":
cp_ip = cp.chargepoint.data.config.configuration.get("ip_address")
Expand Down Expand Up @@ -1251,6 +1261,9 @@ def removeConsumer(self, connection_id: str, payload: dict) -> None:
payload, connection_id,
f'Die ID \'{payload["data"]["consumer_id"]}\' ist größer als die maximal vergebene '
f'ID \'{self.max_id_hierarchy}\'.', MessageType.ERROR)
consumer = SubData.consumer_data.get(f'consumer{payload["data"]["consumer_id"]}')
if consumer is not None and getattr(consumer.data.module, "type", None) == "wallbox_socket":
consumer.module.switch_off()
ProcessBrokerBranch(f'consumer/{payload["data"]["consumer_id"]}/').remove_topics()
SubData.counter_all_data.hierarchy_remove_item(payload["data"]["consumer_id"])
Pub().pub("openWB/set/counter/get/hierarchy", SubData.counter_all_data.data.get.hierarchy)
Expand Down
19 changes: 19 additions & 0 deletions packages/helpermodules/command_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,22 @@ def test_get_max_id_hierarchy_by_topics(mock_pub, monkeypatch):

assert command_instance.max_id_hierarchy == 9
mock_pub.pub.assert_any_call("openWB/set/command/max_id/hierarchy", 9)


@pytest.mark.parametrize("consumer_type", ["wallbox_socket", "mqtt"])
def test_remove_wallbox_socket_switches_off_before_removing_topics(consumer_type, mock_pub, monkeypatch):
order = []
consumer = SimpleNamespace(data=SimpleNamespace(module=SimpleNamespace(type=consumer_type)), module=Mock())
consumer.module.switch_off.side_effect = lambda: order.append("off")
branch = Mock()
branch.remove_topics.side_effect = lambda: order.append("remove")
monkeypatch.setattr(command, "ProcessBrokerBranch", Mock(return_value=branch))
monkeypatch.setattr(command, "pub_user_message", Mock())
monkeypatch.setattr(SubData, "consumer_data", {"consumer2": consumer})
monkeypatch.setattr(SubData, "counter_all_data", Mock())
monkeypatch.setattr(SubData, "system_data", {
"system": SimpleNamespace(data={"security": {"user_management_active": False}})})
instance = Command.__new__(Command)
instance.max_id_hierarchy = 2
instance.removeConsumer("test", {"data": {"consumer_id": 2}})
assert order == (["off", "remove"] if consumer_type == "wallbox_socket" else ["remove"])
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,20 @@ def _get_data_path() -> Path:
'openWB/internal_chargepoint/+/get/state_str',
'openWB/internal_chargepoint/+/get/rfid',
'openWB/internal_chargepoint/+/get/rfid_timestamp',
'openWB/internal_socket/data/set_state',
'openWB/internal_socket/data/detected',
'openWB/internal_socket/data/last_tag',
'openWB/internal_socket/get/state',
'openWB/internal_socket/get/rfid',
'openWB/internal_socket/get/power',
'openWB/internal_socket/get/imported',
'openWB/internal_socket/get/exported',
'openWB/internal_socket/get/currents',
'openWB/internal_socket/get/voltages',
'openWB/internal_socket/get/powers',
'openWB/internal_socket/get/fault_state',
'openWB/internal_socket/get/fault_str',
'openWB/internal_socket/get/error_timestamp',
# abgesehen vom Fahrzeug können mqtt get-Topics bei aktiver BV nur gepublished werden
'openWB/mqtt/bat/+/get/power',
'openWB/mqtt/bat/+/get/soc',
Expand Down
22 changes: 22 additions & 0 deletions packages/helpermodules/setdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ def on_message(self, client: mqtt.Client, userdata, msg: mqtt.MQTTMessage):
self.process_command_topic(msg)
elif "openWB/set/internal_chargepoint/" in msg.topic:
self.process_internal_chargepoint_topic(msg)
elif "openWB/set/internal_socket/" in msg.topic:
self.process_wallbox_socket_topic(msg)
elif "openWB/set/LegacySmartHome/" in msg.topic:
self.process_legacy_smart_home_topic(msg)
elif "openWB/set/consumer/" in msg.topic:
Expand Down Expand Up @@ -1235,6 +1237,26 @@ def process_internal_chargepoint_topic(self, msg):
except Exception:
log.exception(f"Fehler im setdata-Modul: Topic {msg.topic}, Value: {msg.payload}")

def process_wallbox_socket_topic(self, msg):
try:
suffix = msg.topic.removeprefix("openWB/set/internal_socket/")
if suffix in ("data/set_state", "data/detected", "get/state"):
self._validate_value(msg, bool)
elif suffix == "get/error_timestamp":
self._validate_value(msg, float, [(0, TIMESTAMP_2100)])
elif suffix == "get/fault_state":
self._validate_value(msg, int, [(0, 2)])
elif suffix in ("data/last_tag", "get/fault_str", "get/rfid"):
self._validate_value(msg, str)
elif suffix in ("get/power", "get/imported", "get/exported"):
self._validate_value(msg, float)
elif suffix in ("get/currents", "get/voltages", "get/powers"):
self._validate_value(msg, "json")
else:
self.__unknown_topic(msg)
except Exception:
log.exception(f"Fehler im setdata-Modul: Topic {msg.topic}, Value: {msg.payload}")

def process_legacy_smart_home_topic(self, msg):
"""Handler für die alten SmartHome-Topics

Expand Down
14 changes: 14 additions & 0 deletions packages/helpermodules/subdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
from modules.internal_chargepoint_handler.internal_chargepoint_handler_config import (
GlobalHandlerData, InternalChargepoint, RfidData)
from modules.vehicles.manual.config import ManualSoc
from modules.internal_chargepoint_handler.wallbox_socket_config import WallboxSocket

log = logging.getLogger(__name__)
mqtt_log = logging.getLogger("mqtt")
Expand Down Expand Up @@ -76,6 +77,7 @@ class SubData:
"global_data": GlobalHandlerData(),
"rfid_data": RfidData()}
io_actions = io_device.IoActions()
wallbox_socket_data = WallboxSocket()
io_states: Dict[str, io_device.IoStates] = {}
optional_data = optional.Optional()
system_data = {"system": system.System()}
Expand Down Expand Up @@ -157,6 +159,7 @@ def on_connect(self, client: mqtt.Client, userdata, flags: dict, rc: int):
("openWB/consumer/+/control_parameter/#", 2),
("openWB/command/command_completed", 2),
("openWB/internal_chargepoint/#", 2),
("openWB/internal_socket/#", 2),
# MQTT Bridge Topics vor "openWB/system/+" abonnieren, damit sie auch vor
# "openWB/system/subdata_initialized" empfangen werden!
("openWB/system/mqtt/bridge/+", 2),
Expand Down Expand Up @@ -205,6 +208,8 @@ def on_message(self, client: mqtt.Client, userdata, msg: mqtt.MQTTMessage):
self.process_io_topic(self.io_states, msg)
elif "openWB/internal_chargepoint/" in msg.topic:
self.process_internal_chargepoint_topic(client, self.internal_chargepoint_data, msg)
elif "openWB/internal_socket/" in msg.topic:
self.process_wallbox_socket_topic(msg)
elif "openWB/optional/" in msg.topic:
self.process_optional_topic(self.optional_data, msg)
elif "openWB/counter/" in msg.topic:
Expand Down Expand Up @@ -1250,6 +1255,15 @@ def set_internal_chargepoint_configured(self):
internal_configured = False
self.internal_chargepoint_data["global_data"].configured = internal_configured

def process_wallbox_socket_topic(self, msg):
try:
if msg.topic.startswith("openWB/internal_socket/data/"):
self.set_json_payload_class(self.wallbox_socket_data.data, msg)
elif msg.topic.startswith("openWB/internal_socket/get/"):
self.set_json_payload_class(self.wallbox_socket_data.get, msg)
except Exception:
log.exception("Fehler im subdata-Modul für die Wallbox-Steckdose")

def process_legacy_smarthome_topic(self, client: mqtt.Client, var: counter_all.CounterAll, msg: mqtt.MQTTMessage):
""" Handler für die SmartHome-Topics des alten

Expand Down
15 changes: 15 additions & 0 deletions packages/helpermodules/update_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,21 @@ class UpdateConfig:
"^openWB/internal_chargepoint/[0-1]/get/rfid$",
"^openWB/internal_chargepoint/[0-1]/get/rfid_timestamp$",

"^openWB/internal_socket/data/set_state$",
"^openWB/internal_socket/data/detected$",
"^openWB/internal_socket/data/last_tag$",
"^openWB/internal_socket/get/state$",
"^openWB/internal_socket/get/rfid$",
"^openWB/internal_socket/get/power$",
"^openWB/internal_socket/get/imported$",
"^openWB/internal_socket/get/exported$",
"^openWB/internal_socket/get/currents$",
"^openWB/internal_socket/get/voltages$",
"^openWB/internal_socket/get/powers$",
"^openWB/internal_socket/get/fault_state$",
"^openWB/internal_socket/get/fault_str$",
"^openWB/internal_socket/get/error_timestamp$",

"^openWB/io/action/[0-9]+/config$",
"^openWB/io/action/[0-9]+/timestamp$",
"^openWB/io/states/[0-9]+/get/analog_input$",
Expand Down
3 changes: 3 additions & 0 deletions packages/modules/common/component_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class ComponentType(Enum):
COUNTER = "counter"
FLEXIBLE_TARIFF = "dynamic_tariff"
GRID_FEE = "grid_tariff"
WALLBOX_SOCKET = "wallbox_socket"
INVERTER = "inverter"
IO = "io"
VEHICLE = "vehicle"
Expand Down Expand Up @@ -37,6 +38,8 @@ def type_to_topic_mapping(component_type: str) -> str:
return "consumer"
elif "counter" in component_type:
return "counter"
elif "wallbox_socket" in component_type:
return "internal_socket"
elif "inverter" in component_type:
return "pv"
elif ComponentType.FLEXIBLE_TARIFF.value in component_type:
Expand Down
10 changes: 5 additions & 5 deletions packages/modules/common/configurable_consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,21 +92,21 @@ def update(self):
self.store.set(consumer_state)

def set_power_limit(self, power_limit: float, data: SetLimitData) -> None:
with SingleComponentUpdateContext(self.fault_state):
with SingleComponentUpdateContext(self.fault_state, update_always=False):
self.module_set_power_limit(power_limit, data)

def switch_on(self) -> None:
with SingleComponentUpdateContext(self.fault_state):
with SingleComponentUpdateContext(self.fault_state, update_always=False):
self.module_switch_on()

def switch_off(self) -> None:
with SingleComponentUpdateContext(self.fault_state):
with SingleComponentUpdateContext(self.fault_state, update_always=False):
self.module_switch_off()

def send_values(self, values: CurrentValues) -> None:
with SingleComponentUpdateContext(self.fault_state):
with SingleComponentUpdateContext(self.fault_state, update_always=False):
self.module_send_values(values)

def clear_rfid(self) -> None:
with SingleComponentUpdateContext(self.fault_state):
with SingleComponentUpdateContext(self.fault_state, update_always=False):
self.module_clear_rfid()
3 changes: 2 additions & 1 deletion packages/modules/common/fault_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ def store_error(self) -> None:
traceback.format_exc())
topic = component_type.type_to_topic_mapping(self.component_info.type)
if (self.component_info.type == component_type.ComponentType.FLEXIBLE_TARIFF.value or
self.component_info.type == component_type.ComponentType.GRID_FEE.value):
self.component_info.type == component_type.ComponentType.GRID_FEE.value or
self.component_info.type == component_type.ComponentType.WALLBOX_SOCKET.value):
topic_prefix = f"openWB/set/{topic}"
else:
topic_prefix = f"openWB/set/{topic}/{self.component_info.id}"
Expand Down
Loading
Loading