python-openevse-http 0.4.1__py3-none-any.whl → 0.4.3__py3-none-any.whl

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.
openevsehttp/client.py CHANGED
@@ -183,12 +183,12 @@ class OpenEVSE(CommandsMixin, ManagersMixin, SensorsMixin, PropertiesMixin):
183
183
  return (False, "")
184
184
  return (value["cmd"], value["ret"])
185
185
 
186
- async def update(self) -> None:
186
+ async def update(self, force_status: bool = False) -> None:
187
187
  """Update the values."""
188
188
  # TODO: add addiontal endpoints to update
189
189
  urls = [f"{self.url}config"]
190
190
 
191
- if not self._ws_listening:
191
+ if not self._ws_listening or force_status or self.ota_update:
192
192
  urls = [f"{self.url}status", f"{self.url}config"]
193
193
 
194
194
  for url in urls:
@@ -196,7 +196,7 @@ class OpenEVSE(CommandsMixin, ManagersMixin, SensorsMixin, PropertiesMixin):
196
196
  response = await self.process_request(url, method="get")
197
197
  if "/status" in url:
198
198
  if isinstance(response, Mapping) and "error" not in response:
199
- self._status = dict(response)
199
+ self._status.update(dict(response))
200
200
  _LOGGER.debug("Status update: %s", self._status)
201
201
  elif isinstance(response, Mapping):
202
202
  _LOGGER.warning(
openevsehttp/commands.py CHANGED
@@ -39,13 +39,27 @@ class CommandsMixin:
39
39
  async def send_command(self, command: str) -> tuple:
40
40
  raise NotImplementedError
41
41
 
42
- async def update(self) -> None:
42
+ async def update(self, force_status: bool = False) -> None:
43
43
  raise NotImplementedError
44
44
 
45
45
  def _normalize_response(self, response: Any) -> dict[str, Any] | list[Any]:
46
46
  """Normalize response to a dict or list."""
47
47
  raise NotImplementedError
48
48
 
49
+ def _flag_ota_if_started(self, response: Any) -> None:
50
+ """Flag OTA as active if response indicates firmware update has started."""
51
+ normalized = self._normalize_response(response)
52
+ if isinstance(normalized, dict) and (
53
+ normalized.get("msg") == "started"
54
+ or normalized.get("msg") in SUCCESS_ANSWERS
55
+ ):
56
+ _LOGGER.debug("Firmware update started, setting ota_update flag.")
57
+ self._status["ota_update"] = 1
58
+ else:
59
+ _LOGGER.debug(
60
+ "Firmware update response did not indicate start: %s", normalized
61
+ )
62
+
49
63
  async def get_schedule(self) -> Mapping[str, Any] | list[Any]:
50
64
  """Return the current schedule."""
51
65
  url = f"{self.url}schedule"
@@ -122,7 +136,12 @@ class CommandsMixin:
122
136
  time_limit: int | None = None,
123
137
  auto_release: bool | None = None,
124
138
  ) -> Any:
125
- """Set the manual override status."""
139
+ """Set the manual override status.
140
+
141
+ Fetches the current override payload first and merges existing values
142
+ into the request payload. This prevents the firmware from clearing/resetting
143
+ previously configured properties that are not passed in the function call.
144
+ """
126
145
  if not self._version_check("4.0.1"):
127
146
  _LOGGER.debug("Feature not supported for older firmware.")
128
147
  raise UnsupportedFeature
@@ -140,6 +159,18 @@ class CommandsMixin:
140
159
  raise ValueError
141
160
 
142
161
  data: dict[str, Any] = {}
162
+ if isinstance(response, Mapping):
163
+ for key in (
164
+ "state",
165
+ "charge_current",
166
+ "max_current",
167
+ "energy_limit",
168
+ "time_limit",
169
+ "auto_release",
170
+ ):
171
+ if key in response:
172
+ data[key] = response[key]
173
+
143
174
  if auto_release is not None:
144
175
  data["auto_release"] = auto_release
145
176
 
@@ -372,6 +403,7 @@ class CommandsMixin:
372
403
  url = f"{base_url}ESP32_WiFi_V4.x/releases/latest"
373
404
  else:
374
405
  url = f"{base_url}ESP8266_WiFi_v2.x/releases/latest"
406
+ _LOGGER.debug("Firmware check URL: %s", url)
375
407
  except AwesomeVersionCompareException:
376
408
  _LOGGER.debug("Non-semver firmware version detected.")
377
409
  return None
