Skip to content

Commit b0d88f4

Browse files
authored
Add BLE model ID mapping for Gen3/Gen4 device identification (#989)
1 parent 55ffc10 commit b0d88f4

File tree

3 files changed

+109
-1
lines changed

3 files changed

+109
-1
lines changed

aioshelly/ble/__init__.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
from __future__ import annotations
44

55
import logging
6+
from typing import TYPE_CHECKING
67

78
from habluetooth import BluetoothScanningMode, HaBluetoothConnector
89

10+
from ..const import MODEL_ID_TO_DEVICE
911
from ..exceptions import RpcCallError
1012
from ..rpc_device import RpcDevice
1113
from .backend.scanner import ShellyBLEScanner
@@ -17,6 +19,9 @@
1719
VAR_VERSION,
1820
)
1921

22+
if TYPE_CHECKING:
23+
from ..const import ShellyDevice
24+
2025
LOGGER = logging.getLogger(__name__)
2126

2227

@@ -109,3 +114,31 @@ async def async_ensure_ble_enabled(device: RpcDevice) -> bool:
109114
LOGGER.info("BLE enabled, restarting device %s:%s", device.ip_address, device.port)
110115
await device.trigger_reboot(3500)
111116
return True
117+
118+
119+
def get_device_from_model_id(model_id: int) -> ShellyDevice | None:
120+
"""Get the ShellyDevice from a BLE model ID.
121+
122+
Args:
123+
model_id: Model ID from BLE manufacturer data
124+
125+
Returns:
126+
ShellyDevice object or None if not found
127+
128+
"""
129+
return MODEL_ID_TO_DEVICE.get(model_id)
130+
131+
132+
def get_name_from_model_id(model_id: int) -> str | None:
133+
"""Get the device name from a BLE model ID.
134+
135+
Args:
136+
model_id: Model ID from BLE manufacturer data
137+
138+
Returns:
139+
Device name or None if not found
140+
141+
"""
142+
if device := get_device_from_model_id(model_id):
143+
return device.name
144+
return None

aioshelly/const.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ class ShellyDevice:
200200
min_fw_date: int
201201
gen: int
202202
supported: bool
203+
model_id: int | None = None # BLE manufacturer data model ID (Gen4+ devices)
203204

204205

