Ac infinity controller 67 Bluetooth temp, humidity, fan pwm

What an interesting rabbit hole I’m burrowing at the moment. I’ve taken the example code displayed here over the last page or two, given it to chatgpt, and asked it to make changes. I wanted it to change the python code to write a log file of the temp, humidity, and fan speed that it obtains from the bluetooth polling.
It produces perfect bash script code and python code. I then asked it to write yaml so that Home Assistant can read the log file created and display it as a time-series graph. It then generated the code.

I don’t yet have any experience with HA or how this should all work, but at least it’s not pretty much possible to have chatgpt write whatever code I need it to make. It seems to know about HA and of course python, bash, etc.

If you’re curious, here’s what I asked it: “write yaml that will read the file that was created by the python script, extract the temp, and display it as a time graph”. It produced this:

“To read a file, extract temperature data, and display it as a time graph in Home Assistant using YAML configuration, you can use the File Sensor and the History Graph components. Here’s an example YAML configuration:”


sensor:
  - platform: file
    name: temperature_data
    file_path: /path/to/theatre_climate_history.log
    value_template: "{{ value.split(' - ')[1].split(':')[1].strip() }}"
    unit_of_measurement: °F

history_graph:
  temperature_history:
    entities:
      - sensor.temperature_data
    hours_to_show: 24
    refresh: 60

In this example:

1. The sensor section configures the File Sensor to read the file theatre_climate_history.log located at /path/to/theatre_climate_history.log. Adjust the file_path according to the actual path of your log file. The value_template extracts the temperature data from each line of the file. Adjust the template if the log file format differs. The unit_of_measurement specifies the unit of measurement for the temperature data.
2. The history_graph section creates a history graph named temperature_history. It includes the sensor.temperature_data entity, which represents the temperature data read from the file. Adjust the entity name if necessary. The hours_to_show defines the duration of the graph, and refresh specifies the refresh rate in seconds.

Save the YAML configuration to your Home Assistant configuration file, then restart Home Assistant for the changes to take effect. Afterward, you should be able to see the temperature data displayed as a time graph in the Home Assistant interface.

Note: Ensure that you have the necessary components (sensor and history_graph) enabled and configured in your Home Assistant installation.

And here’s the modified python code it created based on my providing Neonerd5’s code:

import struct
import binascii
from bluepy.btle import Scanner, DefaultDelegate
from time import sleep
from datetime import datetime

LEAF_OFFSET = -3.8

def get_name(device):
    if device == "xx:xx:xx:xx:xx:xx": # change to the lowercase mac address of your fan
        return "Theatre" # change to the name of the room it's in

def get_vpd(humidity: float, temperature: float, leaf_offset: float = 0.0):
    # VPD Calculation
    temp_ambient = (temperature - 32) / 1.8
    # Leaf surface temperature is typically -3 to -4 with LED
    # We subtract -35 instead of -32 for the normal F->C calc
    temp_leaf = (temperature - (32 - leaf_offset)) / 1.8
    vpsat = 610.7 * 10 ** ((7.5 * temp_ambient) / (237.3 + temp_ambient)) / 1000
    vpsat_leaf = 610.7 * 10 ** ((7.5 * (temp_leaf)) / (237.3 + (temp_leaf))) / 1000
    # This is the vpair calculation. If we don't want to use leaf
    # surface, we could use this equation: vpd = round(vpsat - vpair,2)
    # vpair = (610.7*10**((7.5*temperature_c)/(237.3+temperature_c))/1000*(h/100))
    vpd_result = round(vpsat_leaf - (vpsat * humidity / 100), 2)
    return vpd_result


class ScanDelegate(DefaultDelegate):
    def __init__(self):
        DefaultDelegate.__init__(self)

    def handleDiscovery(self, dev, isNewDev, isNewData):
        device_list = [
            "xx:xx:xx:xx:xx:xx" #change to the same mac address you entered earlier, in lowercase
        ]

        addr = dev.addr

        if isNewData and addr in device_list:
            name = get_name(addr)
            scan_data = dev.getScanData()[2][2]
           if len(scan_data) >= 20:
                d = binascii.unhexlify(scan_data)
                temp, hum, fan = (
                    float(struct.unpack(">h", d[16:18])[0] / 100 * 9 / 5 + 32),
                    float(struct.unpack(">h", d[18:20])[0] / 100),
                    d[20],
                )
                vpd = get_vpd(humidity=hum, temperature=temp, leaf_offset=LEAF_OFFSET)
                print(f"Device name: {name} - {addr} - Temp: {temp} - Humidity: {hum} - Fan: {fan}")
                if temp > 0 and hum > 0 and fan > -1 and vpd > 0: # if all values are valid
                    ## write out each record to a file, appending it each time.  Include a timestamp.
                    with open("climate_history.log", "a") as file:
                        # Get the current date and time
                        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                        # Write the data with the date stamp
                        file.write(f"{timestamp} - Temp: {temp} - Humidity: {hum} - Fan: {fan}\n")
                    pass