@@ -403,6 +435,7 @@ class CommandsMixin:
403
435
  method,
404
436
  )
405
437
  async with http_method(url) as resp:
438
+ _LOGGER.debug("Firmware check response status: %d", resp.status)
406
439
  if resp.status != 200:
407
440
  return None
408
441
  message = await resp.text()
@@ -413,19 +446,51 @@ class CommandsMixin:
413
446
  return None
414
447
 
415
448
  if not isinstance(message, dict):
449
+ _LOGGER.debug(
450
+ "Invalid JSON response type from GitHub: %s", type(message)
451
+ )
416
452
  return None
417
453
 
454
+ _LOGGER.debug(
455
+ "GitHub release metadata successfully fetched for version: %s",
456
+ message.get("tag_name"),
457
+ )
458
+
418
459
  # Match browser_download_url based on buildenv
419
460
  download_url = None
420
461
  buildenv = self._config.get("buildenv")
421
462
  assets = message.get("assets", [])
422
463
 
423
- if buildenv and assets:
464
+ if not buildenv:
465
+ _LOGGER.debug(
466
+ "Cannot resolve firmware asset: missing buildenv in config."
467
+ )
468
+ assets = []
469
+ elif not isinstance(assets, list):
470
+ _LOGGER.debug("Invalid GitHub assets payload: %r", assets)
471
+ assets = []
472
+ else:
473
+ _LOGGER.debug("Matching buildenv '%s' against assets", buildenv)
424
474
  target_filename = f"{buildenv}.bin"
425
475
  for asset in assets:
476
+ if not isinstance(asset, Mapping):
477
+ continue
426
478
  if asset.get("name") == target_filename:
427
479
  download_url = asset.get("browser_download_url")
480
+ _LOGGER.debug("Found matching firmware asset: %s", download_url)
428
481
  break
482
+ if buildenv and not download_url:
483
+ _LOGGER.debug(
484
+ "Could not find asset matching target filename '%s.bin' in assets: %s",
485
+ buildenv,
486
+ [
487
+ asset.get("name")
488
+ for asset in assets
489
+ if isinstance(asset, Mapping)
490
+ ]
491
+ if assets
492
+ else "None",
493
+ )
429
494
 
430
495
  return {
431
496
  "latest_version": message.get("tag_name"),
@@ -452,10 +517,16 @@ class CommandsMixin:
452
517
  raise UnsupportedFeature
453
518
 
454
519
  if firmware_bytes is not None and firmware_url is not None:
520
+ _LOGGER.error("Cannot specify both firmware_bytes and firmware_url")
455
521
  raise ValueError("Cannot specify both firmware_bytes and firmware_url")
456
522
 
523
+ if firmware_bytes is not None and len(firmware_bytes) == 0:
524
+ _LOGGER.error("Empty firmware bytes provided")
525
+ raise ValueError("Empty firmware bytes provided")
526
+
457
527
  if firmware_url is not None:
458
528
  if not isinstance(firmware_url, str) or not firmware_url.strip():
529
+ _LOGGER.error("Invalid firmware_url: %s", firmware_url)
459
530
  raise ValueError("Invalid firmware_url")
460
531
 
461
532
  url = f"{self.url}update"
@@ -473,12 +544,23 @@ class CommandsMixin:
473
544
  "Uploading firmware binary to %s (%d bytes)", url, len(firmware_bytes)
474
545
  )
475
546
  # Rapi is mapped to http request's data kwarg in process_request
476
- return await self.process_request(url=url, method="post", rapi=form_data)
547
+ response = await self.process_request(
548
+ url=url, method="post", rapi=form_data
549
+ )
550
+ _LOGGER.debug("Firmware upload request completed. Response: %s", response)
551
+ self._flag_ota_if_started(response)
552
+ return response
477
553
 
478
554
  # 2. Resolve URL from GitHub if not specified
479
555
  if firmware_url is None:
556
+ _LOGGER.debug(
557
+ "No firmware URL provided. Resolving latest matching firmware from GitHub."
558
+ )
480
559
  check_result = await self.firmware_check()
481
560
  if not check_result or not check_result.get("browser_download_url"):
561
+ _LOGGER.error(
562
+ "Could not resolve latest firmware download URL from GitHub."
563
+ )
482
564
  raise RuntimeError(
483
565
  "Could not resolve latest firmware download URL from GitHub."
484
566
  )
@@ -489,7 +571,10 @@ class CommandsMixin:
489
571
  _LOGGER.debug(
490
572
  "Requesting OpenEVSE to download and update from: %s", firmware_url
491
573
  )
