python-openevse-http 0.2.1__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_openevse_http-0.2.1/python_openevse_http.egg-info → python_openevse_http-0.2.3}/PKG-INFO +1 -1
- {python_openevse_http-0.2.1 → python_openevse_http-0.2.3}/openevsehttp/__main__.py +107 -136
- {python_openevse_http-0.2.1 → python_openevse_http-0.2.3/python_openevse_http.egg-info}/PKG-INFO +1 -1
- {python_openevse_http-0.2.1 → python_openevse_http-0.2.3}/setup.py +1 -1
- {python_openevse_http-0.2.1 → python_openevse_http-0.2.3}/tests/test_main.py +61 -27
- {python_openevse_http-0.2.1 → python_openevse_http-0.2.3}/tests/test_main_edge_cases.py +6 -4
- {python_openevse_http-0.2.1 → python_openevse_http-0.2.3}/tests/test_websocket.py +4 -3
- {python_openevse_http-0.2.1 → python_openevse_http-0.2.3}/LICENSE +0 -0
- {python_openevse_http-0.2.1 → python_openevse_http-0.2.3}/README.md +0 -0
- {python_openevse_http-0.2.1 → python_openevse_http-0.2.3}/openevsehttp/__init__.py +0 -0
- {python_openevse_http-0.2.1 → python_openevse_http-0.2.3}/openevsehttp/const.py +0 -0
- {python_openevse_http-0.2.1 → python_openevse_http-0.2.3}/openevsehttp/exceptions.py +0 -0
- {python_openevse_http-0.2.1 → python_openevse_http-0.2.3}/openevsehttp/websocket.py +0 -0
- {python_openevse_http-0.2.1 → python_openevse_http-0.2.3}/python_openevse_http.egg-info/SOURCES.txt +0 -0
- {python_openevse_http-0.2.1 → python_openevse_http-0.2.3}/python_openevse_http.egg-info/dependency_links.txt +0 -0
- {python_openevse_http-0.2.1 → python_openevse_http-0.2.3}/python_openevse_http.egg-info/not-zip-safe +0 -0
- {python_openevse_http-0.2.1 → python_openevse_http-0.2.3}/python_openevse_http.egg-info/requires.txt +0 -0
- {python_openevse_http-0.2.1 → python_openevse_http-0.2.3}/python_openevse_http.egg-info/top_level.txt +0 -0
- {python_openevse_http-0.2.1 → python_openevse_http-0.2.3}/setup.cfg +0 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
-
import datetime
|
|
6
|
+
from datetime import datetime, timedelta, timezone
|
|
7
7
|
import json
|
|
8
8
|
import logging
|
|
9
9
|
import re
|
|
@@ -896,37 +896,32 @@ class OpenEVSE:
|
|
|
896
896
|
raise UnknownError
|
|
897
897
|
|
|
898
898
|
@property
|
|
899
|
-
def led_brightness(self) ->
|
|
899
|
+
def led_brightness(self) -> int | None:
|
|
900
900
|
"""Return charger led_brightness."""
|
|
901
901
|
if not self._version_check("4.1.0"):
|
|
902
902
|
_LOGGER.debug("Feature not supported for older firmware.")
|
|
903
903
|
raise UnsupportedFeature
|
|
904
|
-
|
|
905
|
-
return self._config["led_brightness"]
|
|
904
|
+
return self._config.get("led_brightness")
|
|
906
905
|
|
|
907
906
|
@property
|
|
908
|
-
def hostname(self) -> str:
|
|
907
|
+
def hostname(self) -> str | None:
|
|
909
908
|
"""Return charger hostname."""
|
|
910
|
-
|
|
911
|
-
return self._config["hostname"]
|
|
909
|
+
return self._config.get("hostname")
|
|
912
910
|
|
|
913
911
|
@property
|
|
914
|
-
def wifi_ssid(self) -> str:
|
|
912
|
+
def wifi_ssid(self) -> str | None:
|
|
915
913
|
"""Return charger connected SSID."""
|
|
916
|
-
|
|
917
|
-
return self._config["ssid"]
|
|
914
|
+
return self._config.get("ssid")
|
|
918
915
|
|
|
919
916
|
@property
|
|
920
|
-
def ammeter_offset(self) -> int:
|
|
917
|
+
def ammeter_offset(self) -> int | None:
|
|
921
918
|
"""Return ammeter's current offset."""
|
|
922
|
-
|
|
923
|
-
return self._config["offset"]
|
|
919
|
+
return self._config.get("offset")
|
|
924
920
|
|
|
925
921
|
@property
|
|
926
|
-
def ammeter_scale_factor(self) -> int:
|
|
922
|
+
def ammeter_scale_factor(self) -> int | None:
|
|
927
923
|
"""Return ammeter's current scale factor."""
|
|
928
|
-
|
|
929
|
-
return self._config["scale"]
|
|
924
|
+
return self._config.get("scale")
|
|
930
925
|
|
|
931
926
|
@property
|
|
932
927
|
def temp_check_enabled(self) -> bool:
|
|
@@ -954,23 +949,21 @@ class OpenEVSE:
|
|
|
954
949
|
return bool(self._config.get("relayt", False))
|
|
955
950
|
|
|
956
951
|
@property
|
|
957
|
-
def service_level(self) -> str:
|
|
952
|
+
def service_level(self) -> str | None:
|
|
958
953
|
"""Return the service level."""
|
|
959
|
-
|
|
960
|
-
return self._config["service"]
|
|
954
|
+
return self._config.get("service")
|
|
961
955
|
|
|
962
956
|
@property
|
|
963
|
-
def openevse_firmware(self) -> str:
|
|
957
|
+
def openevse_firmware(self) -> str | None:
|
|
964
958
|
"""Return the firmware version."""
|
|
965
|
-
|
|
966
|
-
return self._config["firmware"]
|
|
959
|
+
return self._config.get("firmware")
|
|
967
960
|
|
|
968
961
|
@property
|
|
969
962
|
def max_current_soft(self) -> int | None:
|
|
970
963
|
"""Return the max current soft."""
|
|
971
|
-
if
|
|
972
|
-
return self._config
|
|
973
|
-
return self._status
|
|
964
|
+
if "max_current_soft" in self._config:
|
|
965
|
+
return self._config.get("max_current_soft")
|
|
966
|
+
return self._status.get("pilot")
|
|
974
967
|
|
|
975
968
|
@property
|
|
976
969
|
async def async_charge_current(self) -> int | None:
|
|
@@ -984,9 +977,9 @@ class OpenEVSE:
|
|
|
984
977
|
return min(
|
|
985
978
|
claims["properties"]["charge_current"], self._config["max_current_hard"]
|
|
986
979
|
)
|
|
987
|
-
if
|
|
988
|
-
return self._config
|
|
989
|
-
return self._status
|
|
980
|
+
if "max_current_soft" in self._config:
|
|
981
|
+
return self._config.get("max_current_soft")
|
|
982
|
+
return self._status.get("pilot")
|
|
990
983
|
|
|
991
984
|
@property
|
|
992
985
|
def max_current(self) -> int | None:
|
|
@@ -994,33 +987,29 @@ class OpenEVSE:
|
|
|
994
987
|
return self._status.get("max_current", None)
|
|
995
988
|
|
|
996
989
|
@property
|
|
997
|
-
def wifi_firmware(self) -> str:
|
|
990
|
+
def wifi_firmware(self) -> str | None:
|
|
998
991
|
"""Return the ESP firmware version."""
|
|
999
|
-
|
|
1000
|
-
value
|
|
1001
|
-
if "dev" in value:
|
|
992
|
+
value = self._config.get("version")
|
|
993
|
+
if value is not None and "dev" in value:
|
|
1002
994
|
_LOGGER.debug("Stripping 'dev' from version.")
|
|
1003
995
|
value = value.split(".")
|
|
1004
996
|
value = ".".join(value[0:3])
|
|
1005
997
|
return value
|
|
1006
998
|
|
|
1007
999
|
@property
|
|
1008
|
-
def ip_address(self) -> str:
|
|
1000
|
+
def ip_address(self) -> str | None:
|
|
1009
1001
|
"""Return the ip address."""
|
|
1010
|
-
|
|
1011
|
-
return self._status["ipaddress"]
|
|
1002
|
+
return self._status.get("ipaddress")
|
|
1012
1003
|
|
|
1013
1004
|
@property
|
|
1014
|
-
def charging_voltage(self) -> int:
|
|
1005
|
+
def charging_voltage(self) -> int | None:
|
|
1015
1006
|
"""Return the charging voltage."""
|
|
1016
|
-
|
|
1017
|
-
return self._status["voltage"]
|
|
1007
|
+
return self._status.get("voltage")
|
|
1018
1008
|
|
|
1019
1009
|
@property
|
|
1020
|
-
def mode(self) -> str:
|
|
1010
|
+
def mode(self) -> str | None:
|
|
1021
1011
|
"""Return the mode."""
|
|
1022
|
-
|
|
1023
|
-
return self._status["mode"]
|
|
1012
|
+
return self._status.get("mode")
|
|
1024
1013
|
|
|
1025
1014
|
@property
|
|
1026
1015
|
def using_ethernet(self) -> bool:
|
|
@@ -1028,98 +1017,80 @@ class OpenEVSE:
|
|
|
1028
1017
|
return bool(self._status.get("eth_connected", False))
|
|
1029
1018
|
|
|
1030
1019
|
@property
|
|
1031
|
-
def stuck_relay_trip_count(self) -> int:
|
|
1020
|
+
def stuck_relay_trip_count(self) -> int | None:
|
|
1032
1021
|
"""Return the stuck relay count."""
|
|
1033
|
-
|
|
1034
|
-
return self._status["stuckcount"]
|
|
1022
|
+
return self._status.get("stuckcount")
|
|
1035
1023
|
|
|
1036
1024
|
@property
|
|
1037
|
-
def no_gnd_trip_count(self) -> int:
|
|
1025
|
+
def no_gnd_trip_count(self) -> int | None:
|
|
1038
1026
|
"""Return the no ground count."""
|
|
1039
|
-
|
|
1040
|
-
return self._status["nogndcount"]
|
|
1027
|
+
return self._status.get("nogndcount")
|
|
1041
1028
|
|
|
1042
1029
|
@property
|
|
1043
|
-
def gfi_trip_count(self) -> int:
|
|
1030
|
+
def gfi_trip_count(self) -> int | None:
|
|
1044
1031
|
"""Return the GFCI count."""
|
|
1045
|
-
|
|
1046
|
-
return self._status["gfcicount"]
|
|
1032
|
+
return self._status.get("gfcicount")
|
|
1047
1033
|
|
|
1048
1034
|
@property
|
|
1049
1035
|
def status(self) -> str:
|
|
1050
1036
|
"""Return charger's state."""
|
|
1051
|
-
|
|
1052
|
-
return state
|
|
1037
|
+
return self._status.get("status", states[int(self._status.get("state", 0))])
|
|
1053
1038
|
|
|
1054
1039
|
@property
|
|
1055
1040
|
def state(self) -> str:
|
|
1056
1041
|
"""Return charger's state."""
|
|
1057
|
-
|
|
1058
|
-
return states[int(self._status["state"])]
|
|
1042
|
+
return states[int(self._status.get("state", 0))]
|
|
1059
1043
|
|
|
1060
1044
|
@property
|
|
1061
|
-
def state_raw(self) -> int:
|
|
1045
|
+
def state_raw(self) -> int | None:
|
|
1062
1046
|
"""Return charger's state int form."""
|
|
1063
|
-
|
|
1064
|
-
return self._status["state"]
|
|
1047
|
+
return self._status.get("state")
|
|
1065
1048
|
|
|
1066
1049
|
@property
|
|
1067
|
-
def charge_time_elapsed(self) -> int:
|
|
1050
|
+
def charge_time_elapsed(self) -> int | None:
|
|
1068
1051
|
"""Return elapsed charging time."""
|
|
1069
|
-
|
|
1070
|
-
return self._status["elapsed"]
|
|
1052
|
+
return self._status.get("elapsed")
|
|
1071
1053
|
|
|
1072
1054
|
@property
|
|
1073
|
-
def wifi_signal(self) -> str:
|
|
1055
|
+
def wifi_signal(self) -> str | None:
|
|
1074
1056
|
"""Return charger's wifi signal."""
|
|
1075
|
-
|
|
1076
|
-
return self._status["srssi"]
|
|
1057
|
+
return self._status.get("srssi")
|
|
1077
1058
|
|
|
1078
1059
|
@property
|
|
1079
|
-
def charging_current(self) -> float:
|
|
1060
|
+
def charging_current(self) -> float | None:
|
|
1080
1061
|
"""Return the charge current.
|
|
1081
1062
|
|
|
1082
1063
|
0 if is not currently charging.
|
|
1083
1064
|
"""
|
|
1084
|
-
|
|
1085
|
-
return self._status["amp"]
|
|
1065
|
+
return self._status.get("amp")
|
|
1086
1066
|
|
|
1087
1067
|
@property
|
|
1088
|
-
def current_capacity(self) -> int:
|
|
1068
|
+
def current_capacity(self) -> int | None:
|
|
1089
1069
|
"""Return the current capacity."""
|
|
1090
|
-
|
|
1091
|
-
return self._status["pilot"]
|
|
1070
|
+
return self._status.get("pilot")
|
|
1092
1071
|
|
|
1093
1072
|
@property
|
|
1094
|
-
def usage_total(self) -> float:
|
|
1073
|
+
def usage_total(self) -> float | None:
|
|
1095
1074
|
"""Return the total energy usage in Wh."""
|
|
1096
|
-
assert self._status is not None
|
|
1097
1075
|
if "total_energy" in self._status:
|
|
1098
|
-
return self._status
|
|
1099
|
-
return self._status
|
|
1076
|
+
return self._status.get("total_energy")
|
|
1077
|
+
return self._status.get("watthour")
|
|
1100
1078
|
|
|
1101
1079
|
@property
|
|
1102
1080
|
def ambient_temperature(self) -> float | None:
|
|
1103
1081
|
"""Return the temperature of the ambient sensor, in degrees Celsius."""
|
|
1104
|
-
|
|
1105
|
-
temp
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
else:
|
|
1109
|
-
temp = self._status["temp1"] / 10
|
|
1110
|
-
return temp
|
|
1082
|
+
temp = self._status.get("temp")
|
|
1083
|
+
if temp:
|
|
1084
|
+
return temp / 10
|
|
1085
|
+
return self._status.get("temp1", 0) / 10
|
|
1111
1086
|
|
|
1112
1087
|
@property
|
|
1113
1088
|
def rtc_temperature(self) -> float | None:
|
|
1114
|
-
"""Return the temperature of the real time clock sensor.
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
temp = self._status["temp2"] if self._status["temp2"] else None
|
|
1120
|
-
if temp is not None:
|
|
1121
|
-
return temp / 10
|
|
1122
|
-
return None
|
|
1089
|
+
"""Return the temperature of the real time clock sensor."""
|
|
1090
|
+
temp = self._status.get("temp2")
|
|
1091
|
+
if temp is None or isinstance(temp, bool):
|
|
1092
|
+
return None
|
|
1093
|
+
return float(temp) / 10
|
|
1123
1094
|
|
|
1124
1095
|
@property
|
|
1125
1096
|
def ir_temperature(self) -> float | None:
|
|
@@ -1127,37 +1098,42 @@ class OpenEVSE:
|
|
|
1127
1098
|
|
|
1128
1099
|
In degrees Celsius.
|
|
1129
1100
|
"""
|
|
1130
|
-
|
|
1131
|
-
temp
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
return None
|
|
1101
|
+
temp = self._status.get("temp3")
|
|
1102
|
+
if temp is None or isinstance(temp, bool):
|
|
1103
|
+
return None
|
|
1104
|
+
return float(temp) / 10
|
|
1135
1105
|
|
|
1136
1106
|
@property
|
|
1137
1107
|
def esp_temperature(self) -> float | None:
|
|
1138
1108
|
"""Return the temperature of the ESP sensor, in degrees Celsius."""
|
|
1139
|
-
|
|
1140
|
-
if
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
return temp / 10
|
|
1144
|
-
return None
|
|
1109
|
+
temp = self._status.get("temp4")
|
|
1110
|
+
if temp is None or isinstance(temp, bool):
|
|
1111
|
+
return None
|
|
1112
|
+
return float(temp) / 10
|
|
1145
1113
|
|
|
1146
1114
|
@property
|
|
1147
|
-
def time(self) -> datetime
|
|
1115
|
+
def time(self) -> datetime | None:
|
|
1148
1116
|
"""Get the RTC time."""
|
|
1149
|
-
|
|
1117
|
+
value = self._status.get("time")
|
|
1118
|
+
if value:
|
|
1119
|
+
try:
|
|
1120
|
+
return datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
1121
|
+
except (ValueError, AttributeError):
|
|
1122
|
+
return None
|
|
1123
|
+
return None
|
|
1150
1124
|
|
|
1151
1125
|
@property
|
|
1152
|
-
def usage_session(self) -> float:
|
|
1126
|
+
def usage_session(self) -> float | None:
|
|
1153
1127
|
"""Get the energy usage for the current charging session.
|
|
1154
1128
|
|
|
1155
1129
|
Return the energy usage in Wh.
|
|
1156
1130
|
"""
|
|
1157
|
-
assert self._status is not None
|
|
1158
1131
|
if "session_energy" in self._status:
|
|
1159
|
-
return self._status
|
|
1160
|
-
|
|
1132
|
+
return self._status.get("session_energy")
|
|
1133
|
+
wattsec = self._status.get("wattsec")
|
|
1134
|
+
if wattsec is not None:
|
|
1135
|
+
return float(round(wattsec / 3600, 2))
|
|
1136
|
+
return None
|
|
1161
1137
|
|
|
1162
1138
|
@property
|
|
1163
1139
|
def total_day(self) -> float | None:
|
|
@@ -1187,61 +1163,53 @@ class OpenEVSE:
|
|
|
1187
1163
|
@property
|
|
1188
1164
|
def protocol_version(self) -> str | None:
|
|
1189
1165
|
"""Return the protocol version."""
|
|
1190
|
-
|
|
1191
|
-
if
|
|
1166
|
+
protocol = self._config.get("protocol")
|
|
1167
|
+
if protocol == "-":
|
|
1192
1168
|
return None
|
|
1193
|
-
return
|
|
1169
|
+
return protocol
|
|
1194
1170
|
|
|
1195
1171
|
@property
|
|
1196
|
-
def vehicle(self) ->
|
|
1172
|
+
def vehicle(self) -> bool:
|
|
1197
1173
|
"""Return if a vehicle is connected dto the EVSE."""
|
|
1198
|
-
|
|
1199
|
-
return self._status["vehicle"]
|
|
1174
|
+
return self._status.get("vehicle", False)
|
|
1200
1175
|
|
|
1201
1176
|
@property
|
|
1202
|
-
def ota_update(self) ->
|
|
1177
|
+
def ota_update(self) -> bool:
|
|
1203
1178
|
"""Return if an OTA update is active."""
|
|
1204
|
-
|
|
1205
|
-
return self._status["ota_update"]
|
|
1179
|
+
return self._status.get("ota_update", False)
|
|
1206
1180
|
|
|
1207
1181
|
@property
|
|
1208
|
-
def manual_override(self) ->
|
|
1182
|
+
def manual_override(self) -> bool:
|
|
1209
1183
|
"""Return if Manual Override is set."""
|
|
1210
|
-
|
|
1211
|
-
return self._status["manual_override"]
|
|
1184
|
+
return self._status.get("manual_override", False)
|
|
1212
1185
|
|
|
1213
1186
|
@property
|
|
1214
1187
|
def divertmode(self) -> str:
|
|
1215
1188
|
"""Return the divert mode."""
|
|
1216
|
-
|
|
1217
|
-
mode = self._status["divertmode"]
|
|
1189
|
+
mode = self._status.get("divertmode", 1)
|
|
1218
1190
|
if mode == 1:
|
|
1219
1191
|
return "fast"
|
|
1220
1192
|
return "eco"
|
|
1221
1193
|
|
|
1222
1194
|
@property
|
|
1223
|
-
def charge_mode(self) -> str:
|
|
1195
|
+
def charge_mode(self) -> str | None:
|
|
1224
1196
|
"""Return the charge mode."""
|
|
1225
|
-
|
|
1226
|
-
return self._config["charge_mode"]
|
|
1197
|
+
return self._config.get("charge_mode")
|
|
1227
1198
|
|
|
1228
1199
|
@property
|
|
1229
|
-
def available_current(self) -> float:
|
|
1200
|
+
def available_current(self) -> float | None:
|
|
1230
1201
|
"""Return the computed available current for divert."""
|
|
1231
|
-
|
|
1232
|
-
return self._status["available_current"]
|
|
1202
|
+
return self._status.get("available_current")
|
|
1233
1203
|
|
|
1234
1204
|
@property
|
|
1235
|
-
def smoothed_available_current(self) -> float:
|
|
1205
|
+
def smoothed_available_current(self) -> float | None:
|
|
1236
1206
|
"""Return the computed smoothed available current for divert."""
|
|
1237
|
-
|
|
1238
|
-
return self._status["smoothed_available_current"]
|
|
1207
|
+
return self._status.get("smoothed_available_current")
|
|
1239
1208
|
|
|
1240
1209
|
@property
|
|
1241
|
-
def charge_rate(self) -> float:
|
|
1210
|
+
def charge_rate(self) -> float | None:
|
|
1242
1211
|
"""Return the divert charge rate."""
|
|
1243
|
-
|
|
1244
|
-
return self._status["charge_rate"]
|
|
1212
|
+
return self._status.get("charge_rate")
|
|
1245
1213
|
|
|
1246
1214
|
@property
|
|
1247
1215
|
def divert_active(self) -> bool:
|
|
@@ -1259,7 +1227,7 @@ class OpenEVSE:
|
|
|
1259
1227
|
|
|
1260
1228
|
Calculate Watts base on V*I
|
|
1261
1229
|
"""
|
|
1262
|
-
if self._status is not None and
|
|
1230
|
+
if self._status is not None and all(
|
|
1263
1231
|
key in self._status for key in ["voltage", "amp"]
|
|
1264
1232
|
):
|
|
1265
1233
|
return round(self._status["voltage"] * self._status["amp"], 2)
|
|
@@ -1308,11 +1276,14 @@ class OpenEVSE:
|
|
|
1308
1276
|
)
|
|
1309
1277
|
|
|
1310
1278
|
@property
|
|
1311
|
-
def vehicle_eta(self) ->
|
|
1279
|
+
def vehicle_eta(self) -> datetime | None:
|
|
1312
1280
|
"""Return time to full charge."""
|
|
1313
|
-
|
|
1314
|
-
"
|
|
1281
|
+
value = self._status.get(
|
|
1282
|
+
"time_to_full_charge", self._status.get("vehicle_eta", None)
|
|
1315
1283
|
)
|
|
1284
|
+
if value is not None:
|
|
1285
|
+
return datetime.now(timezone.utc) + timedelta(seconds=value)
|
|
1286
|
+
return value
|
|
1316
1287
|
|
|
1317
1288
|
# There is currently no min/max amps JSON data
|
|
1318
1289
|
# available via HTTP API methods
|
|
@@ -8,6 +8,8 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
8
8
|
|
|
9
9
|
import aiohttp
|
|
10
10
|
import pytest
|
|
11
|
+
from datetime import datetime, timezone, timedelta
|
|
12
|
+
from freezegun import freeze_time
|
|
11
13
|
from aiohttp.client_exceptions import ContentTypeError, ServerTimeoutError
|
|
12
14
|
from aiohttp.client_reqrep import ConnectionKey
|
|
13
15
|
from awesomeversion.exceptions import AwesomeVersionCompareException
|
|
@@ -488,18 +490,42 @@ async def test_get_esp_temperature(fixture, expected, request):
|
|
|
488
490
|
|
|
489
491
|
|
|
490
492
|
@pytest.mark.parametrize(
|
|
491
|
-
"fixture,
|
|
493
|
+
"fixture, expected_str",
|
|
492
494
|
[("test_charger", "2021-08-10T23:00:11Z"), ("test_charger_v2", None)],
|
|
493
495
|
)
|
|
494
|
-
async def test_get_time(fixture,
|
|
496
|
+
async def test_get_time(fixture, expected_str, request):
|
|
495
497
|
"""Test v4 Status reply."""
|
|
496
498
|
charger = request.getfixturevalue(fixture)
|
|
497
499
|
await charger.update()
|
|
498
|
-
|
|
499
|
-
|
|
500
|
+
|
|
501
|
+
result = charger.time
|
|
502
|
+
|
|
503
|
+
if expected_str:
|
|
504
|
+
expected_dt = datetime(2021, 8, 10, 23, 0, 11, tzinfo=timezone.utc)
|
|
505
|
+
assert result == expected_dt
|
|
506
|
+
assert isinstance(result, datetime)
|
|
507
|
+
else:
|
|
508
|
+
assert result is None
|
|
509
|
+
|
|
500
510
|
await charger.ws_disconnect()
|
|
501
511
|
|
|
502
512
|
|
|
513
|
+
@pytest.mark.parametrize(
|
|
514
|
+
"bad_value",
|
|
515
|
+
[
|
|
516
|
+
"not-a-timestamp",
|
|
517
|
+
123456789,
|
|
518
|
+
True,
|
|
519
|
+
{"some": "dict"},
|
|
520
|
+
],
|
|
521
|
+
)
|
|
522
|
+
async def test_time_parsing_errors(test_charger, bad_value):
|
|
523
|
+
"""Test that ValueError and AttributeError are caught and return None."""
|
|
524
|
+
test_charger._status["time"] = bad_value
|
|
525
|
+
result = test_charger.time
|
|
526
|
+
assert result is None
|
|
527
|
+
|
|
528
|
+
|
|
503
529
|
@pytest.mark.parametrize(
|
|
504
530
|
"fixture, expected",
|
|
505
531
|
[
|
|
@@ -661,29 +687,27 @@ async def test_get_charge_rate(fixture, expected, request):
|
|
|
661
687
|
|
|
662
688
|
|
|
663
689
|
@pytest.mark.parametrize(
|
|
664
|
-
"fixture, expected", [("test_charger",
|
|
690
|
+
"fixture, expected", [("test_charger", None), ("test_charger_v2", None)]
|
|
665
691
|
)
|
|
666
692
|
async def test_get_available_current(fixture, expected, request):
|
|
667
693
|
"""Test v4 Status reply."""
|
|
668
694
|
charger = request.getfixturevalue(fixture)
|
|
669
695
|
await charger.update()
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
await charger.ws_disconnect()
|
|
696
|
+
status = charger.available_current
|
|
697
|
+
assert status == expected
|
|
698
|
+
await charger.ws_disconnect()
|
|
674
699
|
|
|
675
700
|
|
|
676
701
|
@pytest.mark.parametrize(
|
|
677
|
-
"fixture, expected", [("test_charger",
|
|
702
|
+
"fixture, expected", [("test_charger", None), ("test_charger_v2", None)]
|
|
678
703
|
)
|
|
679
704
|
async def test_get_smoothed_available_current(fixture, expected, request):
|
|
680
705
|
"""Test v4 Status reply."""
|
|
681
706
|
charger = request.getfixturevalue(fixture)
|
|
682
707
|
await charger.update()
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
await charger.ws_disconnect()
|
|
708
|
+
status = charger.smoothed_available_current
|
|
709
|
+
assert status == expected
|
|
710
|
+
await charger.ws_disconnect()
|
|
687
711
|
|
|
688
712
|
|
|
689
713
|
@pytest.mark.parametrize(
|
|
@@ -700,16 +724,15 @@ async def test_get_divert_active(fixture, expected, request):
|
|
|
700
724
|
|
|
701
725
|
|
|
702
726
|
@pytest.mark.parametrize(
|
|
703
|
-
"fixture, expected", [("test_charger",
|
|
727
|
+
"fixture, expected", [("test_charger", False), ("test_charger_v2", False)]
|
|
704
728
|
)
|
|
705
729
|
async def test_get_manual_override(fixture, expected, request):
|
|
706
730
|
"""Test v4 Status reply."""
|
|
707
731
|
charger = request.getfixturevalue(fixture)
|
|
708
732
|
await charger.update()
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
await charger.ws_disconnect()
|
|
733
|
+
status = charger.manual_override
|
|
734
|
+
assert status == expected
|
|
735
|
+
await charger.ws_disconnect()
|
|
713
736
|
|
|
714
737
|
|
|
715
738
|
async def test_toggle_override(
|
|
@@ -1266,14 +1289,25 @@ async def test_vehicle_range(fixture, expected, request):
|
|
|
1266
1289
|
|
|
1267
1290
|
|
|
1268
1291
|
@pytest.mark.parametrize(
|
|
1269
|
-
"fixture,
|
|
1292
|
+
"fixture, expected_seconds", [("test_charger", 18000), ("test_charger_v2", None)]
|
|
1270
1293
|
)
|
|
1271
|
-
|
|
1294
|
+
@freeze_time("2026-01-09 12:00:00+00:00")
|
|
1295
|
+
async def test_vehicle_eta(fixture, expected_seconds, request):
|
|
1272
1296
|
"""Test vehicle_eta reply."""
|
|
1273
1297
|
charger = request.getfixturevalue(fixture)
|
|
1274
1298
|
await charger.update()
|
|
1275
|
-
|
|
1276
|
-
|
|
1299
|
+
|
|
1300
|
+
result = charger.vehicle_eta
|
|
1301
|
+
|
|
1302
|
+
if expected_seconds is not None:
|
|
1303
|
+
# Calculate what the expected datetime should be based on our frozen time
|
|
1304
|
+
expected_datetime = datetime(
|
|
1305
|
+
2026, 1, 9, 12, 0, 0, tzinfo=timezone.utc
|
|
1306
|
+
) + timedelta(seconds=expected_seconds)
|
|
1307
|
+
assert result == expected_datetime
|
|
1308
|
+
else:
|
|
1309
|
+
assert result is None
|
|
1310
|
+
|
|
1277
1311
|
await charger.ws_disconnect()
|
|
1278
1312
|
|
|
1279
1313
|
|
|
@@ -2235,10 +2269,10 @@ async def test_main_auth_instantiation():
|
|
|
2235
2269
|
# Ensure session.get() returns the request context
|
|
2236
2270
|
mock_session.get.return_value = mock_request_ctx
|
|
2237
2271
|
|
|
2238
|
-
with
|
|
2239
|
-
"aiohttp.
|
|
2240
|
-
|
|
2241
|
-
|
|
2272
|
+
with (
|
|
2273
|
+
patch("aiohttp.ClientSession", return_value=mock_session),
|
|
2274
|
+
patch("aiohttp.BasicAuth") as mock_basic_auth,
|
|
2275
|
+
):
|
|
2242
2276
|
await charger.update()
|
|
2243
2277
|
|
|
2244
2278
|
# Verify BasicAuth was instantiated
|
|
@@ -33,8 +33,9 @@ async def test_process_request_decode_error(charger, caplog):
|
|
|
33
33
|
mock_resp.__aenter__.return_value = mock_resp
|
|
34
34
|
mock_resp.__aexit__.return_value = None
|
|
35
35
|
|
|
36
|
-
with
|
|
37
|
-
|
|
36
|
+
with (
|
|
37
|
+
patch("aiohttp.ClientSession.get", return_value=mock_resp),
|
|
38
|
+
caplog.at_level(logging.DEBUG),
|
|
38
39
|
):
|
|
39
40
|
data = await charger.process_request("http://url", method="get")
|
|
40
41
|
|
|
@@ -53,8 +54,9 @@ async def test_process_request_http_warnings(charger, caplog):
|
|
|
53
54
|
mock_resp.__aenter__.return_value = mock_resp
|
|
54
55
|
mock_resp.__aexit__.return_value = None
|
|
55
56
|
|
|
56
|
-
with
|
|
57
|
-
|
|
57
|
+
with (
|
|
58
|
+
patch("aiohttp.ClientSession.get", return_value=mock_resp),
|
|
59
|
+
caplog.at_level(logging.WARNING),
|
|
58
60
|
):
|
|
59
61
|
await charger.process_request("http://url", method="get")
|
|
60
62
|
# Verify the 404 response body was logged as a warning
|
|
@@ -97,9 +97,10 @@ async def test_connection_error_retry(ws_client, mock_callback):
|
|
|
97
97
|
"""Test connection retry logic."""
|
|
98
98
|
error = aiohttp.ClientConnectionError("Connection lost")
|
|
99
99
|
|
|
100
|
-
with
|
|
101
|
-
"
|
|
102
|
-
|
|
100
|
+
with (
|
|
101
|
+
patch("aiohttp.ClientSession.ws_connect", side_effect=error),
|
|
102
|
+
patch("asyncio.sleep", new_callable=AsyncMock) as mock_sleep,
|
|
103
|
+
):
|
|
103
104
|
# Simulate one run of 'running' which catches the error and triggers sleep
|
|
104
105
|
await ws_client.running()
|
|
105
106
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_openevse_http-0.2.1 → python_openevse_http-0.2.3}/python_openevse_http.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{python_openevse_http-0.2.1 → python_openevse_http-0.2.3}/python_openevse_http.egg-info/not-zip-safe
RENAMED
|
File without changes
|
{python_openevse_http-0.2.1 → python_openevse_http-0.2.3}/python_openevse_http.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|