205206
DEVICES = {
@@ -510,6 +511,7 @@ class ShellyDevice:
510511
min_fw_date=GEN3_GATEWAY_MIN_FIRMWARE_DATE,
511512
gen=GEN3,
512513
supported=True,
514+
model_id=0x1817,
513515
),
514516
MODEL_PLUS_1: ShellyDevice(
515517
model=MODEL_PLUS_1,
@@ -888,83 +890,95 @@ class ShellyDevice:
888890
min_fw_date=GEN3_MIN_FIRMWARE_DATE,
889891
gen=GEN3,
890892
supported=True,
893+
model_id=0x1018,
891894
),
892895
MODEL_1L_G3: ShellyDevice(
893896
model=MODEL_1L_G3,
894897
name="Shelly 1L Gen3",
895898
min_fw_date=GEN3_MIN_FIRMWARE_DATE,
896899
gen=GEN3,
897900
supported=True,
901+
model_id=0x1014,
898902
),
899903
MODEL_1_MINI_G3: ShellyDevice(
900904
model=MODEL_1_MINI_G3,
901905
name="Shelly 1 Mini Gen3",
902906
min_fw_date=GEN3_MIN_FIRMWARE_DATE,
903907
gen=GEN3,
904908
supported=True,
909+
model_id=0x1015,
905910
),
906911
MODEL_1PM_G3: ShellyDevice(
907912
model=MODEL_1PM_G3,
908913
name="Shelly 1PM Gen3",
909914
min_fw_date=GEN3_MIN_FIRMWARE_DATE,
910915
gen=GEN3,
911916
supported=True,
917+
model_id=0x1019,
912918
),
913919
MODEL_1PM_MINI_G3: ShellyDevice(
914920
model=MODEL_1PM_MINI_G3,
915921
name="Shelly 1PM Mini Gen3",
916922
min_fw_date=GEN3_MIN_FIRMWARE_DATE,
917923
gen=GEN3,
918924
supported=True,
925+
model_id=0x1016,
919926
),
920927
MODEL_2L_G3: ShellyDevice(
921928
model=MODEL_2L_G3,
922929
name="Shelly 2L Gen3",
923930
min_fw_date=GEN3_MIN_FIRMWARE_DATE,
924931
gen=GEN3,
925932
supported=True,
933+
model_id=0x1013,
926934
),
927935
MODEL_2PM_G3: ShellyDevice(
928936
model=MODEL_2PM_G3,
929937
name="Shelly 2PM Gen3",
930938
min_fw_date=GEN3_MIN_FIRMWARE_DATE,
931939
gen=GEN3,
932940
supported=True,
941+
model_id=0x1005,
933942
),
934943
MODEL_3EM_63_G3: ShellyDevice(
935944
model=MODEL_3EM_63_G3,
936945
name="Shelly 3EM-63 Gen3",
937946
min_fw_date=GEN3_MIN_FIRMWARE_DATE,
938947
gen=GEN3,
939948
supported=True,
949+
model_id=0x1026,
940950
),
941951
MODEL_AZ_PLUG: ShellyDevice(
942952
model=MODEL_AZ_PLUG,
943953
name="Shelly AZ Plug",
944954
min_fw_date=GEN3_MIN_FIRMWARE_DATE,
945955
gen=GEN3,
946956
supported=True,
957+
model_id=0x1850,
947958
),
948959
MODEL_DALI_DIMMER_G3: ShellyDevice(
949960
model=MODEL_DALI_DIMMER_G3,
950961
name="Shelly DALI Dimmer Gen3",
951962
min_fw_date=GEN3_MIN_FIRMWARE_DATE,
952963
gen=GEN3,
953964
supported=True,
965+
model_id=0x1071,
954966
),
955967
MODEL_DIMMER_10V_G3: ShellyDevice(
956968
model=MODEL_DIMMER_10V_G3,
957969
name="Shelly Dimmer 0/1-10V PM Gen3",
958970
min_fw_date=GEN3_MIN_FIRMWARE_DATE,
959971
gen=GEN3,
960972
supported=True,
973+
model_id=0x1072,
961974
),
962975
MODEL_DIMMER_G3: ShellyDevice(
963976
model=MODEL_DIMMER_G3,
964977
name="Shelly Dimmer Gen3",
965978
min_fw_date=GEN3_MIN_FIRMWARE_DATE,
966979
gen=GEN3,
967980
supported=True,
981+
model_id=0x1073,
968982
),
969983
MODEL_DUO_BULB_G3: ShellyDevice(
970984
model=MODEL_DUO_BULB_G3,
@@ -979,20 +993,23 @@ class ShellyDevice:
979993
min_fw_date=GEN3_MIN_FIRMWARE_DATE,
980994
gen=GEN3,
981995
supported=True,
996+
model_id=0x1027,
982997
),
983998
MODEL_HT_G3: ShellyDevice(
984999
model=MODEL_HT_G3,
9851000
name="Shelly H&T Gen3",
9861001
min_fw_date=GEN3_MIN_FIRMWARE_DATE,
9871002
gen=GEN3,
9881003
supported=True,
1004+
model_id=0x1809,
9891005
),
9901006
MODEL_I4_G3: ShellyDevice(
9911007
model=MODEL_I4_G3,
9921008
name="Shelly I4 Gen3",
9931009
min_fw_date=GEN3_MIN_FIRMWARE_DATE,
9941010
gen=GEN3,
9951011
supported=True,
1012+
model_id=0x1812,
9961013
),
9971014
MODEL_MULTICOLOR_BULB_G3: ShellyDevice(
9981015
model=MODEL_MULTICOLOR_BULB_G3,
@@ -1007,6 +1024,7 @@ class ShellyDevice:
10071024
min_fw_date=GEN3_MIN_FIRMWARE_DATE,
10081025
gen=GEN3,
10091026
supported=True,
1027+
model_id=0x1853,
10101028
),
10111029
MODEL_PILL_G3: ShellyDevice(
10121030
model=MODEL_PILL_G3,
@@ -1021,13 +1039,15 @@ class ShellyDevice:
10211039
min_fw_date=GEN3_MIN_FIRMWARE_DATE,
10221040
gen=GEN3,
10231041
supported=True,
1042+
model_id=0x1023,
10241043
),
10251044
MODEL_PLUG_S_G3: ShellyDevice(
10261045
model=MODEL_PLUG_S_G3,
10271046
name="Shelly Plug S Gen3",
10281047
min_fw_date=GEN3_MIN_FIRMWARE_DATE,
10291048
gen=GEN3,
10301049
supported=True,
1050+
model_id=0x1805,
10311051
),
10321052
MODEL_X_MOD1: ShellyDevice(
10331053
model=MODEL_X_MOD1,
@@ -1042,34 +1062,39 @@ class ShellyDevice:
10421062
min_fw_date=GEN4_MIN_FIRMWARE_DATE,
10431063
gen=GEN4,
10441064
supported=True,
1065+
model_id=0x1028,
10451066
),
10461067
MODEL_1_MINI_G4: ShellyDevice(
10471068
model=MODEL_1_MINI_G4,
10481069
name="Shelly 1 Mini Gen4",
10491070
min_fw_date=GEN4_MIN_FIRMWARE_DATE,
10501071
gen=GEN4,
10511072
supported=True,
1073+
model_id=0x1030,
10521074
),
10531075
MODEL_1PM_G4: ShellyDevice(
10541076
model=MODEL_1PM_G4,
10551077
name="Shelly 1PM Gen4",
10561078
min_fw_date=GEN4_MIN_FIRMWARE_DATE,
10571079
gen=GEN4,
10581080
supported=True,
1081+
model_id=0x1029,
10591082
),
10601083
MODEL_1PM_MINI_G4: ShellyDevice(
10611084
model=MODEL_1PM_MINI_G4,
10621085
name="Shelly 1PM Mini Gen4",
10631086
min_fw_date=GEN4_MIN_FIRMWARE_DATE,
10641087
gen=GEN4,
10651088
supported=True,
1089+
model_id=0x1031,
10661090
),
10671091
MODEL_2PM_G4: ShellyDevice(
10681092
model=MODEL_2PM_G4,
10691093
name="Shelly 2PM Gen4",
10701094
min_fw_date=GEN4_MIN_FIRMWARE_DATE,
10711095
gen=GEN4,
10721096
supported=True,
1097+
model_id=0x1032,
10731098
),
10741099
MODEL_CURY_G4: ShellyDevice(
10751100
model=MODEL_CURY_G4,
@@ -1084,6 +1109,7 @@ class ShellyDevice:
10841109
min_fw_date=GEN4_MIN_FIRMWARE_DATE,
10851110
gen=GEN4,
10861111
supported=True,
1112+
model_id=0x1075,
10871113
),
10881114
MODEL_I4_G4: ShellyDevice(
10891115
model=MODEL_I4_G4,
@@ -1098,20 +1124,23 @@ class ShellyDevice:
10981124
min_fw_date=GEN4_MIN_FIRMWARE_DATE,
10991125
gen=GEN4,
11001126
supported=True,
1127+
model_id=0x1822,
11011128
),
11021129
MODEL_PLUG_US_G4: ShellyDevice(
11031130
model=MODEL_PLUG_US_G4,
11041131
name="Shelly Plug US Gen4",
11051132
min_fw_date=GEN2_MIN_FIRMWARE_DATE,
11061133
gen=GEN4,
11071134
supported=True,
1135+
model_id=0x1852,
11081136
),
11091137
MODEL_POWER_STRIP_G4: ShellyDevice(
11101138
model=MODEL_POWER_STRIP_G4,
11111139
name="Shelly Power Strip Gen4",
11121140
min_fw_date=GEN4_MIN_FIRMWARE_DATE,
11131141
gen=GEN4,
11141142
supported=True,
1143+
model_id=0x1851,
11151144
),
11161145
MODEL_PRESENCE_G4: ShellyDevice(
11171146
model=MODEL_PRESENCE_G4,
@@ -1187,3 +1216,11 @@ class UndefinedType(Enum):
11871216

11881217
# value confirmed by Shelly team
11891218
BLU_TRV_TIMEOUT = 60
1219+
1220+
# BLE manufacturer data model ID to ShellyDevice mapping
1221+
# Built from DEVICES dict at module load time
1222+
MODEL_ID_TO_DEVICE: dict[int, ShellyDevice] = {
1223+
device.model_id: device
1224+
for device in DEVICES.values()
1225+
if device.model_id is not None
1226+
}

tests/ble/test_init.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
import pytest
66
from habluetooth import BluetoothScanningMode
77

8-
from aioshelly.ble import create_scanner
8+
from aioshelly.ble import (
9+
create_scanner,
10+
get_device_from_model_id,
11+
get_name_from_model_id,
12+
)
913

1014

1115
@pytest.mark.asyncio
@@ -35,3 +39,37 @@ async def test_create_scanner_back_compat() -> None:
3539
scanner = create_scanner("AA:BB:CC:DD:EE:FF", "shelly")
3640
assert scanner.requested_mode is None
3741
assert scanner.current_mode is None
42+
43+
44+
def test_get_device_from_model_id() -> None:
45+
"""Test get_device_from_model_id helper function."""
46+
# Test with valid model ID (Shelly 1 Mini Gen4)
47+
device = get_device_from_model_id(0x1030)
48+
assert device is not None
49+
assert device.name == "Shelly 1 Mini Gen4"
50+
assert device.model == "S4SW-001X8EU"
51+
52+
# Test with another valid model ID (Shelly 2PM Gen3)
53+
device = get_device_from_model_id(0x1005)
54+
assert device is not None
55+
assert device.name == "Shelly 2PM Gen3"
56+
assert device.model == "S3SW-002P16EU"
57+
58+
# Test with invalid model ID
59+
device = get_device_from_model_id(0x9999)
60+
assert device is None
61+
62+
63+
def test_get_name_from_model_id() -> None:
64+
"""Test get_name_from_model_id helper function."""
65+
# Test with valid model ID (Shelly 1PM Gen4)
66+
name = get_name_from_model_id(0x1029)
67+
assert name == "Shelly 1PM Gen4"
68+
69+
# Test with another valid model ID (Shelly Flood Gen4)
70+
name = get_name_from_model_id(0x1822)
71+
assert name == "Shelly Flood Gen4"
72+
73+
# Test with invalid model ID
74+
name = get_name_from_model_id(0x9999)
75+
assert name is None

0 commit comments

Comments
 (0)