From bd57797df4360618269a165b43db3a1f64ff5a88 Mon Sep 17 00:00:00 2001 From: zhouteng Date: Sun, 21 Jul 2024 16:21:48 +0800 Subject: [PATCH 1/2] Add initial support for Senssun scale --- .../ble_monitor/ble_parser/__init__.py | 5 ++ .../ble_monitor/ble_parser/senssun.py | 50 ++++++++++++++++++ custom_components/ble_monitor/const.py | 2 + .../ble_monitor/test/test_senssun_parser.py | 28 ++++++++++ docs/_devices/Senssun_Smart_Scale.md | 17 ++++++ docs/assets/images/IF_B7.jpg | Bin 0 -> 6126 bytes docs/config_params.md | 2 +- info.md | 1 + 8 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 custom_components/ble_monitor/ble_parser/senssun.py create mode 100644 custom_components/ble_monitor/test/test_senssun_parser.py create mode 100644 docs/_devices/Senssun_Smart_Scale.md create mode 100644 docs/assets/images/IF_B7.jpg diff --git a/custom_components/ble_monitor/ble_parser/__init__.py b/custom_components/ble_monitor/ble_parser/__init__.py index 5e3a11599..944de8014 100644 --- a/custom_components/ble_monitor/ble_parser/__init__.py +++ b/custom_components/ble_monitor/ble_parser/__init__.py @@ -46,6 +46,7 @@ from .tilt import parse_tilt from .xiaogui import parse_xiaogui from .xiaomi import parse_xiaomi +from .senssun import parse_senssun _LOGGER = logging.getLogger(__name__) @@ -422,6 +423,10 @@ def parse_advertisement( # Laica sensor_data = parse_laica(self, man_spec_data, mac) break + elif comp_id == 0x0100 and data_len == 0x14: + # Senssun IF_B7 + sensor_data = parse_senssun(self, man_spec_data, mac) + break # Filter on part of the UUID16 elif man_spec_data[2] == 0xC0 and data_len == 0x10: diff --git a/custom_components/ble_monitor/ble_parser/senssun.py b/custom_components/ble_monitor/ble_parser/senssun.py new file mode 100644 index 000000000..5d18e3396 --- /dev/null +++ b/custom_components/ble_monitor/ble_parser/senssun.py @@ -0,0 +1,50 @@ +"""Parser for Senssun Scale BLE advertisements""" + +import logging +from struct import unpack + +from .helpers import to_mac, to_unformatted_mac + +_LOGGER = logging.getLogger(__name__) + + +def read_stable(ctr1): + """Parse Stable""" + return int((ctr1 & 0xA0) == 0xA0) + +def parse_senssun(self, data: bytes, mac: str): + """Parser for Senssun Scales.""" + xvalue = data[13:19] + + (division, weight, impedance, ctr1) = unpack(">bhhb", xvalue) + result = { + "type": "Senssun Smart Scale", + "firmware": "Senssun", + "mac": to_unformatted_mac(mac), + "data": True, + "impedance": impedance, + "weight": weight / 100.0, + "stabilized": read_stable(ctr1), + } + + # Check for duplicate messages + packet_id = xvalue.hex() + try: + prev_packet = self.lpacket_ids[mac] + except KeyError: + # start with empty first packet + prev_packet = None + if prev_packet == packet_id: + # only process new messages + if self.filter_duplicates is True: + return None + self.lpacket_ids[mac] = packet_id + if prev_packet is None: + if self.filter_duplicates is True: + # ignore first message after a restart + return None + + result.update({ + "packet": packet_id, + }) + return result diff --git a/custom_components/ble_monitor/const.py b/custom_components/ble_monitor/const.py index 9fa8e032e..522435ff8 100755 --- a/custom_components/ble_monitor/const.py +++ b/custom_components/ble_monitor/const.py @@ -1975,6 +1975,7 @@ class BLEMonitorBinarySensorEntityDescription( 'K6 Sensor Beacon' : [["temperature", "humidity", "acceleration", "voltage", "battery", "rssi"], [], []], 'DSL-C08' : [["battery", "rssi", "voltage"], [], ["lock", "childlock"]], 'SmartDry cloth dryer' : [["temperature", "humidity", "voltage", "battery", "shake", "rssi"], [], ["switch"]], + 'Senssun Smart Scale' : [["rssi"], ["weight", "impedance"], []], } # Sensor manufacturer dictionary @@ -2111,6 +2112,7 @@ class BLEMonitorBinarySensorEntityDescription( 'Laica Smart Scale' : 'Laica', 'K6 Sensor Beacon' : 'KKM', 'SmartDry cloth dryer' : 'SmartDry', + 'Senssun Smart Scale' : 'Senssun', } diff --git a/custom_components/ble_monitor/test/test_senssun_parser.py b/custom_components/ble_monitor/test/test_senssun_parser.py new file mode 100644 index 000000000..e7aad570d --- /dev/null +++ b/custom_components/ble_monitor/test/test_senssun_parser.py @@ -0,0 +1,28 @@ +"""The tests for the Senssun ble_parser.""" + +import datetime + +from ble_monitor.ble_parser import BleParser + + +class TestSenssun: + """Tests for the Senssun parser""" + + def test_Senssun_IF_B7(self): + """Test Senssun parser for IF_B7.""" + data_string = "043E2B0201030033B3C1937A181F020106060949465F423714FF0001020311187A93C1B333021A4500B4A1AB61C5" + data = bytes(bytearray.fromhex(data_string)) + + # pylint: disable=unused-variable + ble_parser = BleParser() + sensor_msg, tracker_msg = ble_parser.parse_raw_data(data) + + assert sensor_msg["firmware"] == "Senssun" + assert sensor_msg["type"] == "Senssun Smart Scale" + assert sensor_msg["mac"] == "187A93C1B333" + assert sensor_msg["packet"] == "021a4500b4a1" + assert sensor_msg["data"] + assert sensor_msg["weight"] == 67.25 + assert sensor_msg["stabilized"] == 1 + assert sensor_msg["impedance"] == 180 + assert sensor_msg["rssi"] == -59 diff --git a/docs/_devices/Senssun_Smart_Scale.md b/docs/_devices/Senssun_Smart_Scale.md new file mode 100644 index 000000000..12e57b4bf --- /dev/null +++ b/docs/_devices/Senssun_Smart_Scale.md @@ -0,0 +1,17 @@ +--- +manufacturer: Senssun +name: Senssun Smart Scale +model: IF_B7 +image: IF_B7.jpg +physical_description: +broadcasted_properties: + - weight + - impedance + - rssi +broadcasted_property_notes: +broadcast_rate: +active_scan: +encryption_key: +custom_firmware: +notes: +--- diff --git a/docs/assets/images/IF_B7.jpg b/docs/assets/images/IF_B7.jpg new file mode 100644 index 0000000000000000000000000000000000000000..09dd8af5e5a7a78d750ae41de0bae1f1a86674a2 GIT binary patch literal 6126 zcmcIoS5ySnX_lktXXHBb#5nb7XVahN~%f#AP@*p`MUtOGk~W6 zd_25=*k2I*H9`_XLIMIpGGbyP5^^$fau68^1g4;-0#iUJKp-j@6@-Qs3WbtW(%pg4 z-l3+2(*8pPi2rvF0U;?NAt@~w1g8Ce z<^Bu!Ks*9MB4QHKzitI801zLK0G|+_l!y?Y@NWzdfQL^2Ata(^6_nE@reU-65DJN- zWtV@JQPDQKPXZNws<%kWVdWVQQ#jDKew$hOSB3ts{y*`r900t(=OHBe>$?y6n;Q=g z4@iLjKScoXAgqG;)NBNDx|TFT9<=he(*Sb(zfll;2tXEat*4+spz!2BOp5Z((9*;b zhx@JmAL%kj4eWTi6Ib6>RH?}(0rr^oK{PktvuiL=n5>CNOT;&abJwDL#fNvcw8&t&;;&1s=A@t%2{6S7LXUyE^X#KU*mzGrTQ?tndPIua0`GHbSVdv zm7fxNm-*Yo09z@@UYjo-e5+lnd%QHk$A^9#D4qA^d{Y)6Gm%-8@8ea5lvebRod`;~ z1rTjlaBndXz!TTi4dEuwQ*C*N!wsYd-aglDRcaTu6Ze5%;VrN9Sq`qwcZVT^&6q!J zHfs%%kAlPOXPft~^Sp0x%dgYI4? zaFVq$GvAM1Jo)!yDAUx(dY(G0_}53z|KvEl$eY7%SiJoqv@3zk(SHaGwXzOMyY@6i;PjG4?|=YYP@U<3uYW0@rF;v zB~{KmjMgg3U1&AJgR@-`6f0>3z3quKza|<_pnLP@?qKlQf#K+w9H>SJhP}yZ(vmr9 z?ETpo6$x#$-J1H~H=D3kpf(2jff$C7soBnc6DhW^a*w;`x3KfUXp{I|$+bSy6+a`% zoC9;aD@-@D`<@8xGXebVG|*thou0=gBQj>D_k0xIFXx<<5ncoy z1{_?JF~&%3ci`o}cF8S_5?9nx|9Yde!>&E!bIkiBeA{ozY2Ud;J5-@d?hXt#iFCco z6xPb0wR~`4oi}uN9-PGg!hkY*Aizhk6(v@N-8UUlAJ|LLVnT8jhSeTuTz_AqXxU=C zIM}Km8zZL5pmXX7oY^0$x=S-1 zidataf^Gp9(Vxu9=Rr@bUl%F9ozY#rFj4~e4EQId1chjFLey{$5C>_aCFsBl&05^Z z|>wdNlR(K9*w7C1#3rpzo$kG!ygrf&sZXbYyh< zWJYc;KGN_h2I5JmGT_zjELE(deuP}hgL#rk7BU0GCkZ8%5Tz`R$$}b;uKBDpP7ix3 zv=u{HzBAFLPLUk~SE6z9BwY4{847EWfBO6tSGa{{-nstPozPU)WgL-G=C$4CLh*#I zxU_V~nte$+A`suX6KJhayx$9v;grlfE=QqXo1Jv@9dHd1eTwHb9?5S)i1?t%U0159f$XtsKZMdq zHkX~)Vb(_UBjQH=gN4?V6i)Tb_ueOju5&jv{rdS_uQyz$&VDVd%dlIXVhFz{bSrt? zxlGZayt0YH*JMXDxV122PsYfywlm==y9s4(qWAeSC@QcwtpZ~rO2X9gqm|IvjVn#u zDlndEa+=h`7XWd{-zwhk{Rh`CjJ9Uhl4Nh@K#w z!|OEbDZ+&)p$bxJ_p%b-O%{DwJ$G?qsj(*r~&-S(z=){4}TA@wP$d<46tFHl&o``{*Oqgc*Z^7q%H5okr{xb1L-$ zBF%+#wxM?ZT z!h7zmH8wUfh)v7jQs^I(Yh_)0agV?2%RnGWA3npdWoak6ywGSE-(392Ui9WAq z@yr8Hkj)}>_7P5Hy}Cnn955OiM{b%Iv>)fR!BKM7eco@fE%zgx-&(Czn__@#24I5+ zeJZB+&}y;b;VD5fcIij@#hG9BD8ru6EA2=Jn^TH78>%a++X+ia*F8p7sY?0n5+~v0 zvdRX28Yr>7QzZ4C`?cN_kK0cPko+cWlc09ms}5SV&WhV6sA9&;{8M#QQMYi#Y!F&>IAXr%Y0);Zf<8Wmead^0)HD;OW1ZBS_jne4?8z z;sH5q6D3GkIZ=kQJ4p``m z$8c87;k%thpKX$KH(aHB4X|Kho$;Sl$-V?$jYoy>*~1c(keI@POfDCM{t%euDkUHO z3kA!?Y<|&I)q^4=Pn}1aq){U=4V-?fn8JFK0CX9bEtf^2hh>cr4Qc?Z*SGA&fW9ET z3T`~Em6A?J6Z*wDakF|q6Vqer>h@TpG#8nC5jA-r>|U&7s`to@wC;TE#tL^08fZ>3 zw?%rc&S%!3$7c??8c4=M=Pbhn@lTuj5Swt#)j72PXo_178DH-OWN2B#>VGYn{-%}aGuIx zPV^-SXbl=nN<0|u)js1}%eJee6#~Ca76K{8s;G{--4C?d(Ve%-wjDT;V{Fn)j5wFh z%&I3L6798EbT4;wP_Szy1z8NXaij5WR_Ij;QHl^4^GQWv*xHroLOJu8c>4R@azkj1 zd@?D=%eE4(g{o}2noliMZ}gwwfbM~%l`+Y}w$??1;$IUH&!j4G5lm)xQi9%G_O3k_ zlk&aqbKb%`_~v)tSjVB~a>i9j4^0o6Iq*53FlqBIp5?jzpl{jx7Z=(FnMyDT4E1BA zb0yjvBP9A@x=*=b-|U1osfx=@KpB8__oWK_prcA9v(pz5%8yv1UmJVYT`)8?%(fHd z<<*3mY15)Xx^ndj_Iq*tu6GIaIo3hQ@91cB^>j>>(y>?~y&mZc0FKC`9xs1svJR@1 z{k#UR{^A_Qy<24KEWi>ol#@{$Ntmv-yiIA<4+@(4rchgUpyC#b-8 zU+i5tB93WUoG`ssJAkQOs6_3J(AW^X!>@R*`oMn58cT3Ma^HU+lqfA$wNe3R6q7`1E9?R1h_u;qEa~On zBxG(WU9u9!$gulZE)}t!w@Rdz40=eX!;8_LL9zXnCTN`B+WAr;E%vf?3) zuj4Bi1KcVJR-c?%YL*?m1uO|BS>#|+6NFZkxS*f`VXO1BY>Q*wBvp57qlL{?e=vtq zm*XK|&Y%2Sb(s@W?mAg)b#sVu+Y6I zbHCC#Xff?ly)WqNSj5+#Ujzp5nB|*uDtc}KoK@4l^>Cib`Dx>-<{81Or-XfzMXVgMTKZjwq!g{tEEI#(z1JW!^ z8KHP`r2c+^Udk-tmGVRpXcNy`KjD`o>}|qbnrPW1UqGiGf>vv{Qx~N|I2tXeC0-9} z=$PUBp}aL-{bpY#l3LB!3uYF4Df@BZAxX1cSn8ks z?!KU(%%fhTPu42}Yt$nZGi^gXoJxtQw++JlK^Y!VOjjK5v2Y9GUL?DeR1!~^=x(=0 z;1}x&zX4O!nDRYgb&jYt#_O22m&jm_k%c&s*e6|^V)TkFM|-9L=`gXHre7k8ikx>w z&U0Vz-2!wg*WEjqP5G0gnm1CkvyEoX-?Ll6XDoM(;!==E@x1npLP74(a;oxatZ&$g z#{B0kn z*`1Nm*Kd%Hhq*c#ecnp+I;Z`t6TF8h&B{Jp`^FP9jKOe&@bE?s1qE7@F1vQdpO@`l z&6I+nd7FRVx&4`2&@$KDxZj0+Qdd&aAvv%8md(TAfqIh*r z?`A8{o-XEjQOsH`J_=MQ;WcLtMkrCzOe?s0ksgi9ii9vykM>x{8XizEl9)nVLU@d$ z!aC*6mY1l-8|^?c=i< z!CC**F9tu!BnpIPm(Z`cv2(c&PbdRDYj=os!0v2a3oId$vO{M!M1BL*7!O96)XT)< zkA_7&0ex3+U#g97hi{Pu?-!KWzsl|YF?Ba?*~#_ntJm`H_sccww$H){1ga71A@tO@ zd%7vhL#-&;fGaKBRj4x$TnP#n7jEJ2MHYFd-}c!S@5|E42Ugz1Z$8rGs{qHII5ZGM zhd+NXV#BCSGiYclHs$JDgd)WV7^Xr!ib5u!d%0oO?Ab9G{I#s#k4k*{v-3W^jD8}( za~Nh&)KU3bPL0X*&9h*MC#UKUUiYWBMA`ZI%cKTl;=@83oFLODZ&7}~|adH+6yutx5S*<@0h)@no^v0_i+x@P>&-{=GwQV(W%o8@oxv@5{ z%*Hoo1r+1|!9{}nqH7HkKc`A_7_Im@DUbP=U+SBjOfFxFZ(DQWv>A8$#U&r)rG_L- z^s?uq*@n6^o-)(@X>Z3?#I>c*TQoElpR3sJKA7V3Q5A*!th~HlU#M~r>hd1Y9A}Lr z2$^Nl34DolnLFi|J{-z}CxGS_QxK-wip-54kPLovLqL1(s%)X&SYWRLE{21xD&*>g%kj55 zw5|;!_VeHzxgV2o^T(UHmTCBFQBy6_Ni;<3K<1%{5}!oP3{SQ4EdbJRnz;GI5Q)$~ zu$Ej+>UA>_5ZGo;AVoD~<)M>VHZiTEINr5L5O*e)vmR~5#`oW!I&x)Q?p;>ZLaj$u zJJXBDrady8TF?; z)|eOV+|4>Y10!*{hACaMa?I8{&z0HjqASHhQIsO4VoOEWOhhaZV^$SgS*eKxEHw1a zxQn{*)Av$wJwLbOc1=WViPfJ!h&u?}SY+4yDorwH-TUUp;24h+{_C>7oVr^;4s%P` z&a~B?5;*lBqJ}r`B4fAeYhViI^6e@SKisPH7we;A_k_7x&9tMF@YUK@&jxI*2&Z{Y z?)VP)2(fKjQI94RxRkd=p$m>Kfr6Gs1D4%mMEHQajyD&KkFytRGgpTKBBX;|P z_^$uy&<$r&7VjhBef;AO!&Z~kr?}*A_eNym9vexodWz@SZ+m-^bG|KNEVTdrup9?! zI>b|s9mTFZw||v4~A`ib$t>C-QmMvyn{BIdge1B3f% zVrJ3MajfRtPa0j_pv+u1KkRjXcKan1YV=~@PVr=D{Z$&x97d|~y1%JSs0*5uwYz^o z^6<$zgOu|40ph6tco;2xc<89XB5v#Sm>#z1y?YZBu#5Ep-}Gm81kmKf);jxA=XYk` zB&eeKQ7jFwY*31bs&3hn-o4keB7UOA9qvRV|B$@vwpxxCq)lD>k_oEoouC!G@xNR6 puVxa`sB9F5^X`5IAB)~Z?66%yb%D?N^52AqlZ5~0N%nT?zW~!6T3Y}B literal 0 HcmV?d00001 diff --git a/docs/config_params.md b/docs/config_params.md index 6bf696362..310976019 100644 --- a/docs/config_params.md +++ b/docs/config_params.md @@ -123,7 +123,7 @@ Data from sensors with other addresses will be ignored. Default value: True **Report unknown sensors** - (`Off`, `Acconeer`, `Air Mentor`, `Amazfit`, `ATC`, `BlueMaestro`, `Blustream`, `Brifit`, `BTHome`, `Chef iQ`, `Govee`, `Grundfos`, `HolyIOT`, `Hormann`, `HHCC`, `iNode`, `iBeacon`, `Jinou`, `Kegtron`, `Mi Scale`, `Mi Band`,`Mikrotik`, `Oras`, `Qingping`, `Relsib`, `rbaron`, `Ruuvitag`, `Sensirion`, `SensorPush`, `SmartDry`, `Switchbot`, `Teltonika`, `Thermoplus`, `Xiaogui`, `Xiaomi`, `Other` or `False`)(Optional) This option is needed primarily for those who want to request an implementation of device support that is not in the list of [supported sensors](devices). If you set this parameter to one of the sensor brands, then the component will log all messages from unknown devices of the specified brand to the Home Assistant log (`logger` component must be enabled at info level, see for instructions the [FAQ](faq#my-sensor-from-the-xiaomi-ecosystem-is-not-in-the-list-of-supported-ones-how-to-request-implementation)). Using a sensor brand might not catch all BLE advertisements. + (`Off`, `Acconeer`, `Air Mentor`, `Amazfit`, `ATC`, `BlueMaestro`, `Blustream`, `Brifit`, `BTHome`, `Chef iQ`, `Govee`, `Grundfos`, `HolyIOT`, `Hormann`, `HHCC`, `iNode`, `iBeacon`, `Jinou`, `Kegtron`, `Mi Scale`, `Mi Band`,`Mikrotik`, `Oras`, `Qingping`, `Relsib`, `rbaron`, `Ruuvitag`, `Sensirion`, `SensorPush`, `Senssun`, `SmartDry`, `Switchbot`, `Teltonika`, `Thermoplus`, `Xiaogui`, `Xiaomi`, `Other` or `False`)(Optional) This option is needed primarily for those who want to request an implementation of device support that is not in the list of [supported sensors](devices). If you set this parameter to one of the sensor brands, then the component will log all messages from unknown devices of the specified brand to the Home Assistant log (`logger` component must be enabled at info level, see for instructions the [FAQ](faq#my-sensor-from-the-xiaomi-ecosystem-is-not-in-the-list-of-supported-ones-how-to-request-implementation)). Using a sensor brand might not catch all BLE advertisements. If you can't find the advertisements in this way, you can set this option to `Other`, which will result is all BLE advertisements being logged. You can also enable this option at device level. **Attention!** Enabling this option can lead to huge output to the Home Assistant log, especially when set to `Other`, do not enable it if you do not need it! If you know the MAC address of the sensor, its advised to set this option at device level. Details in the [FAQ](faq#my-sensor-from-the-xiaomi-ecosystem-is-not-in-the-list-of-supported-ones-how-to-request-implementation). Default value: `Off` diff --git a/info.md b/info.md index 8b110a621..dd9f1e845 100644 --- a/info.md +++ b/info.md @@ -55,6 +55,7 @@ This custom component for [Home Assistant](https://www.home-assistant.io) passiv - Ruuvitag - Sensirion - SensorPush +- Senssun - SmartDry - Switchbot - Teltonika From 532717bc11456ca5c0535413724840c2ac9887bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E8=85=BE?= <233151@nbcb.com> Date: Wed, 31 Jul 2024 17:25:19 +0800 Subject: [PATCH 2/2] fix precommit errors --- README.md | 1 + custom_components/ble_monitor/ble_parser/__init__.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d069dccb3..3911aa33b 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ This custom component for [Home Assistant](https://www.home-assistant.io) passiv - Ruuvitag - Sensirion - SensorPush +- Senssun (Scale) - SmartDry - Switchbot - Teltonika diff --git a/custom_components/ble_monitor/ble_parser/__init__.py b/custom_components/ble_monitor/ble_parser/__init__.py index 944de8014..b82855f87 100644 --- a/custom_components/ble_monitor/ble_parser/__init__.py +++ b/custom_components/ble_monitor/ble_parser/__init__.py @@ -38,6 +38,7 @@ from .ruuvitag import parse_ruuvitag from .sensirion import parse_sensirion from .sensorpush import parse_sensorpush +from .senssun import parse_senssun from .smartdry import parse_smartdry from .switchbot import parse_switchbot from .teltonika import parse_teltonika @@ -46,7 +47,6 @@ from .tilt import parse_tilt from .xiaogui import parse_xiaogui from .xiaomi import parse_xiaomi -from .senssun import parse_senssun _LOGGER = logging.getLogger(__name__)