diff --git a/src/CONFIG_README.md b/src/CONFIG_README.md
index 31d4a6a4..9c204206 100644
--- a/src/CONFIG_README.md
+++ b/src/CONFIG_README.md
@@ -87,7 +87,7 @@ Feel free to add more PV forecast entries under the pv_forecast section by provi
### Inverter Configuration Settings
-- **inverter.type**: default: fronius_gen24 - currently not used
+- **inverter.type**: fronius_gen24 or default
- **inverter.address**: address of the inverter - e.g. 192.168.1.12
- **inverter.user**: username in local portal e.g. customer (inverter local webpage login data - be aware: website Customer vs. customer in this level)
- **inverter.password**: password for local portal
diff --git a/src/eos_connect.py b/src/eos_connect.py
index b294eb7e..6467df6a 100644
--- a/src/eos_connect.py
+++ b/src/eos_connect.py
@@ -113,6 +113,8 @@ def charging_state_callback(new_state):
"""
# update the base control with the new charging state
base_control.set_current_evcc_charging_state(evcc_interface.get_charging_state())
+ base_control.set_current_evcc_charging_mode(evcc_interface.get_charging_mode())
+
logger.info("[MAIN] EVCC Event - Charging state changed to: %s", new_state)
change_control_state()
@@ -579,6 +581,7 @@ def setting_control_data(ac_charge_demand_rel, dc_charge_demand_rel, discharge_a
base_control.set_current_battery_soc(battery_interface.get_current_soc())
# getting the current charging state from evcc
base_control.set_current_evcc_charging_state(evcc_interface.get_charging_state())
+ base_control.set_current_evcc_charging_mode(evcc_interface.get_charging_mode())
def change_control_state():
@@ -642,7 +645,7 @@ def change_control_state():
base_control.get_current_overall_state(), "unknown state"
),
)
- # MODE_AVOID_DISCHARGE_EVCC
+ # MODE_AVOID_DISCHARGE_EVCC_FAST
elif base_control.get_current_overall_state() == 3:
if inverter_en:
inverter_interface.set_mode_avoid_discharge()
@@ -652,6 +655,15 @@ def change_control_state():
base_control.get_current_overall_state(), "unknown state"
),
)
+ elif base_control.get_current_overall_state() == 4:
+ if inverter_en:
+ inverter_interface.set_mode_allow_discharge()
+ logger.info(
+ "[Main] Inverter mode set to %s (_____-+-+-_____)",
+ base_control.get_state_mapping().get(
+ base_control.get_current_overall_state(), "unknown state"
+ ),
+ )
elif base_control.get_current_overall_state() < 0:
logger.warning("[Main] Inverter mode not initialized yet")
return True
@@ -726,6 +738,7 @@ def get_controls():
current_battery_soc = battery_interface.get_current_soc()
base_control.set_current_battery_soc(current_battery_soc)
current_inverter_mode = base_control.get_current_overall_state(False)
+ current_inverter_mode_num = base_control.get_current_overall_state()
response_data = {
"current_states": {
@@ -733,12 +746,18 @@ def get_controls():
"current_dc_charge_demand": current_dc_charge_demand,
"current_discharge_allowed": current_discharge_allowed,
"inverter_mode": current_inverter_mode,
- "evcc_charging_state": base_control.get_current_evcc_charging_state(),
+ "inverter_mode_num": current_inverter_mode_num,
+ },
+ "evcc": {
+ "charging_state": base_control.get_current_evcc_charging_state(),
+ "charging_mode": base_control.get_current_evcc_charging_mode(),
+ },
+ "battery": {
+ "soc": current_battery_soc,
+ "max_charge_power_dyn": battery_interface.get_max_charge_power_dyn(),
},
- "battery_soc": current_battery_soc,
- "battery_max_charge_power_dyn": battery_interface.get_max_charge_power_dyn(),
- "timestamp": datetime.now(time_zone).isoformat(),
"state": optimization_scheduler.get_current_state(),
+ "timestamp": datetime.now(time_zone).isoformat(),
}
return Response(json.dumps(response_data, indent=4), content_type="application/json")
diff --git a/src/interfaces/base_control.py b/src/interfaces/base_control.py
index 1e82ff31..d77cf19a 100644
--- a/src/interfaces/base_control.py
+++ b/src/interfaces/base_control.py
@@ -14,15 +14,18 @@
MODE_CHARGE_FROM_GRID = 0
MODE_AVOID_DISCHARGE = 1
MODE_DISCHARGE_ALLOWED = 2
-MODE_AVOID_DISCHARGE_EVCC = 3
+MODE_AVOID_DISCHARGE_EVCC_FAST = 3
+MODE_DISCHARGE_ALLOWED_EVCC_PV = 4
state_mapping = {
0: "MODE_CHARGE_FROM_GRID",
1: "MODE_AVOID_DISCHARGE",
2: "MODE_DISCHARGE_ALLOWED",
- 3: "MODE_AVOID_DISCHARGE_EVCC",
+ 3: "MODE_AVOID_DISCHARGE_EVCC_FAST",
+ 4: "MODE_DISCHARGE_ALLOWED_EVCC_PV",
}
+
class BaseControl:
"""
BaseControl is a class that manages the state and demands of a control system.
@@ -37,6 +40,7 @@ def __init__(self, config, timezone):
self.current_dc_charge_demand = 0
self.current_discharge_allowed = 1
self.current_evcc_charging_state = False
+ self.current_evcc_charging_mode = False
# startup with None to force a writing to the inverter
self.current_overall_state = None
self.current_battery_soc = 0
@@ -91,6 +95,12 @@ def get_current_overall_state(self, number=True):
# Return the string representation of the state
return state_mapping.get(self.current_overall_state, "unknown state")
+ def get_current_overall_state_number(self):
+ """
+ Returns the current overall state as a number.
+ """
+ return self.current_overall_state
+
def get_current_battery_soc(self):
"""
Returns the current battery state of charge (SOC).
@@ -103,6 +113,12 @@ def get_current_evcc_charging_state(self):
"""
return self.current_evcc_charging_state
+ def get_current_evcc_charging_mode(self):
+ """
+ Returns the current EVCC charging mode.
+ """
+ return self.current_evcc_charging_mode
+
def set_current_ac_charge_demand(self, value_relative):
"""
Sets the current AC charge demand.
@@ -158,6 +174,14 @@ def set_current_evcc_charging_state(self, value):
# logger.debug("[BASE_CTRL] set current EVCC charging state to %s", value)
self.set_current_overall_state()
+ def set_current_evcc_charging_mode(self, value):
+ """
+ Sets the current EVCC charging mode.
+ """
+ self.current_evcc_charging_mode = value
+ # logger.debug("[BASE_CTRL] set current EVCC charging mode to %s", value)
+ self.set_current_overall_state()
+
def set_current_overall_state(self):
"""
Sets the current overall state and logs the timestamp if it changes.
@@ -173,9 +197,13 @@ def set_current_overall_state(self):
self.current_ac_charge_demand != self.last_ac_charge_demand
)
- # override overall state if EVCC charging state is active and discharge is allowed
- if new_state == MODE_DISCHARGE_ALLOWED and self.current_evcc_charging_state:
- new_state = MODE_AVOID_DISCHARGE_EVCC
+ # override overall state if EVCC charging state is active and in mode fast charge and discharge is allowed
+ if (
+ new_state == MODE_DISCHARGE_ALLOWED
+ and self.current_evcc_charging_state
+ and self.current_evcc_charging_mode == "now"
+ ):
+ new_state = MODE_AVOID_DISCHARGE_EVCC_FAST
logger.info(
"[BASE_CTRL] EVCC charging state is active,"
+ " setting overall state to MODE_AVOID_DISCHARGE"
diff --git a/src/interfaces/evcc_interface.py b/src/interfaces/evcc_interface.py
index e81e4cef..9e57bed2 100644
--- a/src/interfaces/evcc_interface.py
+++ b/src/interfaces/evcc_interface.py
@@ -1,21 +1,26 @@
-'''
-This module provides the `EvccInterface` class, which serves as an interface to interact
-with the Electric Vehicle Charging Controller (EVCC) API. The class enables periodic
-fetching of the charging state and triggers a callback when the state changes.
+"""
+This module provides the `EvccInterface` class, which serves as an interface to interact
+with the Electric Vehicle Charging Controller (EVCC) API. The class enables periodic
+fetching of the charging state and charging mode, and triggers a callback when either
+state changes.
+
Classes:
- EvccInterface: A class to interact with the EVCC API, manage charging state updates,
- and handle state change callbacks.
+ EvccInterface: A class to interact with the EVCC API, manage charging state and mode
+ updates, and handle state change callbacks.
+
Dependencies:
- logging: For logging messages and errors.
- threading: For managing background threads.
- time: For implementing delays in the update loop.
- requests: For making HTTP requests to the EVCC API.
+
Usage:
- Create an instance of the `EvccInterface` class by providing the EVCC API URL,
- an optional update interval, and a callback function to handle charging state changes.
- The class will automatically start a background thread to periodically fetch the
- charging state from the API.
-'''
+ Create an instance of the `EvccInterface` class by providing the EVCC API URL,
+ an optional update interval, and a callback function to handle charging state or
+ mode changes. The class will automatically start a background thread to periodically
+ fetch the charging state and mode from the API.
+"""
+
import logging
import threading
import time
@@ -26,27 +31,33 @@
class EvccInterface:
- '''
- EvccInterface is a class that provides an interface to interact with the EVCC
- (Electric Vehicle Charging Controller) API.
- It periodically fetches the charging state and triggers a callback when the state changes.
- Attributes:
- last_known_charging_state (bool): The last known charging state.
- on_charging_state_change (callable): A callback function to be called when
- the charging state changes.
- _update_thread (threading.Thread): The background thread for updating
- the charging state.
- _stop_event (threading.Event): An event to signal the thread to stop.
- Methods:
- __init__(url, update_interval=15, on_charging_state_change=None):
- get_charging_state():
- start_update_service():
- shutdown():
- _update_charging_state_loop():
- request_charging_state():
- Fetches the EVCC state from the API and updates the charging state.
- fetch_evcc_state_via_api():
- '''
+ """
+ EvccInterface is a class that provides an interface to interact with the EVCC
+ (Electric Vehicle Charging Controller) API.
+ It periodically fetches the charging state and mode, and triggers a callback when
+ either the state or mode changes.
+
+ Attributes:
+ last_known_charging_state (bool): The last known charging state.
+ last_known_charging_mode (str): The last known charging mode.
+ on_charging_state_change (callable): A callback function to be called when
+ the charging state or mode changes.
+ _update_thread (threading.Thread): The background thread for updating
+ the charging state and mode.
+ _stop_event (threading.Event): An event to signal the thread to stop.
+
+ Methods:
+ __init__(url, update_interval=15, on_charging_state_change=None):
+ get_charging_state():
+ get_charging_mode():
+ start_update_service():
+ shutdown():
+ _update_charging_state_loop():
+ __request_charging_state():
+ Fetches the EVCC state from the API and updates the charging state and mode.
+ fetch_evcc_state_via_api():
+ """
+
def __init__(self, url, update_interval=15, on_charging_state_change=None):
"""
Initializes the EVCC interface and starts the update service.
@@ -59,6 +70,8 @@ def __init__(self, url, update_interval=15, on_charging_state_change=None):
"""
self.url = url
self.last_known_charging_state = False
+ # off, pv, pvmin, now
+ self.last_known_charging_mode = None
self.update_interval = update_interval
self.on_charging_state_change = on_charging_state_change # Store the callback
self._update_thread = None
@@ -70,6 +83,11 @@ def get_charging_state(self):
Returns the last known charging state.
"""
return self.last_known_charging_state
+ def get_charging_mode(self):
+ """
+ Returns the last known charging mode.
+ """
+ return self.last_known_charging_mode
def start_update_service(self):
"""
@@ -98,10 +116,10 @@ def _update_charging_state_loop(self):
"""
while not self._stop_event.is_set():
try:
- self.request_charging_state()
+ self.__request_charging_state()
except (requests.exceptions.RequestException, ValueError, KeyError) as e:
logger.error("[EVCC] Error while updating charging state: %s", e)
- # Break the sleep interval into smaller chunks to allow immediate shutdown
+ # Break the sleep interval into smaller chunks to allow immediate shutdown
sleep_interval = self.update_interval
while sleep_interval > 0:
if self._stop_event.is_set():
@@ -111,7 +129,7 @@ def _update_charging_state_loop(self):
self.start_update_service()
- def request_charging_state(self):
+ def __request_charging_state(self):
"""
Fetches the EVCC state from the API and returns the charging state.
"""
@@ -119,23 +137,40 @@ def request_charging_state(self):
if not data or not isinstance(data.get("result", {}).get("loadpoints"), list):
logger.error("[EVCC] Invalid or missing loadpoints in the response.")
return None
- loadpoint = data["result"]["loadpoints"][0] if data["result"]["loadpoints"] else None
+ loadpoint = (
+ data["result"]["loadpoints"][0] if data["result"]["loadpoints"] else None
+ )
charging_state = loadpoint.get("charging") if loadpoint else None
if not isinstance(charging_state, bool):
logger.error("[EVCC] Charging state is not a valid boolean value.")
return None
- logger.debug("[EVCC] Charging state: %s", charging_state)
-
+ # logger.debug("[EVCC] Charging state: %s", charging_state)
# Check if the charging state has changed
if charging_state != self.last_known_charging_state:
logger.info("[EVCC] Charging state changed to: %s", charging_state)
self.last_known_charging_state = charging_state
+ # Trigger the callback if provided
+ if self.on_charging_state_change:
+ self.on_charging_state_change(charging_state)
+ charging_mode = loadpoint.get("mode") if loadpoint else None
+ if charging_mode not in ["off", "pv", "minpv", "now"]:
+ logger.error(
+ "[EVCC] Charging mode is not a valid state."
+ + " Expected one of ['off', 'pv', 'minpv', 'now']. Got: %s",
+ charging_mode
+ )
+ return None
+ logger.debug("[EVCC] Charging state: %s - Charging mode: %s", charging_mode, charging_state)
+ # Check if the charging state has changed
+ if charging_mode != self.last_known_charging_mode:
+ logger.info("[EVCC] Charging mode changed to: %s", charging_mode)
+ self.last_known_charging_mode = charging_mode
# Trigger the callback if provided
if self.on_charging_state_change:
self.on_charging_state_change(charging_state)
- return charging_state
+ return charging_state, charging_mode
def fetch_evcc_state_via_api(self):
"""
@@ -147,7 +182,7 @@ def fetch_evcc_state_via_api(self):
returns None.
Returns:
- dict: The JSON response from the EVCC API containing the state information,
+ dict: The JSON response from the EVCC API containing the state information,
or None if the request fails or times out.
"""
evcc_url = self.url + "/api/state"
diff --git a/src/web/index.html b/src/web/index.html
index 827714cb..ad42d327 100644
--- a/src/web/index.html
+++ b/src/web/index.html
@@ -9,7 +9,8 @@
-
+
+