try:
    scanner = Scanner().withDelegate(ScanDelegate())
    scanner.scan(15.0)
    sleep(25)
    scanner.scan(15.0)
except Exception as e:
    print(e)

Until I understand how HA needs the output data in order to display it, I can’t know what code is required. As for being able to change fan speed, I’ve tried the bash code in this thread and it works. Once I know what HA needs in order to display a control for it, I’ll ask chatGPT to generate the code.

Update several hours later: I (well, ChatGPT and me) wrote a python script to find the AC-Infinity bluetooth controller, retrieve, and display the fan speed info. It’s not much different than code already posted in here so I won’t bother pasting it. I’m currently trying to figure out how to write widget thingies so that I can integrate it into HA but that’s a new rabbit hole.

2 Likes

You’re very smart. I can’t even find the Bluetooth MAC address. I read somewhere on the HA instructions for the 69 controller that I needed to use gatttool, but when I run
apk add gatttool
I get a “no such package”
error

Whoa that’s amazing! Quick q, I have speed controller with the UIS connector. I don;t have the controller 67, just the button. Do you know if I would be able to control the fan from HA? Ideally I would want to connect it to an esp32 board to I could control it via mqtt. Do you have any insight on that?

It appears to use bluetooth, in which case it almost certainly uses the same protocols and commands as the 67. Some values may be different and there’s like far more additional controls and commands that it uses since it appears to be a new architecture for numerous devices.

An update since yesterday. ChatGPT (actually it’s all me. I wrote it all. I’m a programming god ok it was completely AI goddamit) has written several python scripts that let me play around with the 67 controller, but I was still blind about what values to change, where. So I started looking at the decompiled apk that was posted on git but found it too hard to navigate and find things. So I decompiled the apk myself
./jadx --deobf ~/acinfinity/AC\ Infinity_1.5.8_apk-dl.com.apk
then opened it in Android Studio Flamingo (in Ubuntu). I found the following interesting values that I’ll likely start focusing in on. My intent is to work out the same sequence of commands, using the same values that the AC Infinity app does, to read and control the controller. Some of the sample code posted in here may or may not work because it usually writes values to UUIDs that, although they appear to work, may corrupt or change other values unintentionally. They also won’t have correct CRC values so the device may ignore them. All this will likely mean that any sort of control from within Home Assistant will be flaky, intermittent, cause device hangs, or may not work on all devices.

Here’s some tantalizing tidbits:

sources/com/eternal/base/global/BluetoothKey.java

pretty much more answers than I can digest at the moment.

Here are all the UUIDs used and their purpose:

FIRMWAREVERSION_UUID = "00002A26"
HARDWAREVERSION_UUID = "00002A27"
MAC_UUID = "00002A23-0000-1000-8000-00805F9B34FB"
MODEL_NAME_UUID = "00002A24-0000-1000-8000-00805F9B34FB"
SN_UUID = "00002A25-0000-1000-8000-00805F9B34FB"
SOFTWAREVERSION_UUID = "00002A28"
UUID_NOTIFY = "70D51002-2C7F-4E75-AE8A-D758951CE4E0"
UUID_NOTIFY_ESP = "0000ff02-0000-1000-8000-00805f9b34fb"
UUID_SERVICE = "70D51000-2C7F-4E75-AE8A-D758951CE4E0"
UUID_SERVICE_ESP = "0000ffff-0000-1000-8000-00805f9b34fb"
UUID_WRITE = "70D51001-2C7F-4E75-AE8A-D758951CE4E0"
UUID_WRITE_ESP = "0000ff01-0000-1000-8000-00805f9b34fb"

Here’s some interesting values used for writes:

DEVICE_VPD_LEAF_TEMPERATURE_OFFSET = 37
DEVICE_TIME_REMAINING_BY_CURRENT_MODE = 24
DEVICE_SET_RESET_DEVICE_TO_FACTORY = 61
DEVICE_BACKLIGHT_BRINGHTNESS_GEAR = 33
DEFAULT_MTU = 247
DEVICE_CURRENT_OPERATING_MODE = 16
DEVICE_CURRENT_TEMP_HUM_STATUS = 2
DEVICE_FAN_GEAR_BY_OFF = 17
DEVICE_FAN_GEAR_BY_ON = 18
DEVICE_INFO = 0

There’s about 100 in all.

1 Like

