Skip to content

Commit

Permalink
Merge pull request #92 from hmn/90-humidity-sensor
Browse files Browse the repository at this point in the history
90 humidity sensor
  • Loading branch information
hmn authored Mar 30, 2024
2 parents d564521 + 069cde8 commit 07e170d
Show file tree
Hide file tree
Showing 18 changed files with 393 additions and 89 deletions.
18 changes: 9 additions & 9 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// See https://aka.ms/vscode-remote/devcontainer.json for format details.
{
"name": "Siku Fan integration development",
"image": "mcr.microsoft.com/vscode/devcontainers/python:0-3.11-bullseye",
"image": "mcr.microsoft.com/vscode/devcontainers/python:3.12-bookworm",
"postCreateCommand": "scripts/setup",
"forwardPorts": [
8123
Expand All @@ -15,19 +15,19 @@
"customizations": {
"vscode": {
"extensions": [
"ms-python.python",
"github.vscode-pull-request-github",
"ryanluker.vscode-coverage-gutters",
"ms-python.vscode-pylance",
"GitHub.copilot"
],
"ms-python.python",
"github.vscode-pull-request-github",
"ryanluker.vscode-coverage-gutters",
"ms-python.vscode-pylance",
"GitHub.copilot",
"github.vscode-github-actions",
"charliermarsh.ruff"
],
"settings": {
"files.eol": "\n",
"editor.tabSize": 4,
"python.pythonPath": "/usr/bin/python3",
"python.analysis.autoSearchPaths": false,
"python.linting.pylintEnabled": true,
"python.linting.enabled": true,
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ jobs:
runs-on: "ubuntu-latest"
steps:
- name: "Checkout the repository"
uses: "actions/checkout@v3.5.2"
uses: "actions/checkout@v4"

- name: "Set up Python"
uses: actions/setup-python@v5.0.0
uses: actions/setup-python@v5.1.0
with:
python-version: "3.10"
python-version: "3.12"
cache: "pip"

- name: "Install requirements"
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
contents: write
steps:
- name: "Checkout the repository"
uses: "actions/checkout@v3.5.2"
uses: "actions/checkout@v4"

- name: "Adjust version number"
shell: "bash"
Expand All @@ -30,6 +30,6 @@ jobs:
zip siku-integration.zip -r ./
- name: "Upload the ZIP file to the release"
uses: softprops/action-gh-release@v0.1.15
uses: softprops/action-gh-release@v2.0.2
with:
files: ${{ github.workspace }}/custom_components/siku/siku-integration.zip
4 changes: 2 additions & 2 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
runs-on: "ubuntu-latest"
steps:
- name: "Checkout the repository"
uses: "actions/checkout@v3.5.2"
uses: "actions/checkout@v4"

- name: "Run hassfest validation"
uses: "home-assistant/actions/hassfest@master"
Expand All @@ -27,7 +27,7 @@ jobs:
runs-on: "ubuntu-latest"
steps:
- name: "Checkout the repository"
uses: "actions/checkout@v3.5.2"
uses: "actions/checkout@v4"

- name: "Run HACS validation"
uses: "hacs/action@main"
Expand Down
68 changes: 34 additions & 34 deletions .ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,47 @@

target-version = "py310"

select = [
"B007", # Loop control variable {name} not used within loop body
"B014", # Exception handler with duplicate exception
"C", # complexity
"D", # docstrings
"E", # pycodestyle
"F", # pyflakes/autoflake
"ICN001", # import concentions; {name} should be imported as {asname}
lint.select = [
"B007", # Loop control variable {name} not used within loop body
"B014", # Exception handler with duplicate exception
"C", # complexity
"D", # docstrings
"E", # pycodestyle
"F", # pyflakes/autoflake
"ICN001", # import concentions; {name} should be imported as {asname}
"PGH004", # Use specific rule codes when using noqa
"PLC0414", # Useless import alias. Import alias does not rename original package.
"SIM105", # Use contextlib.suppress({exception}) instead of try-except-pass
"SIM117", # Merge with-statements that use the same scope
"SIM118", # Use {key} in {dict} instead of {key} in {dict}.keys()
"SIM201", # Use {left} != {right} instead of not {left} == {right}
"SIM212", # Use {a} if {a} else {b} instead of {b} if not {a} else {a}
"SIM300", # Yoda conditions. Use 'age == 42' instead of '42 == age'.
"SIM401", # Use get from dict with default instead of an if block
"T20", # flake8-print
"TRY004", # Prefer TypeError exception for invalid type
"RUF006", # Store a reference to the return value of asyncio.create_task
"UP", # pyupgrade
"W", # pycodestyle
"SIM105", # Use contextlib.suppress({exception}) instead of try-except-pass
"SIM117", # Merge with-statements that use the same scope
"SIM118", # Use {key} in {dict} instead of {key} in {dict}.keys()
"SIM201", # Use {left} != {right} instead of not {left} == {right}
"SIM212", # Use {a} if {a} else {b} instead of {b} if not {a} else {a}
"SIM300", # Yoda conditions. Use 'age == 42' instead of '42 == age'.
"SIM401", # Use get from dict with default instead of an if block
"T20", # flake8-print
"TRY004", # Prefer TypeError exception for invalid type
"RUF006", # Store a reference to the return value of asyncio.create_task
"UP", # pyupgrade
"W", # pycodestyle
]

ignore = [
"D202", # No blank lines allowed after function docstring
"D203", # 1 blank line required before class docstring
"D213", # Multi-line docstring summary should start at the second line
"D404", # First word of the docstring should not be This
"D406", # Section name should end with a newline
"D407", # Section name underlining
"D411", # Missing blank line before section
"E501", # line too long
"E731", # do not assign a lambda expression, use a def
lint.ignore = [
"D202", # No blank lines allowed after function docstring
"D203", # 1 blank line required before class docstring
"D213", # Multi-line docstring summary should start at the second line
"D404", # First word of the docstring should not be This
"D406", # Section name should end with a newline
"D407", # Section name underlining
"D411", # Missing blank line before section
"E501", # line too long
"E731", # do not assign a lambda expression, use a def
]

[flake8-pytest-style]
[lint.flake8-pytest-style]
fixture-parentheses = false

[pyupgrade]
[lint.pyupgrade]
keep-runtime-typing = true

[mccabe]
max-complexity = 25
[lint.mccabe]
max-complexity = 25
8 changes: 7 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
{
"recommendations": [
"github.copilot"
"ms-python.python",
"github.vscode-pull-request-github",
"ryanluker.vscode-coverage-gutters",
"ms-python.vscode-pylance",
"GitHub.copilot",
"github.vscode-github-actions",
"charliermarsh.ruff"
]
}
6 changes: 6 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"[python]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "charliermarsh.ruff"
}
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ The fan is sold under different brands, for instance :
- [SIKU RV/Twinfresh](https://www.siku.at/produkte/)
- [DUKA One](https://dukaventilation.dk/produkter/1-rums-ventilationsloesninger)
- [Oxxify](https://raumluft-shop.de/lueftung/dezentrale-lueftungsanlage-mit-waermerueckgewinnung/oxxify.html)
- [Twinfresh](https://twinfresh.no)

## Installation

Expand Down
3 changes: 2 additions & 1 deletion custom_components/siku/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""The Siku Fan integration."""

from __future__ import annotations

from homeassistant.config_entries import ConfigEntry
Expand All @@ -9,7 +10,7 @@
from .const import DOMAIN
from .coordinator import SikuDataUpdateCoordinator

PLATFORMS: list[Platform] = [Platform.FAN]
PLATFORMS: list[Platform] = [Platform.FAN, Platform.SENSOR]


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
Expand Down
2 changes: 2 additions & 0 deletions custom_components/siku/api_v1.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Helper api function for sending commands to the fan controller."""

import logging
import socket

Expand Down Expand Up @@ -135,4 +136,5 @@ async def _translate_response(self, hexlist: list[str]) -> dict:
if direction_value != DIRECTION_ALTERNATING
else None,
"mode": mode_value,
"version": "1",
}
79 changes: 78 additions & 1 deletion custom_components/siku/api_v2.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Helper api function for sending commands to the fan controller."""

import logging
import socket

Expand Down Expand Up @@ -35,7 +36,28 @@
COMMAND_SPEED = "02"
COMMAND_DIRECTION = "B7"
COMMAND_DEVICE_TYPE = "B9"
COMMAND_BOOST = "06"
COMMAND_MODE = "07"
COMMAND_CURRENT_HUMIDITY = "25"
COMMAND_MANUAL_SPEED = "44"
COMMAND_FAN1RPM = "4A"
# Byte 1: Minutes (0...59)
# Byte 2: Hours (0...23)
# Byte 3: Days (0...181)
COMMAND_FILTER_TIMER = "64"
COMMAND_RESET_FILTER_TIMER = "65"
COMMAND_SEARCH = "7C"
COMMAND_RUN_HOURS = "7E"
COMMAND_RESET_ALARMS = "80"
COMMAND_READ_ALARM = "83"
# Byte 1: Firmware-Version (major)
# Byte 2: Firmware-Version (minor)
# Byte 3: Day
# Byte 4: Month
# Byte 5 and 6: Year
COMMAND_READ_FIRMWARE_VERSION = "86"
COMMAND_FILTER_ALARM = "88"
COMMAND_FAN_TYPE = "B9"

COMMAND_FUNCTION_R = "01"
COMMAND_FUNCTION_W = "02"
Expand Down Expand Up @@ -69,7 +91,20 @@ def __init__(self, host: str, port: int, idnum: str, password: str) -> None:

async def status(self) -> dict:
"""Get status from fan controller."""
cmd = f"{COMMAND_DEVICE_TYPE}{COMMAND_ON_OFF}{COMMAND_SPEED}{COMMAND_DIRECTION}{COMMAND_MODE}".upper()
commands = [
COMMAND_DEVICE_TYPE,
COMMAND_ON_OFF,
COMMAND_SPEED,
COMMAND_DIRECTION,
COMMAND_BOOST,
COMMAND_MODE,
COMMAND_CURRENT_HUMIDITY,
COMMAND_FAN1RPM,
COMMAND_FILTER_TIMER,
COMMAND_READ_ALARM,
COMMAND_READ_FIRMWARE_VERSION,
]
cmd = "".join(commands).upper()
hexlist = await self._send_command(FUNC_READ, cmd)
data = await self._parse_response(hexlist)
return await self._translate_response(data)
Expand Down Expand Up @@ -218,16 +253,58 @@ async def _translate_response(self, data: dict) -> dict:
except KeyError:
direction = None
oscillating = True
try:
boost = bool(data[COMMAND_BOOST] != "00")
except KeyError:
boost = False
try:
mode = MODES[data[COMMAND_MODE]]
except KeyError:
mode = PRESET_MODE_AUTO
try:
humidity = int(data[COMMAND_CURRENT_HUMIDITY], 16)
except KeyError:
humidity = None
try:
rpm = int(data[COMMAND_FAN1RPM], 16)
except KeyError:
rpm = 0
try:
# Byte 1: Minutes (0...59)
# Byte 2: Hours (0...23)
# Byte 3: Days (0...181)
minutes = int(data[COMMAND_FILTER_TIMER][0:2], 16)
hours = int(data[COMMAND_FILTER_TIMER][2:4], 16)
days = int(data[COMMAND_FILTER_TIMER][4:6], 16)
filter_timer = int(minutes + hours * 60 + days * 24 * 60)
except KeyError:
filter_timer = 0
try:
alarm = bool(data[COMMAND_READ_ALARM] != "00")
except KeyError:
alarm = False
try:
# Byte 1: Firmware-Version (major)
# Byte 2: Firmware-Version (minor)
# Byte 3: Day
# Byte 4: Month
# Byte 5 and 6: Year
firmware = f"{int(data[COMMAND_READ_FIRMWARE_VERSION][0], 16)}.{int(data[COMMAND_READ_FIRMWARE_VERSION][1], 16)}"
except KeyError:
firmware = None
return {
"is_on": is_on,
"speed": speed,
"oscillating": oscillating,
"direction": direction,
"boost": boost,
"mode": mode,
"humidity": humidity,
"rpm": rpm,
"firmware": firmware,
"filter_timer": filter_timer,
"alarm": alarm,
"version": "2",
}

async def _parse_response(self, hexlist: list[str]) -> dict:
Expand Down
5 changes: 4 additions & 1 deletion custom_components/siku/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Config flow for Siku Fan integration."""

from __future__ import annotations

import logging
Expand Down Expand Up @@ -41,10 +42,12 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
"""
if data[CONF_VERSION] == 1:
api = SikuV1Api(data[CONF_IP_ADDRESS], data[CONF_PORT])
else:
elif data[CONF_VERSION] == 2:
api = SikuV2Api(
data[CONF_IP_ADDRESS], data[CONF_PORT], data[CONF_ID], data[CONF_PASSWORD]
)
else:
raise ValueError("Invalid API version")
if not await api.status():
raise CannotConnect

Expand Down
Loading

0 comments on commit 07e170d

Please sign in to comment.