Skip to content

Commit

Permalink
feat: 🎸 first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
RayChang committed Apr 19, 2024
0 parents commit dd02228
Show file tree
Hide file tree
Showing 16 changed files with 1,221 additions and 0 deletions.
22 changes: 22 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Validate

on:
push:
branches:
- main
pull_request:
branches:
- main
schedule:
- cron: "0 0 * * *"
workflow_dispatch:

jobs:
validate-hacs:
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v3"
- name: HACS validation
uses: "hacs/action@main"
with:
category: "integration"
24 changes: 24 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# artifacts
__pycache__
.pytest*
*.egg-info
*/build/*
*/dist/*


# misc
.coverage
.vscode
venv/
__pycache__/
.env
.venv
.ipynb_checkpoints/
.DS_Store
*.py[cod]
coverage.xml


# Home Assistant configuration
config/*
!config/configuration.yaml
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## RS-485

透過 rs-485 to tcp 裝置控制特定的 rs-485 裝置

目前僅支援:
- rs-485 開關 LP-F8
- 杜亞窗簾電機 CMD82-5S

使用方式:
- 輸入 rs-485 to tcp 的 ip 位置與 port
- 選擇要加入的裝置類型
- 繼電器開關
- 裝置名稱
- 從機位置(10進位)
- 按鍵數量
- 是否包含繼電器(強電)
- 窗簾電機
- 裝置名稱
- 從機位置(10進位)
預設位置為 0x12 0x34 所以要輸入 4660

目前功能只開發必要功能,還有很多功能尚未實現
86 changes: 86 additions & 0 deletions custom_components/rs485_device/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""The RS-485 Device integration."""

from __future__ import annotations

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_COVERS,
CONF_DEVICE,
CONF_HOST,
CONF_NAME,
CONF_PORT,
CONF_SENSORS,
CONF_SLAVE,
CONF_STATE,
CONF_SWITCHES,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr

from .const import CURTAIN_MODEL, DOMAIN, SENSOR_MODEL, SWITCH_MODEL
from .rs485_tcp_publisher import RS485TcpPublisher

PLATFORMS: dict[str, list[Platform]] = {
CONF_SWITCHES: [Platform.SWITCH],
CONF_COVERS: [Platform.COVER],
CONF_SENSORS: [Platform.SENSOR],
}


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""獲取裝置註冊表。."""

# 從 entry.data 中獲取所配置的裝置類型
device_type = entry.data[CONF_DEVICE]

device_registry = dr.async_get(hass)

_model = None
_domain_data = {"watchdog_task": None}
if device_type == CONF_SWITCHES:
_model = SWITCH_MODEL
_domain_data.update(
{
CONF_STATE: None,
CONF_SWITCHES: None,
}
)
elif device_type == CONF_COVERS:
_model = CURTAIN_MODEL
elif device_type == CONF_SENSORS:
_model = SENSOR_MODEL[0]

# 在裝置註冊表中創建一個新的裝置
device = device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(DOMAIN, entry.data[CONF_SLAVE])},
name=entry.data[CONF_NAME],
model=_model,
)

hass.data.setdefault(
DOMAIN,
{
"rs485_tcp_publisher": RS485TcpPublisher(
host=entry.data[CONF_HOST], port=entry.data[CONF_PORT], byte_length=12
)
},
)
hass.data[DOMAIN][entry.entry_id] = {CONF_DEVICE: device, **_domain_data}

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS[device_type])

return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
device_type = entry.data[CONF_DEVICE]

if unload_ok := await hass.config_entries.async_unload_platforms(
entry, PLATFORMS[device_type]
):
hass.data[DOMAIN].pop(entry.entry_id)

return unload_ok
105 changes: 105 additions & 0 deletions custom_components/rs485_device/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"""Config flow for RS-485 Device integration."""

from __future__ import annotations

import logging
from typing import Any, Optional

import voluptuous as vol

from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import (
CONF_COUNT,
CONF_DEVICE,
CONF_ENTITY_ID,
CONF_HOST,
CONF_NAME,
CONF_PORT,
CONF_SLAVE,
CONF_SWITCHES,
)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import async_generate_entity_id

from .const import DEFAULT_NAME, DEVICE_TYPE, DOMAIN, HAS_RELAY, KEY_COUNT

_LOGGER = logging.getLogger(__name__)

STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_HOST, default="10.0.4.101"): cv.string,
vol.Required(CONF_PORT, default=4196): int,
vol.Required(CONF_DEVICE, default=CONF_SWITCHES): vol.In(DEVICE_TYPE),
}
)

STEP_SWITCH_CONFIG_SCHEMA = {
vol.Required(CONF_SLAVE, default=1): cv.positive_int,
vol.Required(CONF_COUNT, default=1): vol.In(KEY_COUNT),
vol.Required(HAS_RELAY, default=True): cv.boolean,
}


class RS485DeviceConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for RS-485 Device."""

VERSION = 1

def __init__(self):
"""Initialize the config flow."""
self.rs485_config = {}

async def async_step_user(
self, user_input: Optional[dict[str, Any]] | None = None
) -> ConfigFlowResult:
"""Handle the user step of the config flow."""
if user_input is not None:
self.rs485_config.update(user_input)
return await self.async_step_device_config()

return self.async_show_form(step_id="user", data_schema=STEP_USER_DATA_SCHEMA)

async def async_step_device_config(
self, user_input: Optional[dict[str, Any]] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""

errors: dict[str, str] = {}
if user_input is not None:
self.rs485_config.update(user_input)
try:
self.rs485_config[CONF_ENTITY_ID] = async_generate_entity_id(
DOMAIN + ".{}",
f"{self.rs485_config[CONF_DEVICE]}.{self.rs485_config[CONF_NAME]}.{self.rs485_config[CONF_SLAVE]}",
hass=self.hass,
)
return self.async_create_entry(
title=self.rs485_config[CONF_NAME],
data=self.rs485_config,
)
except ValueError as e:
_LOGGER.exception("Error generating entity ID: %s", e)
errors["base"] = "entity_id_error"
except KeyError as e:
_LOGGER.exception("Missing required input: %s", e)
errors["base"] = "missing_input"
except Exception as e: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception: %s", e)
errors["base"] = "unknown"

device_type = self.rs485_config[CONF_DEVICE]

default_name = DEFAULT_NAME[device_type]

schema = {
vol.Required(CONF_NAME, default=default_name): cv.string,
vol.Required(CONF_SLAVE, default=1): cv.positive_int,
}

if device_type == CONF_SWITCHES:
schema.update(STEP_SWITCH_CONFIG_SCHEMA)

return self.async_show_form(
step_id="device_config",
data_schema=vol.Schema(schema),
)
29 changes: 29 additions & 0 deletions custom_components/rs485_device/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Constants for the RS-485 Device integration."""

from typing import Final

from homeassistant.const import CONF_COVERS, CONF_SENSORS, CONF_SWITCHES

DOMAIN: Final = "rs485_device"
MODBUS_HUB: Final = "rs-485_device_hub"
CURTAIN_MODEL: Final = "CMD82-5S"
SENSOR_MODEL: Final = ["SD123-HPR05", "SD123-HPR06"]
SWITCH_MODEL: Final = "LP-F8"
DEFAULT_NAME: Final = {
CONF_SWITCHES: "Wall Switch",
CONF_COVERS: "Curtain",
CONF_SENSORS: "Sensor",
}

# 按鈕數量
KEY_COUNT: Final = list(range(1, 7))

# 含有繼電器
HAS_RELAY: Final = "has_relay"

# 設備類型
DEVICE_TYPE: Final = {
CONF_SWITCHES: CONF_SWITCHES,
CONF_COVERS: CONF_COVERS,
CONF_SENSORS: CONF_SENSORS,
}
Loading

0 comments on commit dd02228

Please sign in to comment.