Skip to content
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

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
22 changes: 22 additions & 0 deletions src/inkbird_ble/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
from home_assistant_bluetooth import BluetoothServiceInfo
from sensor_state_data import SensorLibrary

IAMT1_SERVICE_UUID = "0000ffe4-0000-1000-8000-00805f9b34fb"
Copy link
Member

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

Copy link
Author

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.

Copy link

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.


_LOGGER = logging.getLogger(__name__)

BBQ_LENGTH_TO_TYPE = {
Expand Down Expand Up @@ -70,6 +72,9 @@ def _start_update(self, service_info: BluetoothServiceInfo) -> None:
dev_type, _ = bbq_data
self.set_device_name(f"{local_name} {short_address(address)}")
self.set_device_type(f"{local_name[0]}{dev_type[1:]}")
elif IAMT1_SERVICE_UUID in service_info.service_uuids:
self.set_device_name(f"{local_name} {short_address(address)}")
Comment on lines +75 to +76
Copy link

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.)

Copy link

@theguy147 theguy147 Jan 5, 2025

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()

self.set_device_type(f"{local_name}")
else:
return

Expand Down Expand Up @@ -121,3 +126,20 @@ def _start_update(self, service_info: BluetoothServiceInfo) -> None:
key=f"temperature_probe_{num}",
name=f"Temperature Probe {num}",
)
elif (
lower_name == "iam-t1" and IAMT1_SERVICE_UUID in service_info.service_uuids
):
service_data = service_info.service_data[IAMT1_SERVICE_UUID]
temp = int.from_bytes(service_data[5:7], "big") / 10
temp_sign = service_data[4] & 0xF
temp = temp if temp_sign == 0 else -temp
self.update_predefined_sensor(SensorLibrary.TEMPERATURE__CELSIUS, temp)
humidity = int.from_bytes(service_data[7:9], "big") / 10
self.update_predefined_sensor(SensorLibrary.HUMIDITY__PERCENTAGE, humidity)
co2_ppm = int.from_bytes(service_data[9:11], "big")
self.update_predefined_sensor(
SensorLibrary.CO2__CONCENTRATION_PARTS_PER_MILLION, co2_ppm
)
pressure_hpa = int.from_bytes(service_data[11:13], "big")
self.update_predefined_sensor(SensorLibrary.PRESSURE__HPA, pressure_hpa)
# TODO Battery
103 changes: 102 additions & 1 deletion tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
Units,
)

from inkbird_ble.parser import INKBIRDBluetoothDeviceData
from inkbird_ble.parser import IAMT1_SERVICE_UUID, INKBIRDBluetoothDeviceData


def test_can_create():
Expand Down Expand Up @@ -824,3 +824,104 @@ def test_n0byd():
binary_entity_values={},
events={},
)


def test_IAMT1():
parser = INKBIRDBluetoothDeviceData()
service_info = BluetoothServiceInfo(
name="IAM-T1",
manufacturer_data={
12628: b"41\x43\x2d\x36\x32\x30\x30\x61\x31\x33\x35\x39\x38\x00\x00"
},
service_uuids=[IAMT1_SERVICE_UUID],
address="aa:bb:cc:dd:ee:ff",
rssi=-60,
service_data={
IAMT1_SERVICE_UUID: (
b"\x55\xaa\x01\x10\x00\x00\xc4\x02\xda\x04\x58\x03\xee\x01\x00\xfe"
),
}, # 1112 PPM CO2, 19,6°C, 73% humidity, 1006 hPa
source="local",
)
result = parser.update(service_info)
assert result == SensorUpdate(
title=None,
devices={
None: SensorDeviceInfo(
name="IAM-T1 EEFF",
model="IAM-T1",
manufacturer="INKBIRD",
sw_version=None,
hw_version=None,
)
},
entity_descriptions={
# TODO Battery
# DeviceKey(key="battery", device_id=None): SensorDescription(
# device_key=DeviceKey(key="battery", device_id=None),
# device_class=SensorDeviceClass.BATTERY,
# native_unit_of_measurement=Units.PERCENTAGE,
# ),
DeviceKey(key="signal_strength", device_id=None): SensorDescription(
device_key=DeviceKey(key="signal_strength", device_id=None),
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
DeviceKey(key="humidity", device_id=None): SensorDescription(
device_key=DeviceKey(key="humidity", device_id=None),
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=Units.PERCENTAGE,
),
DeviceKey(key="carbon_dioxide", device_id=None): SensorDescription(
device_key=DeviceKey(key="carbon_dioxide", device_id=None),
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=Units.CONCENTRATION_PARTS_PER_MILLION,
),
DeviceKey(key="pressure", device_id=None): SensorDescription(
device_key=DeviceKey(key="pressure", device_id=None),
device_class=SensorDeviceClass.PRESSURE,
native_unit_of_measurement=Units.PRESSURE_HPA,
),
DeviceKey(key="temperature", device_id=None): SensorDescription(
device_key=DeviceKey(key="temperature", device_id=None),
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=Units.TEMP_CELSIUS,
),
},
entity_values={
# TODO Battery
# DeviceKey(key="battery", device_id=None): SensorValue(
# device_key=DeviceKey(key="battery", device_id=None),
# name="Battery",
# native_value=55,
# ),
DeviceKey(key="signal_strength", device_id=None): SensorValue(
device_key=DeviceKey(key="signal_strength", device_id=None),
name="Signal " "Strength",
native_value=-60,
),
DeviceKey(key="humidity", device_id=None): SensorValue(
device_key=DeviceKey(key="humidity", device_id=None),
name="Humidity",
native_value=73.0,
),
DeviceKey(key="pressure", device_id=None): SensorValue(
device_key=DeviceKey(key="pressure", device_id=None),
name="Pressure",
native_value=1006,
),
DeviceKey(key="temperature", device_id=None): SensorValue(
device_key=DeviceKey(key="temperature", device_id=None),
name="Temperature",
native_value=19.6,
),
DeviceKey(key="carbon_dioxide", device_id=None): SensorValue(
device_key=DeviceKey(key="carbon_dioxide", device_id=None),
name="Carbon Dioxide",
native_value=1112,
),
},
binary_entity_descriptions={},
binary_entity_values={},
events={},
)
Loading