python-openevse-http 0.1.54__tar.gz → 0.1.59__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.
Files changed (16) hide show
  1. {python-openevse-http-0.1.54 → python-openevse-http-0.1.59}/PKG-INFO +2 -1
  2. {python-openevse-http-0.1.54 → python-openevse-http-0.1.59}/openevsehttp/__main__.py +229 -10
  3. python-openevse-http-0.1.59/openevsehttp/const.py +17 -0
  4. {python-openevse-http-0.1.54 → python-openevse-http-0.1.59}/openevsehttp/exceptions.py +4 -0
  5. {python-openevse-http-0.1.54 → python-openevse-http-0.1.59}/openevsehttp/websocket.py +1 -0
  6. {python-openevse-http-0.1.54 → python-openevse-http-0.1.59}/python_openevse_http.egg-info/PKG-INFO +2 -1
  7. {python-openevse-http-0.1.54 → python-openevse-http-0.1.59}/setup.py +3 -1
  8. python-openevse-http-0.1.54/openevsehttp/const.py +0 -4
  9. {python-openevse-http-0.1.54 → python-openevse-http-0.1.59}/README.md +0 -0
  10. {python-openevse-http-0.1.54 → python-openevse-http-0.1.59}/openevsehttp/__init__.py +0 -0
  11. {python-openevse-http-0.1.54 → python-openevse-http-0.1.59}/python_openevse_http.egg-info/SOURCES.txt +0 -0
  12. {python-openevse-http-0.1.54 → python-openevse-http-0.1.59}/python_openevse_http.egg-info/dependency_links.txt +0 -0
  13. {python-openevse-http-0.1.54 → python-openevse-http-0.1.59}/python_openevse_http.egg-info/not-zip-safe +0 -0
  14. {python-openevse-http-0.1.54 → python-openevse-http-0.1.59}/python_openevse_http.egg-info/requires.txt +0 -0
  15. {python-openevse-http-0.1.54 → python-openevse-http-0.1.59}/python_openevse_http.egg-info/top_level.txt +0 -0
  16. {python-openevse-http-0.1.54 → python-openevse-http-0.1.59}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-openevse-http
3
- Version: 0.1.54
3
+ Version: 0.1.59
4
4
  Summary: Python wrapper for OpenEVSE HTTP API
5
5
  Home-page: https://github.com/firstof9/python-openevse-http
6
6
  Author: firstof9
@@ -14,6 +14,7 @@ Classifier: Natural Language :: English
14
14
  Classifier: Programming Language :: Python :: 3
15
15
  Classifier: Programming Language :: Python :: 3.10
16
16
  Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
17
18
  Requires-Python: >=3.10
18
19
  Description-Content-Type: text/markdown
19
20
 