Mine still works I don’t do updates tho so never needed to change anything sorry

The 67 used to have to be told you want the data then the unit responds with that data

The 69 streams it automatically

@mikeybatoz For that python code you posted while back (ACI_UNI.py, etc.) was that only for the 69 and would it/does it work for the 67? I’d like to try using it but unsure if it’s applicable. Also, are those the latest versions (ACI_UNI.py, manifest.json, fan.py, init.py) you’re using?

I’ve played around with the other approach of using shell scripts to call bluetoothctl, but they are extremely variable - they might return a value 1/4 of the time (it doesn’t usually find the controller, select-attribute and ‘read’ may or may not display anything), I need to awk the 3rd record rather than the 2nd, and it’s the $17 and $18 that seem to equate to temp but even then I get values like “0x9b0f” which certainly aren’t centigrade temps! Here’s an example run. Note that the first try produced little, but re-running it immediately produced more data. I believe it’s the “9f 0f” values that you indicate are temperature?

moa@sophie:~/scripts/acinfinity$ ./acinfinity_temp_humidity.sh | grep a5
[bluetooth]# write "0xa5 0x00 0x00 0x03 0x02 0xe1 0x7e 0x04 0x00 0x01 0x02 0x03 0x20 0x78 0xe9"
moa@sophie:~/scripts/acinfinity$ ./acinfinity_temp_humidity.sh | grep a5
        8ec90003-f315-4f60-9fb8-838830daea50
[ACI-UniversalController:/service001b/char001c]# write "0xa5 0x00 0x00 0x03 0x02 0xe1 0x7e 0x04 0x00 0x01 0x02 0x03 0x20 0x78 0xe9"
  a5 10 00 11 02 e1 57 5d 00 01 02 05 09 0a 9f 0f  ......W]........
  a5 10 00 11 02 e1 57 5d 00 01 02 05 09 0a 9f 0f  ......W]........
  a5 10 00 11 02 e1 57 5d 00 01 02 05 09 0a 9f 0f  ......W]........
moa@sophie:~/scripts/acinfinity$

So I’d rather use python where I have a better method to deal with errors. Your (demo?) code seems to be all I’ll need so I’m just checking to see if it’s current and working for the 67.

Thanks!

Yes it worked for 67, it had an additional send command which made the system return its values

Hexadecimal values need to be changed

Latest version when the post was made, I don’t do updates on the system once it’s setup so it doesn’t throw errors from new programming

In this script you’ll notice you notify, that will allow the controller to recognize the system is going to ask for information. Next we ask attribute for the information after notifying other. This worked when we posted, I’ve not changed anything on mine and still relaying information with this script

#!/usr/bin/expect -f

set prompt "#"
set address f0:74:59:49:c0:45
spawn bluetoothctl
expect -re $prompt
send "connect $address\r"
expect "Connection successful"
send "list-attributes\r"
send "menu gatt\r"
send "select-attribute /org/bluez/hci0/dev_F0_74_59_49_C0_45/service001b/char001e\r"
send "notify on\r"
send "select-attribute /org/bluez/hci0/dev_F0_74_59_49_C0_45/service001b/char001c\r"
send "write \"0xa5 0x00 0x00 0x03 0x03 0x49 0x79 0xd7 0x00 0x01 0x02 0x03 0x20 0x78 0xe9\"\r" 
send "select-attribute /org/bluez/hci0/dev_F0_74_59_49_C0_45/service001b/char001e\r"
send "read\r"
sleep 2
send "quit\r"
expect eof

I posted in the thread above, but since I don’t own a Controller 67, I could use some beta testers from here as well: GitHub - hunterjm/ac-infinity-hacs: Custom Integration for AC Infinity Controllers

just gave this a shot on a 67 and receive error after hitting submit (it does detect my unit ok)

2023-07-15 20:12:25.948 ERROR (MainThread) [custom_components.ac_infinity.config_flow] Unexpected error
Traceback (most recent call last):
  File "/usr/local/lib/python3.11/site-packages/ac_infinity_ble/device.py", line 159, in update
    await events[CallbackType.NOTIFICATION].wait()
  File "/usr/local/lib/python3.11/asyncio/locks.py", line 213, in wait
    await fut
asyncio.exceptions.CancelledError
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "/config/custom_components/ac_infinity/config_flow.py", line 66, in async_step_user
    await controller.update()
  File "/usr/local/lib/python3.11/site-packages/ac_infinity_ble/device.py", line 158, in update
    async with async_timeout.timeout(5):
  File "/usr/local/lib/python3.11/site-packages/async_timeout/__init__.py", line 129, in __aexit__
    self._do_exit(exc_type)
  File "/usr/local/lib/python3.11/site-packages/async_timeout/__init__.py", line 212, in _do_exit
    raise asyncio.TimeoutError
