python-openevse-http 0.3.5__py3-none-any.whl → 0.4.1__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
@@ -337,6 +337,16 @@ class OpenEVSE(CommandsMixin, ManagersMixin, SensorsMixin, PropertiesMixin):
337
337
  # TODO: update specific endpoints based on _version prefix
338
338
  if any(key in keys for key in UPDATE_TRIGGERS):
339
339
  await self.update()
340
+
341
+ if "ota" in keys:
342
+ ota_val = data["ota"]
343
+ if ota_val == "started":
344
+ self._status["ota_update"] = 1
345
+ elif ota_val in ("completed", "failed"):
346
+ self._status["ota_update"] = 0
347
+ data.pop("ota_progress", None)
348
+ self._status.pop("ota_progress", None)
349
+
340
350
  self._status.update(data)
341
351
 
342
352
  if self.callback is not None:
openevsehttp/commands.py CHANGED
@@ -414,12 +414,83 @@ class CommandsMixin:
414
414
 
415
415
  if not isinstance(message, dict):
416
416
  return None
417
+
418
+ # Match browser_download_url based on buildenv
419
+ download_url = None
420
+ buildenv = self._config.get("buildenv")
421
+ assets = message.get("assets", [])
422
+
423
+ if buildenv and assets:
424
+ target_filename = f"{buildenv}.bin"
425
+ for asset in assets:
426
+ if asset.get("name") == target_filename:
427
+ download_url = asset.get("browser_download_url")
428
+ break
429
+
417
430
  return {
418
431
  "latest_version": message.get("tag_name"),
419
432
  "release_notes": message.get("body"),
420
433
  "release_url": message.get("html_url"),
434
+ "browser_download_url": download_url,
421
435
  }
422
436
 
437
+ async def update_firmware(
438
+ self,
439
+ firmware_url: str | None = None,
440
+ firmware_bytes: bytes | None = None,
441
+ filename: str = "firmware.bin",
442
+ ) -> Mapping[str, Any] | list[Any] | str:
443
+ """Instruct the device to update its firmware.
444
+
445
+ You can either:
446
+ 1. Pass firmware_bytes to perform a multipart upload of a local file.
447
+ 2. Pass firmware_url to tell the device to download the file directly.
448
+ 3. Pass neither to automatically resolve the latest matching binary URL from GitHub.
449
+ """
450
+ if not self._version_check("4.1.7"):
451
+ _LOGGER.debug("Feature not supported for older firmware.")
452
+ raise UnsupportedFeature
453
+
454
+ if firmware_bytes is not None and firmware_url is not None:
455
+ raise ValueError("Cannot specify both firmware_bytes and firmware_url")
456
+
457
+ if firmware_url is not None:
458
+ if not isinstance(firmware_url, str) or not firmware_url.strip():
459
+ raise ValueError("Invalid firmware_url")
460
+
461
+ url = f"{self.url}update"
462
+
463
+ # 1. Handle multipart binary upload
464
+ if firmware_bytes is not None:
465
+ form_data = aiohttp.FormData()
466
+ form_data.add_field(
467
+ name="file",
468
+ value=firmware_bytes,
469
+ filename=filename,
470
+ content_type="application/octet-stream",
471
+ )
472
+ _LOGGER.debug(
473
+ "Uploading firmware binary to %s (%d bytes)", url, len(firmware_bytes)
474
+ )
475
+ # 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)
477
+
478
+ # 2. Resolve URL from GitHub if not specified
479
+ if firmware_url is None:
480
+ check_result = await self.firmware_check()
481
+ if not check_result or not check_result.get("browser_download_url"):
482
+ raise RuntimeError(
483
+ "Could not resolve latest firmware download URL from GitHub."
484
+ )
485
+ firmware_url = check_result["browser_download_url"]
486
+
487
+ # 3. Post JSON URL payload
488
+ data = {"url": firmware_url}
489
+ _LOGGER.debug(
490
+ "Requesting OpenEVSE to download and update from: %s", firmware_url
491
+ )
492
+ return await self.process_request(url=url, method="post", data=data)
493
+
423
494
  async def set_led_brightness(self, level: int) -> None:
424
495
  """Set LED brightness level."""
425
496
  if isinstance(level, bool) or not isinstance(level, int):
@@ -341,7 +341,17 @@ class PropertiesMixin:
341
341
  @property
342
342
  def ota_update(self) -> bool:
343
343
  """Return if an OTA update is active."""
344
- return self._status.get("ota_update", False)
344
+ return bool(self._status.get("ota_update", False))
345
+
346
+ @property
347
+ def ota_progress(self) -> int | None:
348
+ """Return the progress of the current OTA update."""
349
+ return self._status.get("ota_progress")
350
+
351
+ @property
352
+ def ota_state(self) -> str | None:
353
+ """Return the state of the current OTA update."""
354
+ return self._status.get("ota")
345
355
 
346
356
  @property
347
357
  def manual_override(self) -> bool:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python_openevse_http
3
- Version: 0.3.5
3
+ Version: 0.4.1
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,16 +1,16 @@
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=JBAC1jJGdzOabVAqHv8x6EJDVjhaW4t7Iwqn4lDWhwE,17502
4
- openevsehttp/commands.py,sha256=lANhgVhtJJlQxLwFlMrxk3DrmnY2bXK5h4z3o9o6ZEk,20617
3
+ openevsehttp/client.py,sha256=XaKet6ZldNVhFbZjoPksaahDNflXmOpeRpa1Jn9pmZ8,17882
4
+ openevsehttp/commands.py,sha256=DKgrzHiOa7wtu47PEMsQeLwYIFpIeYZczCGxPfPPir8,23538
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
8
- openevsehttp/properties.py,sha256=QVSyn_5a7vI1b4TdnnToRdw6veVCfnp7a19VYit95hg,17107
8
+ openevsehttp/properties.py,sha256=9fmJo6xU8im-Dd2QoH8f2E-0veD8uL1QQYiaERvIRr8,17430
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.3.5.dist-info/licenses/LICENSE,sha256=hSB6TOQ7rmwSGb6XzqRjDGMvmUj5_GlacqQin3tegtA,11341
13
- python_openevse_http-0.3.5.dist-info/METADATA,sha256=3VxDib4FCHGZ3QODauCoWzXQiPggRx_7q8yRvCwa7G8,4363
14
- python_openevse_http-0.3.5.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
15
- python_openevse_http-0.3.5.dist-info/top_level.txt,sha256=u8RUkoEIE33Cjn6gmqiEoVpZ0VZ59WJ3FXBwwOg0CPE,13
16
- python_openevse_http-0.3.5.dist-info/RECORD,,
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,,