@@ -1,10 +1,12 @@
1
1
  """Main librbary functions for python-openevse-http."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import asyncio
5
6
  import datetime
6
7
  import json
7
8
  import logging
9
+ import re
8
10
  from typing import Any, Callable, Dict, Union
9
11
 
10
12
  import aiohttp # type: ignore
@@ -12,10 +14,24 @@ from aiohttp.client_exceptions import ContentTypeError, ServerTimeoutError
12
14
  from awesomeversion import AwesomeVersion
13
15
  from awesomeversion.exceptions import AwesomeVersionCompareException
14
16
 
15
- from .const import MAX_AMPS, MIN_AMPS
17
+ from .const import (
18
+ BAT_LVL,
19
+ BAT_RANGE,
20
+ CLIENT,
21
+ GRID,
22
+ MAX_AMPS,
23
+ MIN_AMPS,
24
+ RELEASE,
25
+ SOLAR,
26
+ TTF,
27
+ TYPE,
28
+ VALUE,
29
+ VOLTAGE,
30
+ )
16
31
  from .exceptions import (
17
32
  AlreadyListening,
18
33
  AuthenticationError,
34
+ InvalidType,
19
35
  MissingMethod,
20
36
  MissingSerial,
21
37
  ParseJSONError,
@@ -120,13 +136,17 @@ class OpenEVSE:
120
136
  _LOGGER.warning("Non JSON response: %s", message)
121
137
 
122
138
  if resp.status == 400:
123
- _LOGGER.error("Error 400: %s", message["msg"])
139
+ if "msg" in message.keys():
140
+ index = "msg"
141
+ elif "error" in message.keys():
142
+ index = "error"
143
+ _LOGGER.error("Error 400: %s", message[index])
124
144
  raise ParseJSONError
125
145
  if resp.status == 401:
126
146
  _LOGGER.error("Authentication error: %s", message)
127
147
  raise AuthenticationError
128
148
  if resp.status in [404, 405, 500]:
129
- _LOGGER.error("%s", message)
149
+ _LOGGER.warning("%s", message)
130
150
 
131
151
  if method == "post" and "config_version" in message:
132
152
  await self.update()
@@ -552,7 +572,16 @@ class OpenEVSE:
552
572
  if max_version != "":
553
573
  limit = AwesomeVersion(max_version)
554
574
 
575
+ firmware_filtered = None
576
+
577
+ try:
578
+ firmware_search = re.search("\\d\\.\\d\\.\\d", self._config["version"])
579
+ if firmware_search is not None:
580
+ firmware_filtered = firmware_search[0]
581
+ except Exception: # pylint: disable=broad-exception-caught
582
+ _LOGGER.warning("Non-standard versioning string.")
555
583
  _LOGGER.debug("Detected firmware: %s", self._config["version"])
584
+ _LOGGER.debug("Filtered firmware: %s", firmware_filtered)
556
585
 
557
586
  if "dev" in self._config["version"]:
558
587
  value = self._config["version"]
@@ -562,7 +591,7 @@ class OpenEVSE:
562
591
  elif "master" in self._config["version"]:
563
592
  value = "dev"
564
593
  else:
565
- value = self._config["version"]
594
+ value = firmware_filtered
566
595
 
567
596
  current = AwesomeVersion(value)
568
597
 
@@ -581,24 +610,214 @@ class OpenEVSE:
581
610
  _LOGGER.debug("Non-semver firmware version detected.")
582
611
  return False
583
612
 
613
+ # HTTP Posting of grid voltage
614
+
615
+ async def grid_voltage(self, voltage: int | None = None) -> None:
616
+ """Send pushed sensor data to grid voltage."""
617
+ if not self._version_check("4.0.0"):
618
+ _LOGGER.debug("Feature not supported for older firmware.")
619
+ raise UnsupportedFeature
620
+
621
+ url = f"{self.url}status"
622
+ data = {}
623
+
624
+ if voltage is not None:
625
+ data[VOLTAGE] = voltage
626
+
627
+ if not data:
628
+ _LOGGER.info("No sensor data to send to device.")
629
+ else:
630
+ _LOGGER.debug("Posting voltage: %s", data)
631
+ response = await self.process_request(url=url, method="post", data=data)
632
+ _LOGGER.debug("Voltage posting response: %s", response)
633
+
584
634
  # Self production HTTP Posting
585
635
 
586
- async def self_production(self, grid: int, solar: int, invert: bool = True) -> None:
636
+ async def self_production(
637
+ self,
638
+ grid: int | None = None,
639
+ solar: int | None = None,
640
+ invert: bool = True,
641
+ voltage: int | None = None,
642
+ ) -> None:
587
643
  """Send pushed sensor data to self-prodcution."""
588
644
  if not self._version_check("4.0.0"):
589
645
  _LOGGER.debug("Feature not supported for older firmware.")
590
646
  raise UnsupportedFeature
591
647
 
592
648
  # Invert the sensor -import/+export
593
- if invert:
649
+ if invert and grid is not None:
594
650
  grid = grid * -1
595
651
 
596
652
  url = f"{self.url}status"
597
- data = {"solar": solar, "grid_ie": grid}
653
+ data = {}
654
+
655
+ # Prefer grid sensor data
656
+ if grid is not None:
657
+ data[GRID] = grid
658
+ elif solar is not None:
659
+ data[SOLAR] = solar
660
+ if voltage is not None:
661
+ data[VOLTAGE] = voltage
662
+
663
+ if not data:
664
+ _LOGGER.info("No sensor data to send to device.")
665
+ else:
666
+ _LOGGER.debug("Posting self-production: %s", data)
667
+ response = await self.process_request(url=url, method="post", data=data)
668
+ _LOGGER.debug("Self-production response: %s", response)
669
+
670
+ # State of charge HTTP posting
671
+ async def soc(
672
+ self,
673
+ battery_level: int | None = None,
674
+ battery_range: int | None = None,
675
+ time_to_full: int | None = None,
676
+ voltage: int | None = None,
677
+ ) -> None:
678
+ """Send pushed sensor data to self-prodcution."""
679
+ if not self._version_check("4.1.0"):
680
+ _LOGGER.debug("Feature not supported for older firmware.")
681
+ raise UnsupportedFeature
682
+
683
+ url = f"{self.url}status"
684
+ data = {}
685
+
686
+ # Build post data
687
+ if battery_level is not None:
688
+ data[BAT_LVL] = battery_level
689
+ if battery_range is not None:
690
+ data[BAT_RANGE] = battery_range
691
+ if time_to_full is not None:
692
+ data[TTF] = time_to_full
693
+ if voltage is not None:
694
+ data[VOLTAGE] = voltage
695
+
696
+ if not data:
697
+ _LOGGER.info("No SOC data to send to device.")
698
+ else:
699
+ _LOGGER.debug("Posting SOC data: %s", data)
700
+ response = await self.process_request(url=url, method="post", data=data)
701
+ _LOGGER.debug("SOC response: %s", response)
702
+
703
+ # Limit endpoint
704
+ async def set_limit(
705
+ self, limit_type: str, value: int, release: bool | None = None
706
+ ) -> Any:
707
+ """Set charge limit."""
708
+ if not self._version_check("5.0.0"):
709
+ _LOGGER.debug("Feature not supported for older firmware.")
710
+ raise UnsupportedFeature
711
+
712
+ url = f"{self.url}limit"
713
+ data: Dict[str, Any] = {}
714
+ valid_types = ["time", "energy", "soc", "range"]
715
+
716
+ if limit_type not in valid_types:
717
+ raise InvalidType
718
+
719
+ data[TYPE] = limit_type
720
+ data[VALUE] = value
721
+ if release is not None:
722
+ data[RELEASE] = release
598
723
 
599
- _LOGGER.debug("Posting self-production: %s", data)
600
- response = await self.process_request(url=url, method="post", data=data)
601
- _LOGGER.debug("Self-production response: %s", response)
724
+ _LOGGER.debug("Limit data: %s", data)
725
+ _LOGGER.debug("Setting limit config on %s", url)
726
+ response = await self.process_request(
727
+ url=url, method="post", data=data
728
+ ) # noqa: E501
729
+ return response
730
+
731
+ async def clear_limit(self) -> Any:
732
+ """Clear charge limit."""
733
+ if not self._version_check("5.0.0"):
734
+ _LOGGER.debug("Feature not supported for older firmware.")
735
+ raise UnsupportedFeature
736
+
737
+ url = f"{self.url}limit"
738
+ data: Dict[str, Any] = {}
739
+
740
+ _LOGGER.debug("Clearing limit config on %s", url)
741
+ response = await self.process_request(
742
+ url=url, method="delete", data=data
743
+ ) # noqa: E501
744
+ return response
745
+
746
+ async def get_limit(self) -> Any:
747
+ """Get charge limit."""
748
+ if not self._version_check("5.0.0"):
749
+ _LOGGER.debug("Feature not supported for older firmware.")
750
+ raise UnsupportedFeature
751
+
752
+ url = f"{self.url}limit"
753
+ data: Dict[str, Any] = {}
754
+
755
+ _LOGGER.debug("Getting limit config on %s", url)
756
+ response = await self.process_request(
757
+ url=url, method="get", data=data
758
+ ) # noqa: E501
759
+ return response
760
+
761
+ async def make_claim(
762
+ self,
763
+ state: str | None = None,
764
+ charge_current: int | None = None,
765
+ max_current: int | None = None,
766
+ auto_release: bool = True,
767
+ client: int = CLIENT,
768
+ ) -> Any:
769
+ """Make a claim."""
770
+ if not self._version_check("4.1.0"):
771
+ _LOGGER.debug("Feature not supported for older firmware.")
772
+ raise UnsupportedFeature
773
+
774
+ if state not in ["active", "disabled", None]:
775
+ _LOGGER.error("Invalid claim state: %s", state)
776
+ raise ValueError
777
+
778
+ url = f"{self.url}claims/{client}"
779
+
780
+ data: dict[str, Any] = {}
781
+
782
+ data["auto_release"] = auto_release
783
+
784
+ if state is not None:
785
+ data["state"] = state
786
+ if charge_current is not None:
787
+ data["charge_current"] = charge_current
788
+ if max_current is not None:
789
+ data["max_current"] = max_current
790
+
791
+ _LOGGER.debug("Claim data: %s", data)
792
+ _LOGGER.debug("Setting up claim on %s", url)
793
+ response = await self.process_request(
794
+ url=url, method="post", data=data
795
+ ) # noqa: E501
796
+ return response
797
+
798
+ async def release_claim(self, client: int = CLIENT) -> Any:
799
+ """Delete a claim."""
800
+ if not self._version_check("4.1.0"):
801
+ _LOGGER.debug("Feature not supported for older firmware.")
802
+ raise UnsupportedFeature
803
+
804
+ url = f"{self.url}claims/{client}"
805
+
806
+ _LOGGER.debug("Releasing claim on %s", url)
807
+ response = await self.process_request(url=url, method="delete") # noqa: E501
808
+ return response
809
+
810
+ async def list_claims(self) -> Any:
811
+ """List all claims."""
812
+ if not self._version_check("4.1.0"):
813
+ _LOGGER.debug("Feature not supported for older firmware.")
814
+ raise UnsupportedFeature
815
+
816
+ url = f"{self.url}claims"
817
+
818
+ _LOGGER.debug("Getting claims on %s", url)
819
+ response = await self.process_request(url=url, method="get") # noqa: E501
820
+ return response
602
821
 
603
822
  @property
604
823
  def hostname(self) -> str:
@@ -0,0 +1,17 @@
1
+ """Constants for the OpenEVSE HTTP python library."""
2
+
3
+ USER_AGENT = "python-openevse-http"
4
+ MIN_AMPS = 6
5
+ MAX_AMPS = 48
6
+
7
+ SOLAR = "solar"
8
+ GRID = "grid_ie"
9
+ BAT_LVL = "battery_level"
10
+ BAT_RANGE = "battery_range"
11
+ TTF = "time_to_full_charge"
12
+ VOLTAGE = "voltage"
13
+ SHAPER_LIVE = "shaper_live_pwr"
14
+ TYPE = "type"
15
+ VALUE = "value"
16
+ RELEASE = "release"
17
+ CLIENT = 4
@@ -27,3 +27,7 @@ class MissingSerial(Exception):
27
27
 
28
28
  class UnsupportedFeature(Exception):
29
29
  """Exception for firmware that is too old."""
30
+
31
+
32
+ class InvalidType(Exception):
33
+ """Exception for invalid types."""
@@ -1,4 +1,5 @@
1
1
  """Websocket class for OpenEVSE HTTP."""
2
+
2
3
  import asyncio
3
4
  import logging
4
5
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-openevse-http
3
- Version: 0.1.54
3
+ Version: 0.1.59
4
4
  Summary: Python wrapper for OpenEVSE HTTP API
5
5
  Home-page: https://github.com/firstof9/python-openevse-http
6
6
  Author: firstof9
@@ -14,6 +14,7 @@ Classifier: Natural Language :: English
14
14
  Classifier: Programming Language :: Python :: 3
15
15
  Classifier: Programming Language :: Python :: 3.10
16
16
  Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
17
18
  Requires-Python: >=3.10
18
19
  Description-Content-Type: text/markdown
19
20
 
@@ -1,11 +1,12 @@
1
1
  """Setup module for python-openevse-http."""
2
+
2
3
  from pathlib import Path
3
4
 
4
5
  from setuptools import find_packages, setup
5
6
 
6
7
  PROJECT_DIR = Path(__file__).parent.resolve()
7
8
  README_FILE = PROJECT_DIR / "README.md"
8
- VERSION = "0.1.54"
9
+ VERSION = "0.1.59"
9
10
 
10
11
  setup(
11
12
  name="python-openevse-http",
@@ -30,5 +31,6 @@ setup(
30
31
  "Programming Language :: Python :: 3",
31
32
  "Programming Language :: Python :: 3.10",
32
33
  "Programming Language :: Python :: 3.11",
34
+ "Programming Language :: Python :: 3.12",
33
35
  ],
34
36
  )
@@ -1,4 +0,0 @@
1
- """Constants for the OpenEVSE HTTP python library."""
2
- USER_AGENT = "python-openevse-http"
3
- MIN_AMPS = 6
4
- MAX_AMPS = 48