TimeoutError
2023-07-15 20:12:42.628 WARNING (MainThread) [ac_infinity_ble.device] A-EZNF4: Device unexpectedly disconnected; RSSI: -127
2023-07-15 20:12:42.630 WARNING (MainThread) [ac_infinity_ble.device] A-EZNF4: Device unexpectedly disconnected; RSSI: -127

Right, I think I saw somewhere that the 67 doesn’t send those notification messages. After sleeping on it, I don’t think we even need them, as we can get the fan speed, tmp, and hum from the advertisement packets and so only have to connect when changing the speed.

Can you enable debug level logging for “ac_infinity_ble” in your configuration? I’d like to see what the device info response looks like.

Let me test that out and I’ll let you know when I update.

Added

 logger:
   logs:
     custom_components.ac_infinity_ble: debug

to my logger, but not getting any extra output

The entry should just be:

logger:
   logs:
     ac_infinity_ble: debug

That being said, I just released v1.0.2 on HACS which should hopefully fix this issue. If it’s not showing, navigate to the integration and click the 3 dots on the top to choose “redownload”.

1.0.2 is operational, nice work!

I did receive one error in the log


2023-07-16 08:20:20.898 DEBUG (MainThread) [ac_infinity_ble.device] A-EZNF4: Notification received: a5100004000434270003100012009e74
2023-07-16 08:20:20.899 ERROR (MainThread) [custom_components.ac_infinity] C9:E4:CE:B5:3B:BB: Failure while polling
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/components/bluetooth/active_update_coordinator.py", line 129, in _async_poll
    self.data = await self._async_poll_data(self._last_service_info)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/bluetooth/active_update_coordinator.py", line 122, in _async_poll_data
    return await self._poll_method(last_service_info)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/ac_infinity/coordinator.py", line 69, in _async_update
    await self.controller.update()
  File "/usr/local/lib/python3.11/site-packages/ac_infinity_ble/device.py", line 148, in update
    if data := await self._send_command(command):
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/ac_infinity_ble/device.py", line 367, in _send_command
    return await self._send_command_while_connected(command, retry)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/ac_infinity_ble/device.py", line 386, in _send_command_while_connected
    return await self._send_command_locked(command)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/bleak_retry_connector/__init__.py", line 454, in _async_wrap_bluetooth_connection_error_retry
    return await func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/ac_infinity_ble/device.py", line 341, in _send_command_locked
    return await self._execute_command_locked(command)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/ac_infinity_ble/device.py", line 412, in _execute_command_locked
    assert self._client is not None  # nosec
           ^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError

I’ll need to release a new version to fix that error. I’ve been working on this for a while now though, so it may not be until tomorrow. Thank you for the logs! Does setting the fan speed work? (I noticed it takes a few seconds for it to reflect in HA after changing)

Yes fan speed works, but you are right that it doesn’t update all that quickly in the UI - seems great tho.

Appreciate all the work you have put into this, and all the research everyone else in the thread has provided. Sorry I had to bail out a few months ago, I didn’t have the time to dig in much further. Looks like we are well on a way to a complete solution tho!

I’ll keep an eye on the thread, happy to test for you with this 67 unit.

Just jumping back in here after a month. I was waiting for some Bluetooth proxy dongles (Atom Lite) to arrive so that I could make sure the relatively weak BLE signal from the 67 controller would be seen by my Pi in the other room. I’m still getting my head around how these proxy things are supposed to work “transparently” in HA, so I’ll park those dongles for now.

In the meantime I installed the new ac infinity integration version 1.02 and it seems to work. I got a few errors that I just put down to the weak signal and the dropouts I was seeing when I last posted.
I then tried out changing fan speeds and for the most part this works, though it requires a bit of patience not just in the delay but in the confusion that happens if I change speeds too quickly. The log files fill up with noise so I’ve now enabled debug logging and will record a few tests and post a link to the log files shortly.

Here’s the logs. The sequence of activities was:

2023-07-30 21:12      HA freshly rebooted
2023-07-30 21:26      Speed changed to 5/50%
2023-07-30 21:31      Fan turned off
2023-07-30 21:33:58   Fan button pressed
2023-07-30 21:34:40   Fan speed slider: slid upwards; no feedback acknowledging action so I tried pressing fan button again; just then the speed slider indicated the previous attempt, showing 30%, but then the button press got acknowledged and the fan speed went to 0.

I was going to try plugging in a bluetooth USB dongle on the end of a USB cable to get a better signal, but that raises the question of how HA or the integration would know which bluetooth controller to use as there’d now be 2 of them.