492
- return await self.process_request(url=url, method="post", data=data)
574
+ response = await self.process_request(url=url, method="post", data=data)
575
+ _LOGGER.debug("Firmware update request completed. Response: %s", response)
576
+ self._flag_ota_if_started(response)
577
+ return response
493
578
 
494
579
  async def set_led_brightness(self, level: int) -> None:
495
580
  """Set LED brightness level."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python_openevse_http
3
- Version: 0.4.1
3
+ Version: 0.4.3
4
4
  Summary: Python wrapper for OpenEVSE HTTP API
5
5
  Home-page: https://github.com/firstof9/python-openevse-http
6
6
  Download-URL: https://github.com/firstof9/python-openevse-http
@@ -1,7 +1,7 @@
1
1
  openevsehttp/__init__.py,sha256=I6a1mjOZHYiWb_qfCuDuFLOOncrkkB_7uwybtOIujfY,1165
2
2
  openevsehttp/__main__.py,sha256=EHmSdT7GjAVvHQxvLBTjZXsj_V5SB6B2_kpgUAT7mPM,146
3
- openevsehttp/client.py,sha256=XaKet6ZldNVhFbZjoPksaahDNflXmOpeRpa1Jn9pmZ8,17882
4
- openevsehttp/commands.py,sha256=DKgrzHiOa7wtu47PEMsQeLwYIFpIeYZczCGxPfPPir8,23538
3
+ openevsehttp/client.py,sha256=2SGL0RKZp08t_hGXHKIIRGk8wyrNJRcSXQE7nc0P9UU,17951
4
+ openevsehttp/commands.py,sha256=vZFzNFg6-DqbpavTulypXOGCDwPChvBzoxIHXBlJrBc,27216
5
5
  openevsehttp/const.py,sha256=y-2hGv_PCal_-VCSGC7IIyzQYtfeVdq3MjOhBWIdZvc,1440
6
6
  openevsehttp/exceptions.py,sha256=bqz-tHTW1AYJMKcm0s5M6z5tA6XZgjnCiBLW1XrZ_70,672
7
7
  openevsehttp/managers.py,sha256=kEX1ZD9u-FY0UEZJsxeFEGBSGzSlkbBc0kmxCiMJtJw,5373
@@ -9,8 +9,8 @@ openevsehttp/properties.py,sha256=9fmJo6xU8im-Dd2QoH8f2E-0veD8uL1QQYiaERvIRr8,17
9
9
  openevsehttp/sensors.py,sha256=sJP2FPnU1Lk5S3VUyFT14JM1nKEBQPsjl-DiZI-pZrs,4673
10
10
  openevsehttp/utils.py,sha256=e3HH_jwZgb1iBWJgIoMOM0JPrQNwXyVdOx5vTWOh4T0,858
11
11
  openevsehttp/websocket.py,sha256=Mi_WFmlT3-9i6bbHIN6ua09SD8CpIle2vRXB3HyWzmM,10066
12
- python_openevse_http-0.4.1.dist-info/licenses/LICENSE,sha256=hSB6TOQ7rmwSGb6XzqRjDGMvmUj5_GlacqQin3tegtA,11341
13
- python_openevse_http-0.4.1.dist-info/METADATA,sha256=tSggymHMnub1tTeWnNQ__TKJOEHG9jfVut2acfuFJb4,4363
14
- python_openevse_http-0.4.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
15
- python_openevse_http-0.4.1.dist-info/top_level.txt,sha256=u8RUkoEIE33Cjn6gmqiEoVpZ0VZ59WJ3FXBwwOg0CPE,13
16
- python_openevse_http-0.4.1.dist-info/RECORD,,
12
+ python_openevse_http-0.4.3.dist-info/licenses/LICENSE,sha256=hSB6TOQ7rmwSGb6XzqRjDGMvmUj5_GlacqQin3tegtA,11341
13
+ python_openevse_http-0.4.3.dist-info/METADATA,sha256=ThGhSK3mu1cGbRBLb4a5El7qhUNERBu6rC9tjdiHd20,4363
14
+ python_openevse_http-0.4.3.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
15
+ python_openevse_http-0.4.3.dist-info/top_level.txt,sha256=u8RUkoEIE33Cjn6gmqiEoVpZ0VZ59WJ3FXBwwOg0CPE,13
16
+ python_openevse_http-0.4.3.dist-info/RECORD,,