-
-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add support for the IAM-T1 Air Quality Sensor #35
base: main
Are you sure you want to change the base?
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #35 +/- ##
===========================================
+ Coverage 86.15% 96.34% +10.18%
===========================================
Files 2 2
Lines 65 82 +17
Branches 9 12 +3
===========================================
+ Hits 56 79 +23
+ Misses 5 1 -4
+ Partials 4 2 -2 ☔ View full report in Codecov by Sentry. |
src/inkbird_ble/parser.py
Outdated
temp = int.from_bytes(service_data[5:7], "big") / 10 | ||
temp_sign = service_data[4] & 0xF | ||
temp = temp if temp_sign == 0 else -temp | ||
# TODO Celsius vs Fahrenheit? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# TODO Celsius vs Fahrenheit? |
C to F happens downstream in Home Assistant
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is true for this device. I ran gatttool
while pressing the °C/°F button on the device multiple times.
$ gatttool -b xx:xx:xx:xx:xx:xx --interactive
[xx:xx:xx:xx:xx:xx ][LE]> connect
Attempting to connect to xx:xx:xx:xx:xx:xx
Connection successful
Notification handle = 0x002c value: 55 aa 01 10 00 00 c4 02 bc 02 89 03 f0 01 00 11
Notification handle = 0x002c value: 55 aa 05 0c 00 00 00 00 00 00 01 11
Notification handle = 0x002c value: 55 aa 01 06 10 02 a0 02 bc 02 89 03 f0 01 01 e6
Notification handle = 0x002c value: 55 aa 05 0c 00 00 00 00 00 00 00 10
Notification handle = 0x002c value: 55 aa 01 06 00 00 c4 02 bc 02 89 03 f0 01 01 08
Notification handle = 0x002c value: 55 aa 05 0c 00 00 00 00 00 00 01 11
Notification handle = 0x002c value: 55 aa 01 06 10 02 a0 02 bc 02 89 03 f0 01 01 e6
Notification handle = 0x002c value: 55 aa 05 0c 00 00 00 00 00 00 00 10
Notification handle = 0x002c value: 55 aa 01 06 00 00 c4 02 bc 02 89 03 f0 01 01 08
Notification handle = 0x002c value: 55 aa 05 0c 00 00 00 00 00 00 01 11
Notification handle = 0x002c value: 55 aa 01 06 10 02 a0 02 bc 02 89 03 f0 01 01 e6
Notification handle = 0x002c value: 55 aa 05 0c 00 00 00 00 00 00 00 10
00c4 (base 16) == 196 (base 10) == 19,6°C
02a0 (base 16) == 672 (base 10) == 67,2°F
The second last byte (00 vs 01) in that intermediate notification seems to indicate the unit.
I think this needs to be considered and converted here in case the device sends Fahrenheit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also agree that the upper nibble of that byte indicates whether the returned value is in Fahrenheit or in Celsius. As far as I found out to ensure compatibility with HA this should be converted to Celsius in case the Fahrenheit value has been received. Later on HA will then convert it back to Fahrenheit if a user wants to display it as such.
@@ -16,6 +16,8 @@ | |||
from home_assistant_bluetooth import BluetoothServiceInfo | |||
from sensor_state_data import SensorLibrary | |||
|
|||
IAMT1_SERVICE_UUID = "0000ffe4-0000-1000-8000-00805f9b34fb" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0000ffe4
will match other devices as well as 0000-1000-8000-00805f9b34fb
is the base part of the base UUID https://github.com/Bluetooth-Devices/bluetooth-data-tools/blob/8fdc7cade9c3b87dfdd77cb741d1fd83fbc154ea/src/bluetooth_data_tools/gap.py#L8
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a match for the device name as well.
It looks like this device only support GATT, but this library only supports reading advertising data. |
Is this a show stopper? Other libraries in |
Yes. GATT supported would need to be added to the library first |
Example implementation Bluetooth-Devices/xiaomi-ble#11 |
Unfortunately, this does not appear to work with the Inkbird IAM-T1. This device only transmits live sensor data when actively connected and when additionally notifications are enabled. More Details: Looking at the different methods to fetch Bluetooth data I tried to modify this Inkbird integration to use a One solution for this that I can think of would be to use a Another resource intensive approach would be to keep the connection open and to keep notifications enabled all the time and then update the sensor data in HA from within the notification callback. Although this approach would be compatible with an |
This is the way.
We generally only want one integration to talk Bluetooth to vendor devices, even if that means we need to have split branching in the integration. Generally we would only make completely new integration if it was Bluetooth vs Cloud communication. |
@@ -16,6 +16,8 @@ | |||
from home_assistant_bluetooth import BluetoothServiceInfo | |||
from sensor_state_data import SensorLibrary | |||
|
|||
IAMT1_SERVICE_UUID = "0000ffe4-0000-1000-8000-00805f9b34fb" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't believe this is the right service. The device I have uses service FFE0 with characteristic FFE4.
elif IAMT1_SERVICE_UUID in service_info.service_uuids: | ||
self.set_device_name(f"{local_name} {short_address(address)}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't expect this to work. The T1 I have doesn't advertise this service (or any service) so I would expect service_uuids
to be empty here. Did this work?
The manufacturer data (0x3154 "AC-6200a135990\0" or maybe they meant "1TAC-..."?) feels like it may be a more reliable identifier (plus the device name?)
(FFE0 is also not an assigned service. It's a service that a lot of devices squat on without registering. So its existence doesn't tell you anything other than the device developer was sloppy, and possibly just following a random tutorial or copied HOPERF modules.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my attempts I also use the "T1AC" (endianness) to identify the IAM-T1. But I wouldn't use the number after that as I think that it is the serial number or at least device specific (mine have other digits).
EDIT: I looked at my code again and this is how I currently identify the IAM-T1
def is_iam_t1(service_info: BluetoothServiceInfo) -> bool:
return 0x3154 in service_info.manufacturer_data.keys()
@hosswald , @theguy147 Are either of you working actively on this? I picked up one of these sensors, and am digging into integrating it. I have extensive background in Bluetooth, but I'm just finding my way around HomeAssistant and trying to work out how to make this change correctly. (That said, while I kind of like the device, its Bluetooth/app protocol is so amateurish that I'm considering replacing it with something else.) Quoting @theguy147:
I don't think this is particularly resource intensive compared to other solutions. Maintaining a BLE connection is generally pretty cheap (much cheaper than scanning for a device), and this sensor only emits notifications 1/min at most (it can be configured to be slower than that). The main issue is that it will break the companion app. I don't currently see any fix for that. The companion app holds a permanent background connection that blocks all other connections. So if the companion app is running in the background, nothing else can get this info. The only way I see this working is for HA to hold the connection and declare that it's incompatible with the companion app. Even if HA were to poll with connect/read/disconnect (to leave the connection available most of the time), if the companion app were ever launched it would break HA until the companion app was force-quit. That's not really workable IMO. |
@rnapier I'm not actively working on it, feel free to take over! |
@rnapier I am working on it but not very actively, only a couple of minutes here and there... So feel free to take this over if you want. My implementation currently works with the IAM-T1 but as you explained well it blocks other connections such as the companion app and I am not happy about it. Because it is either the companion app OR the home assistant integration I was thinking about also implementing the other stuff you can do with the app, e.g., setting the notification interval. The mechanism is quite easy, actually. In the "ffe0" service the "ffe4" characteristic is for reading (notifications only) and the "ffe9" characteristic is for writing. If you write certain commands to "ffe9" you will get replies at "ffe4", e.g., the historical sensor data. Let me know if you are interested in all that and I can give you my notes with the discovered "secret" commands and the response formats. (Probably it doesnt make sense to implement fetching historical data for home assistant but it would make sense to have e.g. a setting to change the sensor update interval and maybe some of the other features like calibration). |
@theguy147 Thanks. I'd be happy to expand on your code if you push up a fork. The companion app really chews on my phone's battery, so I'm not really happy with having it installed and would rather integrate it into HA. I want to use its output to control my air exchanger anyway. Given the caveats on this device, I'm not sure if it should be considered a "core" integration. (I'm still learning my way around how HA is structured; what's the right approach here @bdraco?) |
My code currently just lives in my home assistant instance. In the next days I will try to find some time to put it into a fork and maybe clean one or two things up beforehand (I put a lot of bad style debugging code in there as I am also new to home assistant and first had to understand how everything works together - Obviously I did this in the worst way possible ;) Here are some of my notes about the service and its characteristics:
As has been established already to get readings from the IAM-T1 one needs to subscribe to the This is the schema, where each hex byte is separated by a dash "-":
And here is an example using from construct import Struct, Byte, Int16ub, Computed
IAM_T1_Sensors = Struct(
"magic" / Byte[4], # 4-byte magic value: 55-AA
"ng" / Byte, # Sign for temperature
"temperature_raw" / Int16ub, # Temperature raw value (TempHi << 8 | TempLo)
"humidity_raw" / Int16ub, # Humidity raw value (HumHi << 8 | HumLo)
"co2" / Int16ub, # CO2 raw value (Co2Hi << 8 | Co2Lo)
"pressure" / Int16ub, # Air pressure raw value (PresHi << 8 | PresLo)
# Computed fields
"temperature"
/ Computed(lambda ctx: (-1 if ctx.ng == 1 else 1) * ctx.temperature_raw / 10),
"humidity" / Computed(lambda ctx: ctx.humidity_raw / 10),
)
# ...
sensors = IAM_T1_Sensors.parse(data)
print(f"Temperature: {sensors.temperature}")
print(f"Humidity: {sensors.humidity}")
print(f"Co2: {sensors.co2}")
print(f"Pressure: {sensors.pressure}") Now the modification of Co2 settings by writing to the
where the
An example would be the following byte sequence:
By the way, the def getEndNum(s: str) -> str:
# sum up all bytes, then AND with 0xff and append this byte to the end of the string = "sum hash"
return s + hex(sum(bytes.fromhex(s)) & 0xFF)[2:] Generally, it appears as if most writes to I hope this helps. If anyone is interested I can also post my findings on the found commands and schemas for:
|
I've switched to using an ESPHome Bluetooth Proxy for this, and it works quite well. Rather than creating a custom integration for this, is there a nice way to get the flexibility of ESPHome "built-in?" Is there a nice way to just configure it in YAML? |
#34
I created an implementation for the IAM-T1 Air Quality sensor including tests. The following sensors are calculated:
I took the byte numbers from https://smarthomescene.com/guides/how-to-integrate-inkbird-iam-t1-air-quality-monitor-in-home-assistant/ and figured out the service UUID with
bluetoothctl
. Then I wrote the test against a real payload from my device for which I knew the decoded values.I'm not sure if my implementation works. Is is the first one to use a non-empty
service_data
and I never implemented anything to do with BLE. Is there any way I can test the implementation with my real IAM-T1, without having to build my own home assistant version? Please advise.