Skip to content

Commit c9cf787

Browse files
andrew-codechimpgithub-actions[bot]Mariusthvdbszupi-ipuzsmbuett
authored
Library improvements (#1931)
* Add optional model id on library * WIP * New device form * Apply automatic changes * WIP * WIP * WIP * WIP * WIP * WIP * WIP * Docs * WIP * Fixes * WIP * matching docs * Library sorting * Apply automatic changes * Remove test * Docs * Docs * Add condition inputs to blueprints * Bump min HA version to 2024.5 * Add icon translations * Add manual library message * Update device: Vibration_sensor_TS0210 by Tuya (#1849) * Apply automatic changes * Update device: Water_leak_sensor_IH_K665 by Aubess (#1851) * Apply automatic changes * Update device: Hue_secure_contact_sensor_SOC001 by Signify_Netherlands_B_V (#1853) Co-authored-by: Mariusthvdb <[email protected]> * Apply automatic changes * Update device: Smart_button_ZG_101ZL by Loginovo (#1857) * Apply automatic changes * Update device: MJYD02YL by Xiaomi (#1855) * Apply automatic changes * Plant Sensor SGS01 via TuyaBLE integration (#1862) * Apply automatic changes * Update device: BRZ1 by Springs_Window_Fashions (#1865) * Apply automatic changes * Update device: BRZ1 by Springs_Window_Fashions (#1867) * Apply automatic changes * Update device: CSZ1 by Springs_Window_Fashions (#1869) * Apply automatic changes * Update device: VCZ1 by Springs_Window_Fashions (#1873) * Apply automatic changes * Update device: MCZ1 by Springs_Window_Fashions (#1871) * Apply automatic changes * Update device: 914C by Kwikset (#1875) * Apply automatic changes * Update device: 916 by Kwikset (#1877) * Apply automatic changes * Update device: 2844_222_0x10_0x16 by SmartLabs_Inc (#1879) * Apply automatic changes * Update device: 2845_222_0x10_0x11 by SmartLabs_Inc (#1883) * Apply automatic changes * Add Moes thermostat radiator valve variants (#1884) * Apply automatic changes * Update device: 2842_222_0x10_0x01 by SmartLabs_Inc (#1886) Co-authored-by: andrew-codechimp <[email protected]> * Apply automatic changes * Bump softprops/action-gh-release from 2.0.6 to 2.0.8 (#1887) * Update crowdin.yml * New Crowdin translations by GitHub Action (#1888) Co-authored-by: Crowdin Bot <[email protected]> * Update device: T8160 by Eufy_Security (#1891) * Apply automatic changes * Update device: T8210C by Eufy_Security (#1893) * Apply automatic changes * Update device: T8113_V by Eufy_Security (#1895) * Apply automatic changes * Update device: AS008 by Aqara (#1898) * Apply automatic changes * Update device: ZEN37_800LR by Zooz (#1901) * Apply automatic changes * Update library.json Change ZSE42 to manual * Apply automatic changes * Update device: Yale by YRL256 (#1903) * Apply automatic changes * Update device: M3_2_9 by OpenEpaperLink (#1905) * Apply automatic changes * Update device: SNZB_05P by Sonoff (#1907) * Apply automatic changes * Device: Shelly H&T (gen1) (#1908) * Apply automatic changes * Correct Yale YRL256 (#1909) The manufacturer and model were swapped * Apply automatic changes * Fix crowdin.yml * New Crowdin translations by GitHub Action (#1911) Co-authored-by: Crowdin Bot <[email protected]> * Update device: Hinge_PIN_Door_Sensor_500S by GE (#1916) * Apply automatic changes * Update device: Q_Sensor by Zooz (#1920) * Apply automatic changes * Update device: Q_Sensor by GE (#1918) * Apply automatic changes * Update device: FRITZ_DECT_302 by AMV_FritzBox (#1922) * Apply automatic changes * Update device: FRITZ_DECT_350 by AMV_FritzBox (#1924) * Apply automatic changes * Update device: FRITZ_DECT_440 by AMV_FritzBox (#1926) * Apply automatic changes * Update library.json * Apply automatic changes * Device: Eaton - Ellipse ECO 650 (#1927) Added new device Eaton - Ellipse ECO 650 * Apply automatic changes * Update device: 99120_021 by Kwikset (#1929) Co-authored-by: CobraDunn <[email protected]> * Apply automatic changes * New Crowdin translations by GitHub Action (#1930) * Apply automatic changes --------- Co-authored-by: andrew-codechimp <[email protected]> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Mariusthvdb <[email protected]> Co-authored-by: Piotr Szulc <[email protected]> Co-authored-by: mbuett <[email protected]> Co-authored-by: andrew-codechimp <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Crowdin Bot <[email protected]> Co-authored-by: Rohan Kapoor <[email protected]> Co-authored-by: Jeffrey <[email protected]> Co-authored-by: CobraDunn <[email protected]>
1 parent 7dfd96f commit c9cf787

40 files changed

+420
-122
lines changed

.github/ISSUE_TEMPLATE/new_device_request.yml

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ body:
99
The battery library is a JSON document at [custom_components/battery_notes/data/library.json](https://github.com/andrew-codechimp/HA-Battery-Notes/blob/main/custom_components/battery_notes/data/library.json)
1010
To contribute, submit your device details via this form and the relevant code changes will be proposed on your behalf.
1111
Note: The title above is not used and can be just a friendly description of the device. Manufacturer and model should be exactly what is displayed on the Device screen within Home Assistant.
12+
If your device has a Model ID or HW Version then these must be included.
1213
To see your devices, click here:
1314
1415
[![Open your Home Assistant instance and show your devices.](https://my.home-assistant.io/badges/devices.svg)](https://my.home-assistant.io/redirect/devices/)
@@ -18,7 +19,7 @@ body:
1819
attributes:
1920
label: Manufacturer
2021
description: The manufacturer should be exactly what is displayed on the Devices screen within Home Assistant.
21-
placeholder: ex. eWeLink
22+
placeholder: ex. Philips
2223
validations:
2324
required: true
2425

@@ -27,12 +28,30 @@ body:
2728
attributes:
2829
label: Model
2930
description: The model should be exactly what is displayed on the Devices screen within Home Assistant.
30-
placeholder: ex. DS01
31+
placeholder: ex. Hue dimmer switch
3132
validations:
3233
required: true
3334

3435
- type: input
35-
id: battery-type
36+
id: model_id
37+
attributes:
38+
label: Model ID
39+
description: If the device has a Model ID in the Devices screen within Home Assistant it must be included.
40+
placeholder: ex. 324131092621
41+
validations:
42+
required: false
43+
44+
- type: input
45+
id: hw_version
46+
attributes:
47+
label: HW Version
48+
description: If the device has a Hardware version shown in the Devices screen within Home Assistant it must be included.
49+
placeholder: ex. V7.2
50+
validations:
51+
required: false
52+
53+
- type: input
54+
id: battery_type
3655
attributes:
3756
label: Battery Type
3857
description: When specifying battery types please use the Most Common naming for general batteries and the IEC naming for battery cells according to [Wikipedia](https://en.wikipedia.org/wiki/List_of_battery_sizes).
@@ -41,7 +60,7 @@ body:
4160
required: true
4261

4362
- type: input
44-
id: battery-quantity
63+
id: battery_quantity
4564
attributes:
4665
label: Battery Quantity
4766
description: The battery_quantity attribute is numeric (no letters or special characters).

.github/scripts/library_doc/generate_file.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ def generate_device_list():
1111
"""Generate static file containing the device library."""
1212

1313
# Load the existing JSON library file
14-
with open("custom_components/battery_notes/data/library.json",
15-
encoding="UTF-8") as f:
14+
with open(
15+
"custom_components/battery_notes/data/library.json", encoding="UTF-8"
16+
) as f:
1617
devices_json = json.loads(f.read())
1718
devices = devices_json.get("devices")
1819

@@ -26,6 +27,8 @@ def generate_device_list():
2627
headers = [
2728
"Manufacturer",
2829
"Model",
30+
"Model ID",
31+
"Hardware",
2932
"Battery Type",
3033
]
3134

@@ -37,14 +40,20 @@ def generate_device_list():
3740
else:
3841
battery_type_qty = device["battery_type"]
3942

40-
if "hw_version" in device:
41-
model = f"{device['model']} ({device['hw_version']})"
42-
else:
43-
model = device['model']
43+
model = device["model"]
44+
model_match_method = device.get("model_match_method", "")
45+
if model_match_method == "startswith":
46+
model = rf"{model}\*"
47+
if model_match_method == "endswith":
48+
model = rf"\*{model}"
49+
if model_match_method == "contains":
50+
model = rf"\*{model}\*"
4451

4552
row = [
46-
device['manufacturer'],
53+
device["manufacturer"],
4754
model,
55+
device.get("model_id", ""),
56+
device.get("hw_version", ""),
4857
battery_type_qty,
4958
]
5059
rows.append(row)
@@ -59,4 +68,5 @@ def generate_device_list():
5968
md_file.write("".join(toc_links) + tables_output)
6069
md_file.close()
6170

71+
6272
generate_device_list()

.github/workflows/json_librarian.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ jobs:
3737
devices = devices_json.get("devices")
3838
3939
# Sort the devices by manufacturer and model
40-
devices.sort(key=lambda k: (k["manufacturer"].lower(), k["model"].lower(), k.get("hw_version", "").lower()))
40+
devices.sort(key=lambda k: (k["manufacturer"].lower(), k.get("model_match_method", "").lower(), k["model"].lower(), k.get("model_id", "").lower(), k.get("hw_version", "").lower()))
4141
with open("custom_components/battery_notes/data/library.json", "w", encoding="UTF-8") as f:
4242
f.write(json.dumps(devices_json, indent=4))
4343

.github/workflows/new_device.yml

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,20 @@ jobs:
4343
# Remove the "battery_quantity" key from the device dictionary if it's 1
4444
new_device = ${{ steps.device-data.outputs.json }}
4545
# Convert battery_quantity field to a numeric
46-
numeric_quantity = int(new_device["battery_quantity"])
46+
numeric_quantity = int(new_device["battery_quantity"])
4747
del new_device["battery_quantity"]
4848
# Add numeric "battery_quantity" key if it's more than 1
4949
if numeric_quantity > 1:
5050
new_device["battery_quantity"] = numeric_quantity
51+
if new_device.get("model_id", "MISSING").strip() == "":
52+
del new_device["model_id"]
53+
if new_device.get("hw_version", "MISSING").strip() == "":
54+
del new_device["hw_version"]
5155
5256
# Check for duplicates and replace old entry with new one
5357
duplicate_found = False
5458
for i, device in enumerate(devices):
55-
if device["manufacturer"] == new_device["manufacturer"] and device["model"] == new_device["model"]:
59+
if device["manufacturer"] == new_device["manufacturer"] and device["model"] == new_device["model"] and device.get("model_id", "") == new_device.get("model_id", "") and device.get("hw_version", "") == new_device.get("hw_version", ""):
5660
devices[i] = new_device
5761
duplicate_found = True
5862
break
@@ -62,15 +66,17 @@ jobs:
6266
devices.append(new_device)
6367
6468
# Save manufacturer and model for later use
65-
set_output("mm", "_".join(re.findall(r"\w+",f"{new_device['manufacturer']}{new_device['model']})".lower())))
69+
set_output("branch", "_".join(re.findall(r"\w+",f"{new_device['manufacturer']}{new_device['model']}{new_device.get('model_id', '')}{new_device.get('hw_version', '')})".lower())))
6670
set_output("manufacturer", "_".join(re.findall(r"\w+",f"{new_device['manufacturer']})")))
6771
set_output("model", "_".join(re.findall(r"\w+",f"{new_device['model']})")))
72+
set_output("model_id", "_".join(re.findall(r"\w+",f"{new_device.get('model_id', '')})")))
73+
set_output("hw_version", "_".join(re.findall(r"\w+",f"{new_device.get('hw_version', '')})")))
6874
set_output("bqt", f"{numeric_quantity}x {new_device['battery_type']}")
6975
7076
if duplicate_found:
7177
set_output("mode", "updates")
7278
else:
73-
set_output("mode", "adds")
79+
set_output("mode", "adds")
7480
7581
with open("custom_components/battery_notes/data/library.json", "w") as f:
7682
f.write(json.dumps(devices_json, indent=4))
@@ -96,10 +102,17 @@ jobs:
96102
with:
97103
commit-message: "Update device: ${{ steps.update-json.outputs.model }} by ${{ steps.update-json.outputs.manufacturer }}"
98104
title: "Device: ${{ steps.update-json.outputs.manufacturer }} - ${{ steps.update-json.outputs.model }}"
99-
body: "This pull request ${{ steps.update-json.outputs.mode }} the device information for ${{ steps.update-json.outputs.model }} by ${{ steps.update-json.outputs.manufacturer }} with ${{ steps.update-json.outputs.bqt }}\nIt closes issue #${{ github.event.issue.number }}"
100-
branch: "device-${{ steps.update-json.outputs.mm }}"
105+
body: |
106+
This pull request ${{ steps.update-json.outputs.mode }} the device information for:
107+
Manufacturer: ${{ steps.update-json.outputs.manufacturer }}
108+
Model: ${{ steps.update-json.outputs.model }}
109+
Model ID: ${{ steps.update-json.outputs.model_id }}
110+
Hardware: ${{ steps.update-json.outputs.hw_version }}
111+
Battery: ${{ steps.update-json.outputs.bqt }}
112+
It closes issue #${{ github.event.issue.number }}
113+
branch: "device-${{ steps.update-json.outputs.branch }}"
101114

102115
- name: Close Issue
103116
run: gh issue close --comment "Thanks for the contribution. We're auto-closing this issue. If it's a new device, a pull request will be created that will be reviewed and merged." ${{github.event.issue.number}}
104117
env:
105-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
118+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

custom_components/battery_notes/binary_sensor.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,6 @@ async def async_registry_updated(event: Event) -> None:
173173
unique_id_suffix="_battery_low",
174174
key="_battery_plus_low",
175175
translation_key="battery_low",
176-
icon="mdi:battery-alert",
177176
entity_category=EntityCategory.DIAGNOSTIC,
178177
device_class=BinarySensorDeviceClass.BATTERY,
179178
)

custom_components/battery_notes/button.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,6 @@ async def async_registry_updated(event: Event) -> None:
147147
unique_id_suffix="_battery_replaced_button",
148148
key="battery_replaced",
149149
translation_key="battery_replaced",
150-
icon="mdi:battery-sync",
151150
entity_category=EntityCategory.DIAGNOSTIC,
152151
entity_registry_enabled_default=enable_replaced,
153152
)

custom_components/battery_notes/common.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""Common functions for battery_notes."""
22

3+
from homeassistant.helpers.device_registry import DeviceEntry
4+
35

46
def validate_is_float(num):
57
"""Validate value is a float."""
@@ -10,3 +12,10 @@ def validate_is_float(num):
1012
except ValueError:
1113
return False
1214
return False
15+
16+
def get_device_model_id(device_entry: DeviceEntry) -> str | None:
17+
"""Get the device model if available."""
18+
if hasattr(device_entry, "model_id"):
19+
return device_entry.model_id
20+
else:
21+
return None

custom_components/battery_notes/config_flow.py

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from homeassistant.helpers.typing import DiscoveryInfoType
2424
from homeassistant.util import dt as dt_util
2525

26+
from .common import get_device_model_id
2627
from .const import (
2728
CONF_BATTERY_LOW_TEMPLATE,
2829
CONF_BATTERY_LOW_THRESHOLD,
@@ -31,6 +32,7 @@
3132
CONF_DEVICE_NAME,
3233
CONF_MANUFACTURER,
3334
CONF_MODEL,
35+
CONF_MODEL_ID,
3436
CONF_SHOW_ALL_DEVICES,
3537
CONF_SOURCE_ENTITY_ID,
3638
DATA_LIBRARY_UPDATER,
@@ -131,6 +133,7 @@ async def async_step_integration_discovery(
131133
"name": discovery_info[CONF_DEVICE_NAME],
132134
"manufacturer": discovery_info[CONF_MANUFACTURER],
133135
"model": discovery_info[CONF_MODEL],
136+
"model_id": discovery_info[CONF_MODEL_ID],
134137
}
135138

136139
return await self.async_step_device(discovery_info)
@@ -149,6 +152,8 @@ async def async_step_device(
149152
) -> config_entries.FlowResult:
150153
"""Handle a flow for a device or discovery."""
151154
errors: dict[str, str] = {}
155+
device_battery_details = None
156+
152157
if user_input is not None:
153158
self.data = user_input
154159

@@ -167,14 +172,15 @@ async def async_step_device(
167172
device_entry = device_registry.async_get(device_id)
168173

169174
_LOGGER.debug(
170-
"Looking up device %s %s %s",
175+
"Looking up device %s %s %s %s",
171176
device_entry.manufacturer,
172177
device_entry.model,
178+
get_device_model_id(device_entry) or "",
173179
device_entry.hw_version,
174180
)
175181

176182
model_info = ModelInfo(
177-
device_entry.manufacturer, device_entry.model, device_entry.hw_version
183+
device_entry.manufacturer, device_entry.model, get_device_model_id(device_entry), device_entry.hw_version
178184
)
179185

180186
library = await Library.factory(self.hass)
@@ -188,9 +194,10 @@ async def async_step_device(
188194

189195
if device_battery_details and not device_battery_details.is_manual:
190196
_LOGGER.debug(
191-
"Found device %s %s %s",
197+
"Found device %s %s %s %s",
192198
device_entry.manufacturer,
193199
device_entry.model,
200+
get_device_model_id(device_entry) or "",
194201
device_entry.hw_version,
195202
)
196203
self.data[CONF_BATTERY_TYPE] = device_battery_details.battery_type
@@ -199,6 +206,9 @@ async def async_step_device(
199206
device_battery_details.battery_quantity
200207
)
201208

209+
if device_battery_details and device_battery_details.is_manual:
210+
return await self.async_step_manual()
211+
202212
return await self.async_step_battery()
203213

204214
schema = DEVICE_SCHEMA
@@ -221,6 +231,8 @@ async def async_step_entity(
221231
) -> config_entries.FlowResult:
222232
"""Handle a flow for a device or discovery."""
223233
errors: dict[str, str] = {}
234+
device_battery_details = None
235+
224236
if user_input is not None:
225237
self.data = user_input
226238

@@ -249,15 +261,17 @@ async def async_step_entity(
249261
device_entry = device_registry.async_get(entity_entry.device_id)
250262

251263
_LOGGER.debug(
252-
"Looking up device %s %s %s",
264+
"Looking up device %s %s %s %s",
253265
device_entry.manufacturer,
254266
device_entry.model,
267+
get_device_model_id(device_entry) or "",
255268
device_entry.hw_version,
256269
)
257270

258271
model_info = ModelInfo(
259272
device_entry.manufacturer,
260273
device_entry.model,
274+
get_device_model_id(device_entry),
261275
device_entry.hw_version,
262276
)
263277

@@ -269,9 +283,10 @@ async def async_step_entity(
269283

270284
if device_battery_details and not device_battery_details.is_manual:
271285
_LOGGER.debug(
272-
"Found device %s %s %s",
286+
"Found device %s %s %s %s",
273287
device_entry.manufacturer,
274288
device_entry.model,
289+
get_device_model_id(device_entry) or "",
275290
device_entry.hw_version,
276291
)
277292
self.data[CONF_BATTERY_TYPE] = (
@@ -282,6 +297,8 @@ async def async_step_entity(
282297
device_battery_details.battery_quantity
283298
)
284299

300+
if device_battery_details and device_battery_details.is_manual:
301+
return await self.async_step_manual()
285302
return await self.async_step_battery()
286303
else:
287304
# No entity_registry entry, must be a config.yaml entity which we can't support
@@ -296,6 +313,20 @@ async def async_step_entity(
296313
last_step=False,
297314
)
298315

316+
async def async_step_manual(self, user_input: dict[str, Any] | None = None):
317+
"""Second step in config flow to add the battery type."""
318+
errors: dict[str, str] = {}
319+
if user_input is not None:
320+
return await self.async_step_battery()
321+
322+
return self.async_show_form(
323+
step_id="manual",
324+
data_schema=None,
325+
last_step=False,
326+
errors=errors,
327+
)
328+
329+
299330
async def async_step_battery(self, user_input: dict[str, Any] | None = None):
300331
"""Second step in config flow to add the battery type."""
301332
errors: dict[str, str] = {}

custom_components/battery_notes/const.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@
44
from logging import Logger, getLogger
55
from pathlib import Path
66
from typing import Final
7-
import voluptuous as vol
87

8+
import voluptuous as vol
99
from homeassistant.const import Platform
10-
1110
from homeassistant.helpers import config_validation as cv
1211

1312
LOGGER: Logger = getLogger(__package__)
1413

15-
MIN_HA_VERSION = "2024.4"
14+
MIN_HA_VERSION = "2024.5"
1615

1716
manifestfile = Path(__file__).parent / "manifest.json"
1817
with open(file=manifestfile, encoding="UTF-8") as json_file:
@@ -40,6 +39,7 @@
4039
CONF_ENABLE_AUTODISCOVERY = "enable_autodiscovery"
4140
CONF_USER_LIBRARY = "user_library"
4241
CONF_MODEL = "model"
42+
CONF_MODEL_ID = "model_id"
4343
CONF_MANUFACTURER = "manufacturer"
4444
CONF_DEVICE_NAME = "device_name"
4545
CONF_LIBRARY_URL = "https://raw.githubusercontent.com/andrew-codechimp/HA-Battery-Notes/main/custom_components/battery_notes/data/library.json" # pylint: disable=line-too-long

0 commit comments

Comments
 (0)