From 4eed75f947323bfef4032aac8211befbbefbc4da Mon Sep 17 00:00:00 2001
From: CV8R <39397297+CV8R@users.noreply.github.com>
Date: Sat, 9 Mar 2024 23:55:39 +1100
Subject: [PATCH 1/8] Modify target_temperature to not return setpoint when in
Fan or Off mode.
---
custom_components/intesisbox/climate.py | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/custom_components/intesisbox/climate.py b/custom_components/intesisbox/climate.py
index 705a7f3..e536152 100644
--- a/custom_components/intesisbox/climate.py
+++ b/custom_components/intesisbox/climate.py
@@ -4,6 +4,8 @@
https://github.com/jnimmo/hass-intesisbox
"""
+from __future__ import annotations
+
import asyncio
from datetime import timedelta
import logging
@@ -406,8 +408,10 @@ def hvac_mode(self):
@property
def target_temperature(self):
- """Return the current setpoint temperature if unit is on."""
- return self._target_temperature
+ """Return the current setpoint temperature if unit is on and not FAN or OFF Mode."""
+ if self._power and self.hvac_mode not in [HVACMode.FAN_ONLY, HVACMode.OFF]:
+ return self._target_temperature
+ return None
@property
def supported_features(self):
From ca8654064e38cc9fbe8dd7e290a6a335ebe639e7 Mon Sep 17 00:00:00 2001
From: CV8R <39397297+CV8R@users.noreply.github.com>
Date: Sun, 10 Mar 2024 00:03:22 +1100
Subject: [PATCH 2/8] Added a poll ambtemp per guidance from Intesis and to
remove "is taking over 10 seconds" in HA error log.
Added an async delay function
Added a writeasync with delay to slow down send of commands
Modify set_mode to handle situation where IntesisBox returns out of order.
Ensure that mode is set prior turning on unit.
---
custom_components/intesisbox/intesisbox.py | 58 +++++++++++++++++++---
1 file changed, 51 insertions(+), 7 deletions(-)
diff --git a/custom_components/intesisbox/intesisbox.py b/custom_components/intesisbox/intesisbox.py
index 38105c9..ebd1de3 100644
--- a/custom_components/intesisbox/intesisbox.py
+++ b/custom_components/intesisbox/intesisbox.py
@@ -1,5 +1,7 @@
"""Communication with an Intesisbox device."""
+from __future__ import annotations
+
import asyncio
from asyncio import BaseTransport, ensure_future
from collections.abc import Callable
@@ -78,6 +80,15 @@ async def keep_alive(self):
else:
_LOGGER.debug("Not connected, skipping keepalive")
+ async def poll_ambtemp(self):
+ """Retrieve Ambient Temperature to prevent integration timeouts."""
+ while self.is_connected:
+ _LOGGER.debug("Sending AMBTEMP")
+ self._write("GET,1:AMBTEMP")
+ await asyncio.sleep(10)
+ else:
+ _LOGGER.debug("Not connected, skipping Ambient Temp Request")
+
async def query_initial_state(self):
"""Fetch configuration from the device upon connection."""
cmds = [
@@ -92,10 +103,22 @@ async def query_initial_state(self):
self._write(cmd)
await asyncio.sleep(1)
+ async def delay(self, delay_time):
+ """Async Delay to slow down commands and await response from units."""
+ _LOGGER.debug(f"Delay Started for {delay_time} seconds...")
+ await asyncio.sleep(delay_time)
+ _LOGGER.debug("Delay Ended...")
+
def _write(self, cmd):
self._transport.write(f"{cmd}\r".encode("ascii"))
_LOGGER.debug(f"Data sent: {cmd!r}")
+ async def _writeasync(self, cmd):
+ """Async write to slow down commands and await response from units."""
+ self._transport.write(f"{cmd}\r".encode("ascii"))
+ _LOGGER.debug(f"Data sent: {cmd!r}")
+ await asyncio.sleep(1)
+
def data_received(self, data):
"""Asyncio callback when data is received on the socket."""
linesReceived = data.decode("ascii").splitlines()
@@ -111,8 +134,9 @@ def data_received(self, data):
if cmd == "ID":
self._parse_id_received(args)
self._connectionStatus = API_AUTHENTICATED
- _ = asyncio.ensure_future(self.keep_alive())
+ # _ = asyncio.ensure_future(self.keep_alive())
_ = asyncio.ensure_future(self.poll_status())
+ _ = asyncio.ensure_future(self.poll_ambtemp())
elif cmd == "CHN,1":
self._parse_change_received(args)
statusChanged = True
@@ -244,17 +268,37 @@ def set_horizontal_vane(self, vane: str):
def _set_value(self, uid: str, value: str | int) -> None:
"""Change a setting on the thermostat."""
try:
- self._write(f"SET,1:{uid},{value}")
+ # self._write(f"SET,1:{uid},{value}")
+ asyncio.run(self._writeasync(f"SET,1:{uid},{value}"))
except Exception as e:
_LOGGER.error("%s Exception. %s / %s", type(e), e.args, e)
- def set_mode(self, mode: str) -> None:
- """Change the thermostat mode (heat, cool, etc)."""
- if not self.is_on:
- self.set_power_on()
-
+ def set_mode(self, mode):
+ """Send mode and confirm change before turning on."""
+ """ Some units return responses out of order"""
+ _LOGGER.debug(f"Setting MODE to {mode}.")
if mode in MODES:
self._set_value(FUNCTION_MODE, mode)
+ if not self.is_on:
+ """ "Check to ensure in correct mode before turning on"""
+ retry = 30
+ while self.mode != mode and retry > 0:
+ _LOGGER.debug(
+ f"Waiting for MODE to return {mode}, currently {str(self.mode)}"
+ )
+ _LOGGER.debug(f"Retry attempt = {retry}")
+ # asyncio.run(self.delay(1)) # SHANE
+ # self._write("GET,1:MODE")
+ asyncio.run(self._writeasync("GET,1:MODE"))
+ retry -= 1
+ else:
+ if retry != 0:
+ _LOGGER.debug(
+ f"MODE confirmed now {str(self.mode)}, proceed to Power On"
+ )
+ self.set_power_on()
+ else:
+ _LOGGER.error("Cannot set Intesisbox mode giving up...")
def set_mode_dry(self):
"""Public method to set device to dry asynchronously."""
From 2a5236097f061b54f0f7668bd95a6c3b77497a38 Mon Sep 17 00:00:00 2001
From: CV8R <39397297+CV8R@users.noreply.github.com>
Date: Wed, 13 Mar 2024 15:02:30 +1100
Subject: [PATCH 3/8] Remove commented code.
---
.idea/hass-intesisbox.iml | 7 ++
.../inspectionProfiles/profiles_settings.xml | 6 +
.idea/misc.xml | 4 +
.idea/shelf/Changes.xml | 4 +
.idea/shelf/Changes/shelved.patch | 112 ++++++++++++++++++
.idea/vcs.xml | 6 +
.idea/workspace.xml | 102 ++++++++++++++++
custom_components/.DS_Store | Bin 0 -> 6148 bytes
custom_components/intesisbox/.DS_Store | Bin 0 -> 6148 bytes
custom_components/intesisbox/intesisbox.py | 14 +--
10 files changed, 243 insertions(+), 12 deletions(-)
create mode 100644 .idea/hass-intesisbox.iml
create mode 100644 .idea/inspectionProfiles/profiles_settings.xml
create mode 100644 .idea/misc.xml
create mode 100644 .idea/shelf/Changes.xml
create mode 100644 .idea/shelf/Changes/shelved.patch
create mode 100644 .idea/vcs.xml
create mode 100644 .idea/workspace.xml
create mode 100644 custom_components/.DS_Store
create mode 100644 custom_components/intesisbox/.DS_Store
diff --git a/.idea/hass-intesisbox.iml b/.idea/hass-intesisbox.iml
new file mode 100644
index 0000000..ec63674
--- /dev/null
+++ b/.idea/hass-intesisbox.iml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..974cdaf
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/shelf/Changes.xml b/.idea/shelf/Changes.xml
new file mode 100644
index 0000000..a345c1c
--- /dev/null
+++ b/.idea/shelf/Changes.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/shelf/Changes/shelved.patch b/.idea/shelf/Changes/shelved.patch
new file mode 100644
index 0000000..f81e5e5
--- /dev/null
+++ b/.idea/shelf/Changes/shelved.patch
@@ -0,0 +1,112 @@
+Index: custom_components/intesisbox/intesisbox.py
+IDEA additional info:
+Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
+<+>\"\"\"Communication with an Intesisbox device.\"\"\"\n\nimport asyncio\nfrom asyncio import BaseTransport, ensure_future\nfrom collections.abc import Callable\nimport logging\n\n_LOGGER = logging.getLogger(__name__)\n\nAPI_DISCONNECTED = \"Disconnected\"\nAPI_CONNECTING = \"Connecting\"\nAPI_AUTHENTICATED = \"Connected\"\n\nPOWER_ON = \"ON\"\nPOWER_OFF = \"OFF\"\nPOWER_STATES = [POWER_ON, POWER_OFF]\n\nMODE_AUTO = \"AUTO\"\nMODE_DRY = \"DRY\"\nMODE_FAN = \"FAN\"\nMODE_COOL = \"COOL\"\nMODE_HEAT = \"HEAT\"\nMODES = [MODE_AUTO, MODE_DRY, MODE_FAN, MODE_COOL, MODE_HEAT]\n\nFUNCTION_ONOFF = \"ONOFF\"\nFUNCTION_MODE = \"MODE\"\nFUNCTION_SETPOINT = \"SETPTEMP\"\nFUNCTION_FANSP = \"FANSP\"\nFUNCTION_VANEUD = \"VANEUD\"\nFUNCTION_VANELR = \"VANELR\"\nFUNCTION_AMBTEMP = \"AMBTEMP\"\nFUNCTION_ERRSTATUS = \"ERRSTATUS\"\nFUNCTION_ERRCODE = \"ERRCODE\"\n\nNULL_VALUES = [\"-32768\", \"32768\"]\n\n\nclass IntesisBox(asyncio.Protocol):\n \"\"\"Handles communication with an intesisbox device via WMP.\"\"\"\n\n def __init__(self, ip: str, port: int = 3310, loop=None):\n \"\"\"Set up base state.\"\"\"\n self._ip = ip\n self._port = port\n self._mac = None\n self._device: dict[str, str] = {}\n self._connectionStatus = API_DISCONNECTED\n self._transport: BaseTransport | None = None\n self._updateCallbacks: list[Callable[[], None]] = []\n self._errorCallbacks: list[Callable[[str], None]] = []\n self._errorMessage: str | None = None\n self._controllerType = None\n self._model: str | None = None\n self._firmversion: str | None = None\n self._rssi: int | None = None\n self._eventLoop = loop\n\n # Limits\n self._operation_list: list[str] = []\n self._fan_speed_list: list[str] = []\n self._vertical_vane_list: list[str] = []\n self._horizontal_vane_list: list[str] = []\n self._setpoint_minimum: int | None = None\n self._setpoint_maximum: int | None = None\n\n def connection_made(self, transport: BaseTransport):\n \"\"\"Asyncio callback for a successful connection.\"\"\"\n _LOGGER.debug(\"Connected to IntesisBox\")\n self._transport = transport\n _ = asyncio.ensure_future(self.query_initial_state())\n\n async def keep_alive(self):\n \"\"\"Send a keepalive command to reset it's watchdog timer.\"\"\"\n while self.is_connected:\n _LOGGER.debug(\"Sending keepalive\")\n self._write(\"PING\")\n await asyncio.sleep(45)\n else:\n _LOGGER.debug(\"Not connected, skipping keepalive\")\n\n async def query_initial_state(self):\n \"\"\"Fetch configuration from the device upon connection.\"\"\"\n cmds = [\n \"ID\",\n \"LIMITS:SETPTEMP\",\n \"LIMITS:FANSP\",\n \"LIMITS:MODE\",\n \"LIMITS:VANEUD\",\n \"LIMITS:VANELR\",\n ]\n for cmd in cmds:\n self._write(cmd)\n await asyncio.sleep(1)\n\n def _write(self, cmd):\n self._transport.write(f\"{cmd}\\r\".encode(\"ascii\"))\n _LOGGER.debug(f\"Data sent: {cmd!r}\")\n\n def data_received(self, data):\n \"\"\"Asyncio callback when data is received on the socket.\"\"\"\n linesReceived = data.decode(\"ascii\").splitlines()\n statusChanged = False\n\n for line in linesReceived:\n _LOGGER.debug(f\"Data received: {line!r}\")\n cmdList = line.split(\":\", 1)\n cmd = cmdList[0]\n args = None\n if len(cmdList) > 1:\n args = cmdList[1]\n if cmd == \"ID\":\n self._parse_id_received(args)\n self._connectionStatus = API_AUTHENTICATED\n _ = asyncio.ensure_future(self.keep_alive())\n _ = asyncio.ensure_future(self.poll_status())\n elif cmd == \"CHN,1\":\n self._parse_change_received(args)\n statusChanged = True\n elif cmd == \"LIMITS\":\n self._parse_limits_received(args)\n statusChanged = True\n\n if statusChanged:\n self._send_update_callback()\n\n def _parse_id_received(self, args):\n # ID:Model,MAC,IP,Protocol,Version,RSSI\n info = args.split(\",\")\n if len(info) >= 6:\n self._model = info[0]\n self._mac = info[1]\n self._firmversion = info[4]\n self._rssi = info[5]\n\n _LOGGER.debug(\n \"Updated info:\",\n f\"model:{self._model}\",\n f\"mac:{self._mac}\",\n f\"version:{self._firmversion}\",\n f\"rssi:{self._rssi}\",\n )\n\n def _parse_change_received(self, args):\n function = args.split(\",\")[0]\n value = args.split(\",\")[1]\n if value in NULL_VALUES:\n value = None\n self._device[function] = value\n\n _LOGGER.debug(f\"Updated state: {self._device!r}\")\n\n def _parse_limits_received(self, args):\n split_args = args.split(\",\", 1)\n\n if len(split_args) == 2:\n function = split_args[0]\n values = split_args[1][1:-1].split(\",\")\n\n if function == FUNCTION_SETPOINT and len(values) == 2:\n self._setpoint_minimum = int(values[0]) / 10\n self._setpoint_maximum = int(values[1]) / 10\n elif function == FUNCTION_FANSP:\n self._fan_speed_list = values\n elif function == FUNCTION_MODE:\n self._operation_list = values\n elif function == FUNCTION_VANEUD:\n self._vertical_vane_list = values\n elif function == FUNCTION_VANELR:\n self._horizontal_vane_list = values\n\n _LOGGER.debug(\n \"Updated limits: \",\n f\"{self._setpoint_minimum=}\",\n f\"{self._setpoint_maximum=}\",\n f\"{self._fan_speed_list=}\",\n f\"{self._operation_list=}\",\n f\"{self._vertical_vane_list=}\",\n f\"{self._horizontal_vane_list=}\",\n )\n return\n\n def connection_lost(self, exc):\n \"\"\"Asyncio callback for a lost TCP connection.\"\"\"\n self._connectionStatus = API_DISCONNECTED\n _LOGGER.info(\"The server closed the connection\")\n self._send_update_callback()\n\n def connect(self):\n \"\"\"Public method for connecting to IntesisHome API.\"\"\"\n if self._connectionStatus == API_DISCONNECTED:\n self._connectionStatus = API_CONNECTING\n try:\n # Must poll to get the authentication token\n if self._ip and self._port:\n # Create asyncio socket\n coro = self._eventLoop.create_connection(\n lambda: self, self._ip, self._port\n )\n _LOGGER.debug(\n \"Opening connection to IntesisBox %s:%s\", self._ip, self._port\n )\n _ = ensure_future(coro, loop=self._eventLoop)\n else:\n _LOGGER.debug(\"Missing IP address or port.\")\n self._connectionStatus = API_DISCONNECTED\n\n except Exception as e:\n _LOGGER.error(\"%s Exception. %s / %s\", type(e), repr(e.args), e)\n self._connectionStatus = API_DISCONNECTED\n else:\n _LOGGER.debug(\"connect() called but already connecting\")\n\n def stop(self):\n \"\"\"Public method for shutting down connectivity with the envisalink.\"\"\"\n self._connectionStatus = API_DISCONNECTED\n self._transport.close()\n\n async def poll_status(self, sendcallback=False):\n \"\"\"Periodically poll for updates since the controllers don't always update reliably.\"\"\"\n while self.is_connected:\n _LOGGER.debug(\"Polling for update\")\n self._write(\"GET,1:*\")\n await asyncio.sleep(60 * 5) # 5 minutes\n else:\n _LOGGER.debug(\"Not connected, skipping poll_status()\")\n\n def set_temperature(self, setpoint):\n \"\"\"Public method for setting the temperature.\"\"\"\n set_temp = int(setpoint * 10)\n self._set_value(FUNCTION_SETPOINT, set_temp)\n\n def set_fan_speed(self, fan_speed):\n \"\"\"Public method to set the fan speed.\"\"\"\n self._set_value(FUNCTION_FANSP, fan_speed)\n\n def set_vertical_vane(self, vane: str):\n \"\"\"Public method to set the vertical vane.\"\"\"\n self._set_value(FUNCTION_VANEUD, vane)\n\n def set_horizontal_vane(self, vane: str):\n \"\"\"Public method to set the horizontal vane.\"\"\"\n self._set_value(FUNCTION_VANELR, vane)\n\n def _set_value(self, uid: str, value: str | int) -> None:\n \"\"\"Change a setting on the thermostat.\"\"\"\n try:\n self._write(f\"SET,1:{uid},{value}\")\n except Exception as e:\n _LOGGER.error(\"%s Exception. %s / %s\", type(e), e.args, e)\n\n def set_mode(self, mode: str) -> None:\n \"\"\"Change the thermostat mode (heat, cool, etc).\"\"\"\n if not self.is_on:\n self.set_power_on()\n\n if mode in MODES:\n self._set_value(FUNCTION_MODE, mode)\n\n def set_mode_dry(self):\n \"\"\"Public method to set device to dry asynchronously.\"\"\"\n self._set_value(FUNCTION_MODE, MODE_DRY)\n\n def set_power_off(self):\n \"\"\"Public method to turn off the device asynchronously.\"\"\"\n self._set_value(FUNCTION_ONOFF, POWER_OFF)\n\n def set_power_on(self):\n \"\"\"Public method to turn on the device asynchronously.\"\"\"\n self._set_value(FUNCTION_ONOFF, POWER_ON)\n\n @property\n def operation_list(self) -> list[str]:\n \"\"\"Supported modes.\"\"\"\n return self._operation_list\n\n @property\n def vane_horizontal_list(self) -> list[str]:\n \"\"\"Supported Horizontal Vane settings.\"\"\"\n return self._horizontal_vane_list\n\n @property\n def vane_vertical_list(self) -> list[str]:\n \"\"\"Supported Vertical Vane settings.\"\"\"\n return self._vertical_vane_list\n\n @property\n def mode(self) -> str | None:\n \"\"\"Current mode.\"\"\"\n return self._device.get(FUNCTION_MODE)\n\n @property\n def fan_speed(self) -> str | None:\n \"\"\"Current fan speed.\"\"\"\n return self._device.get(FUNCTION_FANSP)\n\n @property\n def fan_speed_list(self) -> list[str]:\n \"\"\"Supported fan speeds.\"\"\"\n return self._fan_speed_list\n\n @property\n def device_mac_address(self) -> str | None:\n \"\"\"MAC address of the IntesisBox.\"\"\"\n return self._mac\n\n @property\n def device_model(self) -> str | None:\n \"\"\"Model of the IntesisBox.\"\"\"\n return self._model\n\n @property\n def firmware_version(self) -> str | None:\n \"\"\"Firmware versioon of the IntesisBox.\"\"\"\n return self._firmversion\n\n @property\n def is_on(self) -> bool:\n \"\"\"Return true if the controlled device is turned on.\"\"\"\n return self._device.get(FUNCTION_ONOFF) == POWER_ON\n\n @property\n def has_swing_control(self) -> bool:\n \"\"\"Return true if the device supports swing modes.\"\"\"\n return len(self._horizontal_vane_list) > 1 or len(self._vertical_vane_list) > 1\n\n @property\n def setpoint(self) -> float | None:\n \"\"\"Public method returns the target temperature.\"\"\"\n setpoint = self._device.get(FUNCTION_SETPOINT)\n return (int(setpoint) / 10) if setpoint else None\n\n @property\n def ambient_temperature(self) -> float | None:\n \"\"\"Public method returns the current temperature.\"\"\"\n temperature = self._device.get(FUNCTION_AMBTEMP)\n return (int(temperature) / 10) if temperature else None\n\n @property\n def max_setpoint(self) -> float | None:\n \"\"\"Maximum allowed target temperature.\"\"\"\n return self._setpoint_maximum\n\n @property\n def min_setpoint(self) -> float | None:\n \"\"\"Minimum allowed target temperature.\"\"\"\n return self._setpoint_minimum\n\n @property\n def rssi(self) -> int | None:\n \"\"\"Wireless signal strength of the IntesisBox.\"\"\"\n return self._rssi\n\n @property\n def vertical_swing(self) -> str | None:\n \"\"\"Current vertical vane setting.\"\"\"\n return self._device.get(FUNCTION_VANEUD)\n\n @property\n def horizontal_swing(self) -> str | None:\n \"\"\"Current horizontal vane setting.\"\"\"\n return self._device.get(FUNCTION_VANELR)\n\n def _send_update_callback(self):\n \"\"\"Notify all listeners that state of the thermostat has changed.\"\"\"\n if not self._updateCallbacks:\n _LOGGER.debug(\"Update callback has not been set by client.\")\n\n for callback in self._updateCallbacks:\n callback()\n\n def _send_error_callback(self, message: str):\n \"\"\"Notify all listeners that an error has occurred.\"\"\"\n self._errorMessage = message\n\n if self._errorCallbacks == []:\n _LOGGER.debug(\"Error callback has not been set by client.\")\n\n for callback in self._errorCallbacks:\n callback(message)\n\n @property\n def is_connected(self) -> bool:\n \"\"\"Returns true if the TCP connection is established.\"\"\"\n return self._connectionStatus == API_AUTHENTICATED\n\n @property\n def error_message(self) -> str | None:\n \"\"\"Returns the last error message, or None if there were no errors.\"\"\"\n return self._errorMessage\n\n @property\n def is_disconnected(self) -> bool:\n \"\"\"Returns true when the TCP connection is disconnected and idle.\"\"\"\n return self._connectionStatus == API_DISCONNECTED\n\n def add_update_callback(self, method):\n \"\"\"Public method to add a callback subscriber.\"\"\"\n self._updateCallbacks.append(method)\n\n def add_error_callback(self, method):\n \"\"\"Public method to add a callback subscriber.\"\"\"\n self._errorCallbacks.append(method)\n
+Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
+<+>UTF-8
+===================================================================
+diff --git a/custom_components/intesisbox/intesisbox.py b/custom_components/intesisbox/intesisbox.py
+--- a/custom_components/intesisbox/intesisbox.py (revision 43d9f4f1385539ffd5725bef604fd718bfc1973d)
++++ b/custom_components/intesisbox/intesisbox.py (date 1709987882337)
+@@ -1,5 +1,7 @@
+ """Communication with an Intesisbox device."""
+
++from __future__ import annotations
++
+ import asyncio
+ from asyncio import BaseTransport, ensure_future
+ from collections.abc import Callable
+@@ -78,6 +80,15 @@
+ else:
+ _LOGGER.debug("Not connected, skipping keepalive")
+
++ async def poll_ambtemp(self):
++ """Retrieve Ambient Temperature to prevent integration timeouts."""
++ while self.is_connected:
++ _LOGGER.debug("Sending AMBTEMP")
++ self._write("GET,1:AMBTEMP")
++ await asyncio.sleep(10)
++ else:
++ _LOGGER.debug("Not connected, skipping Ambient Temp Request")
++
+ async def query_initial_state(self):
+ """Fetch configuration from the device upon connection."""
+ cmds = [
+@@ -92,9 +103,21 @@
+ self._write(cmd)
+ await asyncio.sleep(1)
+
++ async def delay(self, delay_time):
++ """Async Delay to slow down commands and await response from units."""
++ _LOGGER.debug(f"Delay Started for {delay_time} seconds...")
++ await asyncio.sleep(delay_time)
++ _LOGGER.debug("Delay Ended...")
++
+ def _write(self, cmd):
+ self._transport.write(f"{cmd}\r".encode("ascii"))
+ _LOGGER.debug(f"Data sent: {cmd!r}")
++
++ async def _writeasync(self, cmd):
++ """Async write to slow down commands and await response from units."""
++ self._transport.write(f"{cmd}\r".encode("ascii"))
++ _LOGGER.debug(f"Data sent: {cmd!r}")
++ await asyncio.sleep(1)
+
+ def data_received(self, data):
+ """Asyncio callback when data is received on the socket."""
+@@ -111,8 +134,9 @@
+ if cmd == "ID":
+ self._parse_id_received(args)
+ self._connectionStatus = API_AUTHENTICATED
+- _ = asyncio.ensure_future(self.keep_alive())
++ # _ = asyncio.ensure_future(self.keep_alive())
+ _ = asyncio.ensure_future(self.poll_status())
++ _ = asyncio.ensure_future(self.poll_ambtemp())
+ elif cmd == "CHN,1":
+ self._parse_change_received(args)
+ statusChanged = True
+@@ -244,17 +268,37 @@
+ def _set_value(self, uid: str, value: str | int) -> None:
+ """Change a setting on the thermostat."""
+ try:
+- self._write(f"SET,1:{uid},{value}")
++ # self._write(f"SET,1:{uid},{value}")
++ asyncio.run(self._writeasync(f"SET,1:{uid},{value}"))
+ except Exception as e:
+ _LOGGER.error("%s Exception. %s / %s", type(e), e.args, e)
+
+- def set_mode(self, mode: str) -> None:
+- """Change the thermostat mode (heat, cool, etc)."""
++ def set_mode(self, mode):
++ """Send mode and confirm change before turning on."""
++ """ Some units return responses out of order"""
++ _LOGGER.debug(f"Setting MODE to {mode}.")
++ if mode in MODES:
++ self._set_value(FUNCTION_MODE, mode)
+ if not self.is_on:
+- self.set_power_on()
+-
+- if mode in MODES:
+- self._set_value(FUNCTION_MODE, mode)
++ """ "Check to ensure in correct mode before turning on"""
++ retry = 30
++ while self.mode != mode and retry > 0:
++ _LOGGER.debug(
++ f"Waiting for MODE to return {mode}, currently {str(self.mode)}"
++ )
++ _LOGGER.debug(f"Retry attempt = {retry}")
++ # asyncio.run(self.delay(1)) # SHANE
++ # self._write("GET,1:MODE")
++ asyncio.run(self._writeasync("GET,1:MODE"))
++ retry -= 1
++ else:
++ if retry != 0:
++ _LOGGER.debug(
++ f"MODE confirmed now {str(self.mode)}, proceed to Power On"
++ )
++ self.set_power_on()
++ else:
++ _LOGGER.error("Cannot set Intesisbox mode giving up...")
+
+ def set_mode_dry(self):
+ """Public method to set device to dry asynchronously."""
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..e4c75bd
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ "customColor": "",
+ "associatedIndex": 3
+}
+
+
+
+
+
+ {
+ "keyToString": {
+ "RunOnceActivity.OpenProjectViewOnStart": "true",
+ "RunOnceActivity.ShowReadmeOnStart": "true",
+ "git-widget-placeholder": "main",
+ "settings.editor.selected.configurable": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable"
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+ 1709979117827
+
+
+ 1709979117827
+
+
+
+ 1709988940994
+
+
+
+ 1709988940994
+
+
+
+ 1709989404146
+
+
+
+ 1709989404146
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ file://$PROJECT_DIR$/custom_components/intesisbox/climate.py
+ 6
+
+
+
+
+
+
\ No newline at end of file
diff --git a/custom_components/.DS_Store b/custom_components/.DS_Store
new file mode 100644
index 0000000000000000000000000000000000000000..c6ddd7e3783bcade5facb0b30994e6052bf6e4f1
GIT binary patch
literal 6148
zcmeHK%}N6?5T3NvZY@F&3OxqA7Obs`;$^Az1zgdCO5LSJ7uSt+w^k^HJ?jhkBtDNb
zNw!d04_-v<3{1Xcem3OGl1TtSbVmI>fEoZesDy^om=lgjlI-Q
zM@h#YUeoA8sVK<%esCEM#=Y9ssftrSj0Z!V5cUTca&r~NeKqc=QQS{-u4f!h#i{ga
z^~vO@)oRFtR(slzljByiA@|#d(`m)o-q}4l?>$COv3fIv3jFu9Y+B6W9Xm_c_TmlW
zNX7T)&vWw}MrME+UQ
z^p+r$7F~;(LG+*qlZt3kg?(ZOla6+2<6MiGL6Z(b&y3%(GYk7d5qfsCOC1ivHOMV9
zzzobYP&C6D)&H~a-~aPTJYoizfq%t-D0RJV2bW}P>%!uw)=JbnR1%8I49-%pqf0Ty
eQYo&ZYC*fC4x(!@Gl&)xz6fXIF~=R@#GmeZ-@zBK@F{HVtbGnE
z{bqKH<2X!ggqRt!`|Zumx3l}f?y^K=I?GX;s7XW>6vlEDRe|w)E^AhDo;uJ-A5*%a
zGkjV!UyD|UDxeDNngYCbYqDmi6j65lKJ>zPI0)kbB6xdvL1}%7p+!`sA%$Sf=mM+$
zQr3KqV~%p1eQ;vr8G|=L)-igB>8_CbnBz9Ui6m2kRzd?X?+clqIpzWNR+t@Ob}{lL
z^7H6pS`_tEn&f(F$ZT1sF=m{A*`jM8=9E+G6wIfLT6M%OkSjv8%{gTjB?Y{!${Ts%
zq}?CJGq=y0cjVg`JBx$BeUp`s6Z3rh_R8b?A%66$`1w_KEc|2H-1lvN5ruKY%HqsW
z+oA$a|4MC(W6;#9fGV(?3h@5mp)mRmQ;V{7ppq*9une~t+Oq!xJ$3+nhp9z)V8&8`
zmTKG;!&o}}o{RGxrWP%ojATYXF0*kr6eHQ;_avN*Z&7PiKowY3V9!1_`TXy0zW=Y1
z^hp&^1^$%+rX2Kx4yNSJ*0ss;S?i&kqp)#YYEdYt+;J=oK8g=f#PH7J2GDnyT7(Ct
Oe*{DZtyF None:
"""Change a setting on the thermostat."""
try:
- # self._write(f"SET,1:{uid},{value}")
asyncio.run(self._writeasync(f"SET,1:{uid},{value}"))
except Exception as e:
_LOGGER.error("%s Exception. %s / %s", type(e), e.args, e)
def set_mode(self, mode):
"""Send mode and confirm change before turning on."""
- """ Some units return responses out of order"""
+ """Some units return responses out of order"""
_LOGGER.debug(f"Setting MODE to {mode}.")
if mode in MODES:
self._set_value(FUNCTION_MODE, mode)
if not self.is_on:
- """ "Check to ensure in correct mode before turning on"""
+ """Check to ensure in correct mode before turning on"""
retry = 30
while self.mode != mode and retry > 0:
_LOGGER.debug(
f"Waiting for MODE to return {mode}, currently {str(self.mode)}"
)
_LOGGER.debug(f"Retry attempt = {retry}")
- # asyncio.run(self.delay(1)) # SHANE
- # self._write("GET,1:MODE")
asyncio.run(self._writeasync("GET,1:MODE"))
retry -= 1
else:
From 130cfcbc5c13a9b58569e5090d927b299e9f1698 Mon Sep 17 00:00:00 2001
From: CV8R <39397297+CV8R@users.noreply.github.com>
Date: Wed, 13 Mar 2024 15:02:30 +1100
Subject: [PATCH 4/8] Remove commented code.
---
.idea/hass-intesisbox.iml | 7 ++
.../inspectionProfiles/profiles_settings.xml | 6 +
.idea/misc.xml | 4 +
.idea/shelf/Changes.xml | 4 +
.idea/shelf/Changes/shelved.patch | 112 ++++++++++++++++++
.idea/vcs.xml | 6 +
.idea/workspace.xml | 102 ++++++++++++++++
custom_components/.DS_Store | Bin 0 -> 6148 bytes
custom_components/intesisbox/.DS_Store | Bin 0 -> 6148 bytes
9 files changed, 241 insertions(+)
create mode 100644 .idea/hass-intesisbox.iml
create mode 100644 .idea/inspectionProfiles/profiles_settings.xml
create mode 100644 .idea/misc.xml
create mode 100644 .idea/shelf/Changes.xml
create mode 100644 .idea/shelf/Changes/shelved.patch
create mode 100644 .idea/vcs.xml
create mode 100644 .idea/workspace.xml
create mode 100644 custom_components/.DS_Store
create mode 100644 custom_components/intesisbox/.DS_Store
diff --git a/.idea/hass-intesisbox.iml b/.idea/hass-intesisbox.iml
new file mode 100644
index 0000000..ec63674
--- /dev/null
+++ b/.idea/hass-intesisbox.iml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..974cdaf
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/shelf/Changes.xml b/.idea/shelf/Changes.xml
new file mode 100644
index 0000000..a345c1c
--- /dev/null
+++ b/.idea/shelf/Changes.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/shelf/Changes/shelved.patch b/.idea/shelf/Changes/shelved.patch
new file mode 100644
index 0000000..f81e5e5
--- /dev/null
+++ b/.idea/shelf/Changes/shelved.patch
@@ -0,0 +1,112 @@
+Index: custom_components/intesisbox/intesisbox.py
+IDEA additional info:
+Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
+<+>\"\"\"Communication with an Intesisbox device.\"\"\"\n\nimport asyncio\nfrom asyncio import BaseTransport, ensure_future\nfrom collections.abc import Callable\nimport logging\n\n_LOGGER = logging.getLogger(__name__)\n\nAPI_DISCONNECTED = \"Disconnected\"\nAPI_CONNECTING = \"Connecting\"\nAPI_AUTHENTICATED = \"Connected\"\n\nPOWER_ON = \"ON\"\nPOWER_OFF = \"OFF\"\nPOWER_STATES = [POWER_ON, POWER_OFF]\n\nMODE_AUTO = \"AUTO\"\nMODE_DRY = \"DRY\"\nMODE_FAN = \"FAN\"\nMODE_COOL = \"COOL\"\nMODE_HEAT = \"HEAT\"\nMODES = [MODE_AUTO, MODE_DRY, MODE_FAN, MODE_COOL, MODE_HEAT]\n\nFUNCTION_ONOFF = \"ONOFF\"\nFUNCTION_MODE = \"MODE\"\nFUNCTION_SETPOINT = \"SETPTEMP\"\nFUNCTION_FANSP = \"FANSP\"\nFUNCTION_VANEUD = \"VANEUD\"\nFUNCTION_VANELR = \"VANELR\"\nFUNCTION_AMBTEMP = \"AMBTEMP\"\nFUNCTION_ERRSTATUS = \"ERRSTATUS\"\nFUNCTION_ERRCODE = \"ERRCODE\"\n\nNULL_VALUES = [\"-32768\", \"32768\"]\n\n\nclass IntesisBox(asyncio.Protocol):\n \"\"\"Handles communication with an intesisbox device via WMP.\"\"\"\n\n def __init__(self, ip: str, port: int = 3310, loop=None):\n \"\"\"Set up base state.\"\"\"\n self._ip = ip\n self._port = port\n self._mac = None\n self._device: dict[str, str] = {}\n self._connectionStatus = API_DISCONNECTED\n self._transport: BaseTransport | None = None\n self._updateCallbacks: list[Callable[[], None]] = []\n self._errorCallbacks: list[Callable[[str], None]] = []\n self._errorMessage: str | None = None\n self._controllerType = None\n self._model: str | None = None\n self._firmversion: str | None = None\n self._rssi: int | None = None\n self._eventLoop = loop\n\n # Limits\n self._operation_list: list[str] = []\n self._fan_speed_list: list[str] = []\n self._vertical_vane_list: list[str] = []\n self._horizontal_vane_list: list[str] = []\n self._setpoint_minimum: int | None = None\n self._setpoint_maximum: int | None = None\n\n def connection_made(self, transport: BaseTransport):\n \"\"\"Asyncio callback for a successful connection.\"\"\"\n _LOGGER.debug(\"Connected to IntesisBox\")\n self._transport = transport\n _ = asyncio.ensure_future(self.query_initial_state())\n\n async def keep_alive(self):\n \"\"\"Send a keepalive command to reset it's watchdog timer.\"\"\"\n while self.is_connected:\n _LOGGER.debug(\"Sending keepalive\")\n self._write(\"PING\")\n await asyncio.sleep(45)\n else:\n _LOGGER.debug(\"Not connected, skipping keepalive\")\n\n async def query_initial_state(self):\n \"\"\"Fetch configuration from the device upon connection.\"\"\"\n cmds = [\n \"ID\",\n \"LIMITS:SETPTEMP\",\n \"LIMITS:FANSP\",\n \"LIMITS:MODE\",\n \"LIMITS:VANEUD\",\n \"LIMITS:VANELR\",\n ]\n for cmd in cmds:\n self._write(cmd)\n await asyncio.sleep(1)\n\n def _write(self, cmd):\n self._transport.write(f\"{cmd}\\r\".encode(\"ascii\"))\n _LOGGER.debug(f\"Data sent: {cmd!r}\")\n\n def data_received(self, data):\n \"\"\"Asyncio callback when data is received on the socket.\"\"\"\n linesReceived = data.decode(\"ascii\").splitlines()\n statusChanged = False\n\n for line in linesReceived:\n _LOGGER.debug(f\"Data received: {line!r}\")\n cmdList = line.split(\":\", 1)\n cmd = cmdList[0]\n args = None\n if len(cmdList) > 1:\n args = cmdList[1]\n if cmd == \"ID\":\n self._parse_id_received(args)\n self._connectionStatus = API_AUTHENTICATED\n _ = asyncio.ensure_future(self.keep_alive())\n _ = asyncio.ensure_future(self.poll_status())\n elif cmd == \"CHN,1\":\n self._parse_change_received(args)\n statusChanged = True\n elif cmd == \"LIMITS\":\n self._parse_limits_received(args)\n statusChanged = True\n\n if statusChanged:\n self._send_update_callback()\n\n def _parse_id_received(self, args):\n # ID:Model,MAC,IP,Protocol,Version,RSSI\n info = args.split(\",\")\n if len(info) >= 6:\n self._model = info[0]\n self._mac = info[1]\n self._firmversion = info[4]\n self._rssi = info[5]\n\n _LOGGER.debug(\n \"Updated info:\",\n f\"model:{self._model}\",\n f\"mac:{self._mac}\",\n f\"version:{self._firmversion}\",\n f\"rssi:{self._rssi}\",\n )\n\n def _parse_change_received(self, args):\n function = args.split(\",\")[0]\n value = args.split(\",\")[1]\n if value in NULL_VALUES:\n value = None\n self._device[function] = value\n\n _LOGGER.debug(f\"Updated state: {self._device!r}\")\n\n def _parse_limits_received(self, args):\n split_args = args.split(\",\", 1)\n\n if len(split_args) == 2:\n function = split_args[0]\n values = split_args[1][1:-1].split(\",\")\n\n if function == FUNCTION_SETPOINT and len(values) == 2:\n self._setpoint_minimum = int(values[0]) / 10\n self._setpoint_maximum = int(values[1]) / 10\n elif function == FUNCTION_FANSP:\n self._fan_speed_list = values\n elif function == FUNCTION_MODE:\n self._operation_list = values\n elif function == FUNCTION_VANEUD:\n self._vertical_vane_list = values\n elif function == FUNCTION_VANELR:\n self._horizontal_vane_list = values\n\n _LOGGER.debug(\n \"Updated limits: \",\n f\"{self._setpoint_minimum=}\",\n f\"{self._setpoint_maximum=}\",\n f\"{self._fan_speed_list=}\",\n f\"{self._operation_list=}\",\n f\"{self._vertical_vane_list=}\",\n f\"{self._horizontal_vane_list=}\",\n )\n return\n\n def connection_lost(self, exc):\n \"\"\"Asyncio callback for a lost TCP connection.\"\"\"\n self._connectionStatus = API_DISCONNECTED\n _LOGGER.info(\"The server closed the connection\")\n self._send_update_callback()\n\n def connect(self):\n \"\"\"Public method for connecting to IntesisHome API.\"\"\"\n if self._connectionStatus == API_DISCONNECTED:\n self._connectionStatus = API_CONNECTING\n try:\n # Must poll to get the authentication token\n if self._ip and self._port:\n # Create asyncio socket\n coro = self._eventLoop.create_connection(\n lambda: self, self._ip, self._port\n )\n _LOGGER.debug(\n \"Opening connection to IntesisBox %s:%s\", self._ip, self._port\n )\n _ = ensure_future(coro, loop=self._eventLoop)\n else:\n _LOGGER.debug(\"Missing IP address or port.\")\n self._connectionStatus = API_DISCONNECTED\n\n except Exception as e:\n _LOGGER.error(\"%s Exception. %s / %s\", type(e), repr(e.args), e)\n self._connectionStatus = API_DISCONNECTED\n else:\n _LOGGER.debug(\"connect() called but already connecting\")\n\n def stop(self):\n \"\"\"Public method for shutting down connectivity with the envisalink.\"\"\"\n self._connectionStatus = API_DISCONNECTED\n self._transport.close()\n\n async def poll_status(self, sendcallback=False):\n \"\"\"Periodically poll for updates since the controllers don't always update reliably.\"\"\"\n while self.is_connected:\n _LOGGER.debug(\"Polling for update\")\n self._write(\"GET,1:*\")\n await asyncio.sleep(60 * 5) # 5 minutes\n else:\n _LOGGER.debug(\"Not connected, skipping poll_status()\")\n\n def set_temperature(self, setpoint):\n \"\"\"Public method for setting the temperature.\"\"\"\n set_temp = int(setpoint * 10)\n self._set_value(FUNCTION_SETPOINT, set_temp)\n\n def set_fan_speed(self, fan_speed):\n \"\"\"Public method to set the fan speed.\"\"\"\n self._set_value(FUNCTION_FANSP, fan_speed)\n\n def set_vertical_vane(self, vane: str):\n \"\"\"Public method to set the vertical vane.\"\"\"\n self._set_value(FUNCTION_VANEUD, vane)\n\n def set_horizontal_vane(self, vane: str):\n \"\"\"Public method to set the horizontal vane.\"\"\"\n self._set_value(FUNCTION_VANELR, vane)\n\n def _set_value(self, uid: str, value: str | int) -> None:\n \"\"\"Change a setting on the thermostat.\"\"\"\n try:\n self._write(f\"SET,1:{uid},{value}\")\n except Exception as e:\n _LOGGER.error(\"%s Exception. %s / %s\", type(e), e.args, e)\n\n def set_mode(self, mode: str) -> None:\n \"\"\"Change the thermostat mode (heat, cool, etc).\"\"\"\n if not self.is_on:\n self.set_power_on()\n\n if mode in MODES:\n self._set_value(FUNCTION_MODE, mode)\n\n def set_mode_dry(self):\n \"\"\"Public method to set device to dry asynchronously.\"\"\"\n self._set_value(FUNCTION_MODE, MODE_DRY)\n\n def set_power_off(self):\n \"\"\"Public method to turn off the device asynchronously.\"\"\"\n self._set_value(FUNCTION_ONOFF, POWER_OFF)\n\n def set_power_on(self):\n \"\"\"Public method to turn on the device asynchronously.\"\"\"\n self._set_value(FUNCTION_ONOFF, POWER_ON)\n\n @property\n def operation_list(self) -> list[str]:\n \"\"\"Supported modes.\"\"\"\n return self._operation_list\n\n @property\n def vane_horizontal_list(self) -> list[str]:\n \"\"\"Supported Horizontal Vane settings.\"\"\"\n return self._horizontal_vane_list\n\n @property\n def vane_vertical_list(self) -> list[str]:\n \"\"\"Supported Vertical Vane settings.\"\"\"\n return self._vertical_vane_list\n\n @property\n def mode(self) -> str | None:\n \"\"\"Current mode.\"\"\"\n return self._device.get(FUNCTION_MODE)\n\n @property\n def fan_speed(self) -> str | None:\n \"\"\"Current fan speed.\"\"\"\n return self._device.get(FUNCTION_FANSP)\n\n @property\n def fan_speed_list(self) -> list[str]:\n \"\"\"Supported fan speeds.\"\"\"\n return self._fan_speed_list\n\n @property\n def device_mac_address(self) -> str | None:\n \"\"\"MAC address of the IntesisBox.\"\"\"\n return self._mac\n\n @property\n def device_model(self) -> str | None:\n \"\"\"Model of the IntesisBox.\"\"\"\n return self._model\n\n @property\n def firmware_version(self) -> str | None:\n \"\"\"Firmware versioon of the IntesisBox.\"\"\"\n return self._firmversion\n\n @property\n def is_on(self) -> bool:\n \"\"\"Return true if the controlled device is turned on.\"\"\"\n return self._device.get(FUNCTION_ONOFF) == POWER_ON\n\n @property\n def has_swing_control(self) -> bool:\n \"\"\"Return true if the device supports swing modes.\"\"\"\n return len(self._horizontal_vane_list) > 1 or len(self._vertical_vane_list) > 1\n\n @property\n def setpoint(self) -> float | None:\n \"\"\"Public method returns the target temperature.\"\"\"\n setpoint = self._device.get(FUNCTION_SETPOINT)\n return (int(setpoint) / 10) if setpoint else None\n\n @property\n def ambient_temperature(self) -> float | None:\n \"\"\"Public method returns the current temperature.\"\"\"\n temperature = self._device.get(FUNCTION_AMBTEMP)\n return (int(temperature) / 10) if temperature else None\n\n @property\n def max_setpoint(self) -> float | None:\n \"\"\"Maximum allowed target temperature.\"\"\"\n return self._setpoint_maximum\n\n @property\n def min_setpoint(self) -> float | None:\n \"\"\"Minimum allowed target temperature.\"\"\"\n return self._setpoint_minimum\n\n @property\n def rssi(self) -> int | None:\n \"\"\"Wireless signal strength of the IntesisBox.\"\"\"\n return self._rssi\n\n @property\n def vertical_swing(self) -> str | None:\n \"\"\"Current vertical vane setting.\"\"\"\n return self._device.get(FUNCTION_VANEUD)\n\n @property\n def horizontal_swing(self) -> str | None:\n \"\"\"Current horizontal vane setting.\"\"\"\n return self._device.get(FUNCTION_VANELR)\n\n def _send_update_callback(self):\n \"\"\"Notify all listeners that state of the thermostat has changed.\"\"\"\n if not self._updateCallbacks:\n _LOGGER.debug(\"Update callback has not been set by client.\")\n\n for callback in self._updateCallbacks:\n callback()\n\n def _send_error_callback(self, message: str):\n \"\"\"Notify all listeners that an error has occurred.\"\"\"\n self._errorMessage = message\n\n if self._errorCallbacks == []:\n _LOGGER.debug(\"Error callback has not been set by client.\")\n\n for callback in self._errorCallbacks:\n callback(message)\n\n @property\n def is_connected(self) -> bool:\n \"\"\"Returns true if the TCP connection is established.\"\"\"\n return self._connectionStatus == API_AUTHENTICATED\n\n @property\n def error_message(self) -> str | None:\n \"\"\"Returns the last error message, or None if there were no errors.\"\"\"\n return self._errorMessage\n\n @property\n def is_disconnected(self) -> bool:\n \"\"\"Returns true when the TCP connection is disconnected and idle.\"\"\"\n return self._connectionStatus == API_DISCONNECTED\n\n def add_update_callback(self, method):\n \"\"\"Public method to add a callback subscriber.\"\"\"\n self._updateCallbacks.append(method)\n\n def add_error_callback(self, method):\n \"\"\"Public method to add a callback subscriber.\"\"\"\n self._errorCallbacks.append(method)\n
+Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
+<+>UTF-8
+===================================================================
+diff --git a/custom_components/intesisbox/intesisbox.py b/custom_components/intesisbox/intesisbox.py
+--- a/custom_components/intesisbox/intesisbox.py (revision 43d9f4f1385539ffd5725bef604fd718bfc1973d)
++++ b/custom_components/intesisbox/intesisbox.py (date 1709987882337)
+@@ -1,5 +1,7 @@
+ """Communication with an Intesisbox device."""
+
++from __future__ import annotations
++
+ import asyncio
+ from asyncio import BaseTransport, ensure_future
+ from collections.abc import Callable
+@@ -78,6 +80,15 @@
+ else:
+ _LOGGER.debug("Not connected, skipping keepalive")
+
++ async def poll_ambtemp(self):
++ """Retrieve Ambient Temperature to prevent integration timeouts."""
++ while self.is_connected:
++ _LOGGER.debug("Sending AMBTEMP")
++ self._write("GET,1:AMBTEMP")
++ await asyncio.sleep(10)
++ else:
++ _LOGGER.debug("Not connected, skipping Ambient Temp Request")
++
+ async def query_initial_state(self):
+ """Fetch configuration from the device upon connection."""
+ cmds = [
+@@ -92,9 +103,21 @@
+ self._write(cmd)
+ await asyncio.sleep(1)
+
++ async def delay(self, delay_time):
++ """Async Delay to slow down commands and await response from units."""
++ _LOGGER.debug(f"Delay Started for {delay_time} seconds...")
++ await asyncio.sleep(delay_time)
++ _LOGGER.debug("Delay Ended...")
++
+ def _write(self, cmd):
+ self._transport.write(f"{cmd}\r".encode("ascii"))
+ _LOGGER.debug(f"Data sent: {cmd!r}")
++
++ async def _writeasync(self, cmd):
++ """Async write to slow down commands and await response from units."""
++ self._transport.write(f"{cmd}\r".encode("ascii"))
++ _LOGGER.debug(f"Data sent: {cmd!r}")
++ await asyncio.sleep(1)
+
+ def data_received(self, data):
+ """Asyncio callback when data is received on the socket."""
+@@ -111,8 +134,9 @@
+ if cmd == "ID":
+ self._parse_id_received(args)
+ self._connectionStatus = API_AUTHENTICATED
+- _ = asyncio.ensure_future(self.keep_alive())
++ # _ = asyncio.ensure_future(self.keep_alive())
+ _ = asyncio.ensure_future(self.poll_status())
++ _ = asyncio.ensure_future(self.poll_ambtemp())
+ elif cmd == "CHN,1":
+ self._parse_change_received(args)
+ statusChanged = True
+@@ -244,17 +268,37 @@
+ def _set_value(self, uid: str, value: str | int) -> None:
+ """Change a setting on the thermostat."""
+ try:
+- self._write(f"SET,1:{uid},{value}")
++ # self._write(f"SET,1:{uid},{value}")
++ asyncio.run(self._writeasync(f"SET,1:{uid},{value}"))
+ except Exception as e:
+ _LOGGER.error("%s Exception. %s / %s", type(e), e.args, e)
+
+- def set_mode(self, mode: str) -> None:
+- """Change the thermostat mode (heat, cool, etc)."""
++ def set_mode(self, mode):
++ """Send mode and confirm change before turning on."""
++ """ Some units return responses out of order"""
++ _LOGGER.debug(f"Setting MODE to {mode}.")
++ if mode in MODES:
++ self._set_value(FUNCTION_MODE, mode)
+ if not self.is_on:
+- self.set_power_on()
+-
+- if mode in MODES:
+- self._set_value(FUNCTION_MODE, mode)
++ """ "Check to ensure in correct mode before turning on"""
++ retry = 30
++ while self.mode != mode and retry > 0:
++ _LOGGER.debug(
++ f"Waiting for MODE to return {mode}, currently {str(self.mode)}"
++ )
++ _LOGGER.debug(f"Retry attempt = {retry}")
++ # asyncio.run(self.delay(1)) # SHANE
++ # self._write("GET,1:MODE")
++ asyncio.run(self._writeasync("GET,1:MODE"))
++ retry -= 1
++ else:
++ if retry != 0:
++ _LOGGER.debug(
++ f"MODE confirmed now {str(self.mode)}, proceed to Power On"
++ )
++ self.set_power_on()
++ else:
++ _LOGGER.error("Cannot set Intesisbox mode giving up...")
+
+ def set_mode_dry(self):
+ """Public method to set device to dry asynchronously."""
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..e4c75bd
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ "customColor": "",
+ "associatedIndex": 3
+}
+
+
+
+
+
+ {
+ "keyToString": {
+ "RunOnceActivity.OpenProjectViewOnStart": "true",
+ "RunOnceActivity.ShowReadmeOnStart": "true",
+ "git-widget-placeholder": "main",
+ "settings.editor.selected.configurable": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable"
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+ 1709979117827
+
+
+ 1709979117827
+
+
+
+ 1709988940994
+
+
+
+ 1709988940994
+
+
+
+ 1709989404146
+
+
+
+ 1709989404146
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ file://$PROJECT_DIR$/custom_components/intesisbox/climate.py
+ 6
+
+
+
+
+
+
\ No newline at end of file
diff --git a/custom_components/.DS_Store b/custom_components/.DS_Store
new file mode 100644
index 0000000000000000000000000000000000000000..c6ddd7e3783bcade5facb0b30994e6052bf6e4f1
GIT binary patch
literal 6148
zcmeHK%}N6?5T3NvZY@F&3OxqA7Obs`;$^Az1zgdCO5LSJ7uSt+w^k^HJ?jhkBtDNb
zNw!d04_-v<3{1Xcem3OGl1TtSbVmI>fEoZesDy^om=lgjlI-Q
zM@h#YUeoA8sVK<%esCEM#=Y9ssftrSj0Z!V5cUTca&r~NeKqc=QQS{-u4f!h#i{ga
z^~vO@)oRFtR(slzljByiA@|#d(`m)o-q}4l?>$COv3fIv3jFu9Y+B6W9Xm_c_TmlW
zNX7T)&vWw}MrME+UQ
z^p+r$7F~;(LG+*qlZt3kg?(ZOla6+2<6MiGL6Z(b&y3%(GYk7d5qfsCOC1ivHOMV9
zzzobYP&C6D)&H~a-~aPTJYoizfq%t-D0RJV2bW}P>%!uw)=JbnR1%8I49-%pqf0Ty
eQYo&ZYC*fC4x(!@Gl&)xz6fXIF~=R@#GmeZ-@zBK@F{HVtbGnE
z{bqKH<2X!ggqRt!`|Zumx3l}f?y^K=I?GX;s7XW>6vlEDRe|w)E^AhDo;uJ-A5*%a
zGkjV!UyD|UDxeDNngYCbYqDmi6j65lKJ>zPI0)kbB6xdvL1}%7p+!`sA%$Sf=mM+$
zQr3KqV~%p1eQ;vr8G|=L)-igB>8_CbnBz9Ui6m2kRzd?X?+clqIpzWNR+t@Ob}{lL
z^7H6pS`_tEn&f(F$ZT1sF=m{A*`jM8=9E+G6wIfLT6M%OkSjv8%{gTjB?Y{!${Ts%
zq}?CJGq=y0cjVg`JBx$BeUp`s6Z3rh_R8b?A%66$`1w_KEc|2H-1lvN5ruKY%HqsW
z+oA$a|4MC(W6;#9fGV(?3h@5mp)mRmQ;V{7ppq*9une~t+Oq!xJ$3+nhp9z)V8&8`
zmTKG;!&o}}o{RGxrWP%ojATYXF0*kr6eHQ;_avN*Z&7PiKowY3V9!1_`TXy0zW=Y1
z^hp&^1^$%+rX2Kx4yNSJ*0ss;S?i&kqp)#YYEdYt+;J=oK8g=f#PH7J2GDnyT7(Ct
Oe*{DZtyF
Date: Tue, 2 Apr 2024 21:47:23 +1100
Subject: [PATCH 5/8] Update .gitignore
---
.gitignore | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.gitignore b/.gitignore
index 894a44c..8e7c643 100644
--- a/.gitignore
+++ b/.gitignore
@@ -102,3 +102,6 @@ venv.bak/
# mypy
.mypy_cache/
+
+#.idea
+.idea
From 89c403bf197e22e9ae41e44ddf7898bb5ec78eb3 Mon Sep 17 00:00:00 2001
From: CV8R <39397297+CV8R@users.noreply.github.com>
Date: Tue, 2 Apr 2024 21:57:00 +1100
Subject: [PATCH 6/8] Cleanup
---
.gitignore | 2 +-
.idea/hass-intesisbox.iml | 7 --
.../inspectionProfiles/profiles_settings.xml | 6 -
.idea/misc.xml | 4 -
.idea/shelf/Changes.xml | 4 -
.idea/shelf/Changes/shelved.patch | 112 ------------------
.idea/vcs.xml | 6 -
.idea/workspace.xml | 102 ----------------
8 files changed, 1 insertion(+), 242 deletions(-)
delete mode 100644 .idea/hass-intesisbox.iml
delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml
delete mode 100644 .idea/misc.xml
delete mode 100644 .idea/shelf/Changes.xml
delete mode 100644 .idea/shelf/Changes/shelved.patch
delete mode 100644 .idea/vcs.xml
delete mode 100644 .idea/workspace.xml
diff --git a/.gitignore b/.gitignore
index 8e7c643..e947772 100644
--- a/.gitignore
+++ b/.gitignore
@@ -104,4 +104,4 @@ venv.bak/
.mypy_cache/
#.idea
-.idea
+/.idea
diff --git a/.idea/hass-intesisbox.iml b/.idea/hass-intesisbox.iml
deleted file mode 100644
index ec63674..0000000
--- a/.idea/hass-intesisbox.iml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
deleted file mode 100644
index 105ce2d..0000000
--- a/.idea/inspectionProfiles/profiles_settings.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 974cdaf..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/.idea/shelf/Changes.xml b/.idea/shelf/Changes.xml
deleted file mode 100644
index a345c1c..0000000
--- a/.idea/shelf/Changes.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/.idea/shelf/Changes/shelved.patch b/.idea/shelf/Changes/shelved.patch
deleted file mode 100644
index f81e5e5..0000000
--- a/.idea/shelf/Changes/shelved.patch
+++ /dev/null
@@ -1,112 +0,0 @@
-Index: custom_components/intesisbox/intesisbox.py
-IDEA additional info:
-Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
-<+>\"\"\"Communication with an Intesisbox device.\"\"\"\n\nimport asyncio\nfrom asyncio import BaseTransport, ensure_future\nfrom collections.abc import Callable\nimport logging\n\n_LOGGER = logging.getLogger(__name__)\n\nAPI_DISCONNECTED = \"Disconnected\"\nAPI_CONNECTING = \"Connecting\"\nAPI_AUTHENTICATED = \"Connected\"\n\nPOWER_ON = \"ON\"\nPOWER_OFF = \"OFF\"\nPOWER_STATES = [POWER_ON, POWER_OFF]\n\nMODE_AUTO = \"AUTO\"\nMODE_DRY = \"DRY\"\nMODE_FAN = \"FAN\"\nMODE_COOL = \"COOL\"\nMODE_HEAT = \"HEAT\"\nMODES = [MODE_AUTO, MODE_DRY, MODE_FAN, MODE_COOL, MODE_HEAT]\n\nFUNCTION_ONOFF = \"ONOFF\"\nFUNCTION_MODE = \"MODE\"\nFUNCTION_SETPOINT = \"SETPTEMP\"\nFUNCTION_FANSP = \"FANSP\"\nFUNCTION_VANEUD = \"VANEUD\"\nFUNCTION_VANELR = \"VANELR\"\nFUNCTION_AMBTEMP = \"AMBTEMP\"\nFUNCTION_ERRSTATUS = \"ERRSTATUS\"\nFUNCTION_ERRCODE = \"ERRCODE\"\n\nNULL_VALUES = [\"-32768\", \"32768\"]\n\n\nclass IntesisBox(asyncio.Protocol):\n \"\"\"Handles communication with an intesisbox device via WMP.\"\"\"\n\n def __init__(self, ip: str, port: int = 3310, loop=None):\n \"\"\"Set up base state.\"\"\"\n self._ip = ip\n self._port = port\n self._mac = None\n self._device: dict[str, str] = {}\n self._connectionStatus = API_DISCONNECTED\n self._transport: BaseTransport | None = None\n self._updateCallbacks: list[Callable[[], None]] = []\n self._errorCallbacks: list[Callable[[str], None]] = []\n self._errorMessage: str | None = None\n self._controllerType = None\n self._model: str | None = None\n self._firmversion: str | None = None\n self._rssi: int | None = None\n self._eventLoop = loop\n\n # Limits\n self._operation_list: list[str] = []\n self._fan_speed_list: list[str] = []\n self._vertical_vane_list: list[str] = []\n self._horizontal_vane_list: list[str] = []\n self._setpoint_minimum: int | None = None\n self._setpoint_maximum: int | None = None\n\n def connection_made(self, transport: BaseTransport):\n \"\"\"Asyncio callback for a successful connection.\"\"\"\n _LOGGER.debug(\"Connected to IntesisBox\")\n self._transport = transport\n _ = asyncio.ensure_future(self.query_initial_state())\n\n async def keep_alive(self):\n \"\"\"Send a keepalive command to reset it's watchdog timer.\"\"\"\n while self.is_connected:\n _LOGGER.debug(\"Sending keepalive\")\n self._write(\"PING\")\n await asyncio.sleep(45)\n else:\n _LOGGER.debug(\"Not connected, skipping keepalive\")\n\n async def query_initial_state(self):\n \"\"\"Fetch configuration from the device upon connection.\"\"\"\n cmds = [\n \"ID\",\n \"LIMITS:SETPTEMP\",\n \"LIMITS:FANSP\",\n \"LIMITS:MODE\",\n \"LIMITS:VANEUD\",\n \"LIMITS:VANELR\",\n ]\n for cmd in cmds:\n self._write(cmd)\n await asyncio.sleep(1)\n\n def _write(self, cmd):\n self._transport.write(f\"{cmd}\\r\".encode(\"ascii\"))\n _LOGGER.debug(f\"Data sent: {cmd!r}\")\n\n def data_received(self, data):\n \"\"\"Asyncio callback when data is received on the socket.\"\"\"\n linesReceived = data.decode(\"ascii\").splitlines()\n statusChanged = False\n\n for line in linesReceived:\n _LOGGER.debug(f\"Data received: {line!r}\")\n cmdList = line.split(\":\", 1)\n cmd = cmdList[0]\n args = None\n if len(cmdList) > 1:\n args = cmdList[1]\n if cmd == \"ID\":\n self._parse_id_received(args)\n self._connectionStatus = API_AUTHENTICATED\n _ = asyncio.ensure_future(self.keep_alive())\n _ = asyncio.ensure_future(self.poll_status())\n elif cmd == \"CHN,1\":\n self._parse_change_received(args)\n statusChanged = True\n elif cmd == \"LIMITS\":\n self._parse_limits_received(args)\n statusChanged = True\n\n if statusChanged:\n self._send_update_callback()\n\n def _parse_id_received(self, args):\n # ID:Model,MAC,IP,Protocol,Version,RSSI\n info = args.split(\",\")\n if len(info) >= 6:\n self._model = info[0]\n self._mac = info[1]\n self._firmversion = info[4]\n self._rssi = info[5]\n\n _LOGGER.debug(\n \"Updated info:\",\n f\"model:{self._model}\",\n f\"mac:{self._mac}\",\n f\"version:{self._firmversion}\",\n f\"rssi:{self._rssi}\",\n )\n\n def _parse_change_received(self, args):\n function = args.split(\",\")[0]\n value = args.split(\",\")[1]\n if value in NULL_VALUES:\n value = None\n self._device[function] = value\n\n _LOGGER.debug(f\"Updated state: {self._device!r}\")\n\n def _parse_limits_received(self, args):\n split_args = args.split(\",\", 1)\n\n if len(split_args) == 2:\n function = split_args[0]\n values = split_args[1][1:-1].split(\",\")\n\n if function == FUNCTION_SETPOINT and len(values) == 2:\n self._setpoint_minimum = int(values[0]) / 10\n self._setpoint_maximum = int(values[1]) / 10\n elif function == FUNCTION_FANSP:\n self._fan_speed_list = values\n elif function == FUNCTION_MODE:\n self._operation_list = values\n elif function == FUNCTION_VANEUD:\n self._vertical_vane_list = values\n elif function == FUNCTION_VANELR:\n self._horizontal_vane_list = values\n\n _LOGGER.debug(\n \"Updated limits: \",\n f\"{self._setpoint_minimum=}\",\n f\"{self._setpoint_maximum=}\",\n f\"{self._fan_speed_list=}\",\n f\"{self._operation_list=}\",\n f\"{self._vertical_vane_list=}\",\n f\"{self._horizontal_vane_list=}\",\n )\n return\n\n def connection_lost(self, exc):\n \"\"\"Asyncio callback for a lost TCP connection.\"\"\"\n self._connectionStatus = API_DISCONNECTED\n _LOGGER.info(\"The server closed the connection\")\n self._send_update_callback()\n\n def connect(self):\n \"\"\"Public method for connecting to IntesisHome API.\"\"\"\n if self._connectionStatus == API_DISCONNECTED:\n self._connectionStatus = API_CONNECTING\n try:\n # Must poll to get the authentication token\n if self._ip and self._port:\n # Create asyncio socket\n coro = self._eventLoop.create_connection(\n lambda: self, self._ip, self._port\n )\n _LOGGER.debug(\n \"Opening connection to IntesisBox %s:%s\", self._ip, self._port\n )\n _ = ensure_future(coro, loop=self._eventLoop)\n else:\n _LOGGER.debug(\"Missing IP address or port.\")\n self._connectionStatus = API_DISCONNECTED\n\n except Exception as e:\n _LOGGER.error(\"%s Exception. %s / %s\", type(e), repr(e.args), e)\n self._connectionStatus = API_DISCONNECTED\n else:\n _LOGGER.debug(\"connect() called but already connecting\")\n\n def stop(self):\n \"\"\"Public method for shutting down connectivity with the envisalink.\"\"\"\n self._connectionStatus = API_DISCONNECTED\n self._transport.close()\n\n async def poll_status(self, sendcallback=False):\n \"\"\"Periodically poll for updates since the controllers don't always update reliably.\"\"\"\n while self.is_connected:\n _LOGGER.debug(\"Polling for update\")\n self._write(\"GET,1:*\")\n await asyncio.sleep(60 * 5) # 5 minutes\n else:\n _LOGGER.debug(\"Not connected, skipping poll_status()\")\n\n def set_temperature(self, setpoint):\n \"\"\"Public method for setting the temperature.\"\"\"\n set_temp = int(setpoint * 10)\n self._set_value(FUNCTION_SETPOINT, set_temp)\n\n def set_fan_speed(self, fan_speed):\n \"\"\"Public method to set the fan speed.\"\"\"\n self._set_value(FUNCTION_FANSP, fan_speed)\n\n def set_vertical_vane(self, vane: str):\n \"\"\"Public method to set the vertical vane.\"\"\"\n self._set_value(FUNCTION_VANEUD, vane)\n\n def set_horizontal_vane(self, vane: str):\n \"\"\"Public method to set the horizontal vane.\"\"\"\n self._set_value(FUNCTION_VANELR, vane)\n\n def _set_value(self, uid: str, value: str | int) -> None:\n \"\"\"Change a setting on the thermostat.\"\"\"\n try:\n self._write(f\"SET,1:{uid},{value}\")\n except Exception as e:\n _LOGGER.error(\"%s Exception. %s / %s\", type(e), e.args, e)\n\n def set_mode(self, mode: str) -> None:\n \"\"\"Change the thermostat mode (heat, cool, etc).\"\"\"\n if not self.is_on:\n self.set_power_on()\n\n if mode in MODES:\n self._set_value(FUNCTION_MODE, mode)\n\n def set_mode_dry(self):\n \"\"\"Public method to set device to dry asynchronously.\"\"\"\n self._set_value(FUNCTION_MODE, MODE_DRY)\n\n def set_power_off(self):\n \"\"\"Public method to turn off the device asynchronously.\"\"\"\n self._set_value(FUNCTION_ONOFF, POWER_OFF)\n\n def set_power_on(self):\n \"\"\"Public method to turn on the device asynchronously.\"\"\"\n self._set_value(FUNCTION_ONOFF, POWER_ON)\n\n @property\n def operation_list(self) -> list[str]:\n \"\"\"Supported modes.\"\"\"\n return self._operation_list\n\n @property\n def vane_horizontal_list(self) -> list[str]:\n \"\"\"Supported Horizontal Vane settings.\"\"\"\n return self._horizontal_vane_list\n\n @property\n def vane_vertical_list(self) -> list[str]:\n \"\"\"Supported Vertical Vane settings.\"\"\"\n return self._vertical_vane_list\n\n @property\n def mode(self) -> str | None:\n \"\"\"Current mode.\"\"\"\n return self._device.get(FUNCTION_MODE)\n\n @property\n def fan_speed(self) -> str | None:\n \"\"\"Current fan speed.\"\"\"\n return self._device.get(FUNCTION_FANSP)\n\n @property\n def fan_speed_list(self) -> list[str]:\n \"\"\"Supported fan speeds.\"\"\"\n return self._fan_speed_list\n\n @property\n def device_mac_address(self) -> str | None:\n \"\"\"MAC address of the IntesisBox.\"\"\"\n return self._mac\n\n @property\n def device_model(self) -> str | None:\n \"\"\"Model of the IntesisBox.\"\"\"\n return self._model\n\n @property\n def firmware_version(self) -> str | None:\n \"\"\"Firmware versioon of the IntesisBox.\"\"\"\n return self._firmversion\n\n @property\n def is_on(self) -> bool:\n \"\"\"Return true if the controlled device is turned on.\"\"\"\n return self._device.get(FUNCTION_ONOFF) == POWER_ON\n\n @property\n def has_swing_control(self) -> bool:\n \"\"\"Return true if the device supports swing modes.\"\"\"\n return len(self._horizontal_vane_list) > 1 or len(self._vertical_vane_list) > 1\n\n @property\n def setpoint(self) -> float | None:\n \"\"\"Public method returns the target temperature.\"\"\"\n setpoint = self._device.get(FUNCTION_SETPOINT)\n return (int(setpoint) / 10) if setpoint else None\n\n @property\n def ambient_temperature(self) -> float | None:\n \"\"\"Public method returns the current temperature.\"\"\"\n temperature = self._device.get(FUNCTION_AMBTEMP)\n return (int(temperature) / 10) if temperature else None\n\n @property\n def max_setpoint(self) -> float | None:\n \"\"\"Maximum allowed target temperature.\"\"\"\n return self._setpoint_maximum\n\n @property\n def min_setpoint(self) -> float | None:\n \"\"\"Minimum allowed target temperature.\"\"\"\n return self._setpoint_minimum\n\n @property\n def rssi(self) -> int | None:\n \"\"\"Wireless signal strength of the IntesisBox.\"\"\"\n return self._rssi\n\n @property\n def vertical_swing(self) -> str | None:\n \"\"\"Current vertical vane setting.\"\"\"\n return self._device.get(FUNCTION_VANEUD)\n\n @property\n def horizontal_swing(self) -> str | None:\n \"\"\"Current horizontal vane setting.\"\"\"\n return self._device.get(FUNCTION_VANELR)\n\n def _send_update_callback(self):\n \"\"\"Notify all listeners that state of the thermostat has changed.\"\"\"\n if not self._updateCallbacks:\n _LOGGER.debug(\"Update callback has not been set by client.\")\n\n for callback in self._updateCallbacks:\n callback()\n\n def _send_error_callback(self, message: str):\n \"\"\"Notify all listeners that an error has occurred.\"\"\"\n self._errorMessage = message\n\n if self._errorCallbacks == []:\n _LOGGER.debug(\"Error callback has not been set by client.\")\n\n for callback in self._errorCallbacks:\n callback(message)\n\n @property\n def is_connected(self) -> bool:\n \"\"\"Returns true if the TCP connection is established.\"\"\"\n return self._connectionStatus == API_AUTHENTICATED\n\n @property\n def error_message(self) -> str | None:\n \"\"\"Returns the last error message, or None if there were no errors.\"\"\"\n return self._errorMessage\n\n @property\n def is_disconnected(self) -> bool:\n \"\"\"Returns true when the TCP connection is disconnected and idle.\"\"\"\n return self._connectionStatus == API_DISCONNECTED\n\n def add_update_callback(self, method):\n \"\"\"Public method to add a callback subscriber.\"\"\"\n self._updateCallbacks.append(method)\n\n def add_error_callback(self, method):\n \"\"\"Public method to add a callback subscriber.\"\"\"\n self._errorCallbacks.append(method)\n
-Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
-<+>UTF-8
-===================================================================
-diff --git a/custom_components/intesisbox/intesisbox.py b/custom_components/intesisbox/intesisbox.py
---- a/custom_components/intesisbox/intesisbox.py (revision 43d9f4f1385539ffd5725bef604fd718bfc1973d)
-+++ b/custom_components/intesisbox/intesisbox.py (date 1709987882337)
-@@ -1,5 +1,7 @@
- """Communication with an Intesisbox device."""
-
-+from __future__ import annotations
-+
- import asyncio
- from asyncio import BaseTransport, ensure_future
- from collections.abc import Callable
-@@ -78,6 +80,15 @@
- else:
- _LOGGER.debug("Not connected, skipping keepalive")
-
-+ async def poll_ambtemp(self):
-+ """Retrieve Ambient Temperature to prevent integration timeouts."""
-+ while self.is_connected:
-+ _LOGGER.debug("Sending AMBTEMP")
-+ self._write("GET,1:AMBTEMP")
-+ await asyncio.sleep(10)
-+ else:
-+ _LOGGER.debug("Not connected, skipping Ambient Temp Request")
-+
- async def query_initial_state(self):
- """Fetch configuration from the device upon connection."""
- cmds = [
-@@ -92,9 +103,21 @@
- self._write(cmd)
- await asyncio.sleep(1)
-
-+ async def delay(self, delay_time):
-+ """Async Delay to slow down commands and await response from units."""
-+ _LOGGER.debug(f"Delay Started for {delay_time} seconds...")
-+ await asyncio.sleep(delay_time)
-+ _LOGGER.debug("Delay Ended...")
-+
- def _write(self, cmd):
- self._transport.write(f"{cmd}\r".encode("ascii"))
- _LOGGER.debug(f"Data sent: {cmd!r}")
-+
-+ async def _writeasync(self, cmd):
-+ """Async write to slow down commands and await response from units."""
-+ self._transport.write(f"{cmd}\r".encode("ascii"))
-+ _LOGGER.debug(f"Data sent: {cmd!r}")
-+ await asyncio.sleep(1)
-
- def data_received(self, data):
- """Asyncio callback when data is received on the socket."""
-@@ -111,8 +134,9 @@
- if cmd == "ID":
- self._parse_id_received(args)
- self._connectionStatus = API_AUTHENTICATED
-- _ = asyncio.ensure_future(self.keep_alive())
-+ # _ = asyncio.ensure_future(self.keep_alive())
- _ = asyncio.ensure_future(self.poll_status())
-+ _ = asyncio.ensure_future(self.poll_ambtemp())
- elif cmd == "CHN,1":
- self._parse_change_received(args)
- statusChanged = True
-@@ -244,17 +268,37 @@
- def _set_value(self, uid: str, value: str | int) -> None:
- """Change a setting on the thermostat."""
- try:
-- self._write(f"SET,1:{uid},{value}")
-+ # self._write(f"SET,1:{uid},{value}")
-+ asyncio.run(self._writeasync(f"SET,1:{uid},{value}"))
- except Exception as e:
- _LOGGER.error("%s Exception. %s / %s", type(e), e.args, e)
-
-- def set_mode(self, mode: str) -> None:
-- """Change the thermostat mode (heat, cool, etc)."""
-+ def set_mode(self, mode):
-+ """Send mode and confirm change before turning on."""
-+ """ Some units return responses out of order"""
-+ _LOGGER.debug(f"Setting MODE to {mode}.")
-+ if mode in MODES:
-+ self._set_value(FUNCTION_MODE, mode)
- if not self.is_on:
-- self.set_power_on()
--
-- if mode in MODES:
-- self._set_value(FUNCTION_MODE, mode)
-+ """ "Check to ensure in correct mode before turning on"""
-+ retry = 30
-+ while self.mode != mode and retry > 0:
-+ _LOGGER.debug(
-+ f"Waiting for MODE to return {mode}, currently {str(self.mode)}"
-+ )
-+ _LOGGER.debug(f"Retry attempt = {retry}")
-+ # asyncio.run(self.delay(1)) # SHANE
-+ # self._write("GET,1:MODE")
-+ asyncio.run(self._writeasync("GET,1:MODE"))
-+ retry -= 1
-+ else:
-+ if retry != 0:
-+ _LOGGER.debug(
-+ f"MODE confirmed now {str(self.mode)}, proceed to Power On"
-+ )
-+ self.set_power_on()
-+ else:
-+ _LOGGER.error("Cannot set Intesisbox mode giving up...")
-
- def set_mode_dry(self):
- """Public method to set device to dry asynchronously."""
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 35eb1dd..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
deleted file mode 100644
index e4c75bd..0000000
--- a/.idea/workspace.xml
+++ /dev/null
@@ -1,102 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- "customColor": "",
- "associatedIndex": 3
-}
-
-
-
-
-
- {
- "keyToString": {
- "RunOnceActivity.OpenProjectViewOnStart": "true",
- "RunOnceActivity.ShowReadmeOnStart": "true",
- "git-widget-placeholder": "main",
- "settings.editor.selected.configurable": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable"
- }
-}
-
-
-
-
-
-
-
-
-
-
-
- 1709979117827
-
-
- 1709979117827
-
-
-
- 1709988940994
-
-
-
- 1709988940994
-
-
-
- 1709989404146
-
-
-
- 1709989404146
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- file://$PROJECT_DIR$/custom_components/intesisbox/climate.py
- 6
-
-
-
-
-
-
\ No newline at end of file
From 15f732fd5f9b88f49158b8dfb3dd50868bb53953 Mon Sep 17 00:00:00 2001
From: CV8R <39397297+CV8R@users.noreply.github.com>
Date: Tue, 2 Apr 2024 22:13:11 +1100
Subject: [PATCH 7/8] Ignore Mac OS DS_Store
---
.gitignore | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.gitignore b/.gitignore
index e947772..89f4f67 100644
--- a/.gitignore
+++ b/.gitignore
@@ -105,3 +105,6 @@ venv.bak/
#.idea
/.idea
+
+# Ignore Mac DS_Store files
+.DS_Store
\ No newline at end of file
From da7dc2bb338f995bedddc6cf49805c7533b21951 Mon Sep 17 00:00:00 2001
From: CV8R <39397297+CV8R@users.noreply.github.com>
Date: Tue, 2 Apr 2024 22:19:56 +1100
Subject: [PATCH 8/8] Remove DS_Store
---
custom_components/.DS_Store | Bin 6148 -> 0 bytes
custom_components/intesisbox/.DS_Store | Bin 6148 -> 0 bytes
2 files changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 custom_components/.DS_Store
delete mode 100644 custom_components/intesisbox/.DS_Store
diff --git a/custom_components/.DS_Store b/custom_components/.DS_Store
deleted file mode 100644
index c6ddd7e3783bcade5facb0b30994e6052bf6e4f1..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 6148
zcmeHK%}N6?5T3NvZY@F&3OxqA7Obs`;$^Az1zgdCO5LSJ7uSt+w^k^HJ?jhkBtDNb
zNw!d04_-v<3{1Xcem3OGl1TtSbVmI>fEoZesDy^om=lgjlI-Q
zM@h#YUeoA8sVK<%esCEM#=Y9ssftrSj0Z!V5cUTca&r~NeKqc=QQS{-u4f!h#i{ga
z^~vO@)oRFtR(slzljByiA@|#d(`m)o-q}4l?>$COv3fIv3jFu9Y+B6W9Xm_c_TmlW
zNX7T)&vWw}MrME+UQ
z^p+r$7F~;(LG+*qlZt3kg?(ZOla6+2<6MiGL6Z(b&y3%(GYk7d5qfsCOC1ivHOMV9
zzzobYP&C6D)&H~a-~aPTJYoizfq%t-D0RJV2bW}P>%!uw)=JbnR1%8I49-%pqf0Ty
eQYo&ZYC*fC4x(!@Gl&)xz6fXIF~=R@#GmeZ-@zBK@F{HVtbGnE
z{bqKH<2X!ggqRt!`|Zumx3l}f?y^K=I?GX;s7XW>6vlEDRe|w)E^AhDo;uJ-A5*%a
zGkjV!UyD|UDxeDNngYCbYqDmi6j65lKJ>zPI0)kbB6xdvL1}%7p+!`sA%$Sf=mM+$
zQr3KqV~%p1eQ;vr8G|=L)-igB>8_CbnBz9Ui6m2kRzd?X?+clqIpzWNR+t@Ob}{lL
z^7H6pS`_tEn&f(F$ZT1sF=m{A*`jM8=9E+G6wIfLT6M%OkSjv8%{gTjB?Y{!${Ts%
zq}?CJGq=y0cjVg`JBx$BeUp`s6Z3rh_R8b?A%66$`1w_KEc|2H-1lvN5ruKY%HqsW
z+oA$a|4MC(W6;#9fGV(?3h@5mp)mRmQ;V{7ppq*9une~t+Oq!xJ$3+nhp9z)V8&8`
zmTKG;!&o}}o{RGxrWP%ojATYXF0*kr6eHQ;_avN*Z&7PiKowY3V9!1_`TXy0zW=Y1
z^hp&^1^$%+rX2Kx4yNSJ*0ss;S?i&kqp)#YYEdYt+;J=oK8g=f#PH7J2GDnyT7(Ct
Oe*{DZtyF