Inkbird ISC-007BW BBQ controller via bluetooth

Hi there,

I’m trying to monitor and control my Inkbird ISC-007BW BBQ controller via bluetooth.

So far I’ve managed to connect to the device, start reading the temperatures and fan speed, and I’ve been able to control the fan speed by sending commands to it. However, setting the speed needs to perform a write action to the blue_client which is an array of 20 bytes which looks like this:
0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x9b, 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00

The first byte is on (0x01) or off (0x00). The seventh byte is the speed in hex (here 0x0a = 10 %). Now the tricky part is bytes 12 and 13. I found out its a CRC16/MODBUS calculation over the first 11 bytes in hex in reversed order. Is there a way to calculate those bytes in ESPhome? Otherwise im left with creating 100 commands for each percentage setting :sweat_smile: Perhaps even more once I figure out the rest of the bytes and commands.

I started out in python because I thought I could create a custom component but since I’m not a programmer, that was a bit too hard and switched to ESPhome. In python I made something like this to create the command using a value for switch and speed:

from crccheck.crc import Crc16Modbus

# calculate hex
def crc(data):
    crcinst = Crc16Modbus()
    crcinst.process(data)
    crc = crcinst.finalhex()
    return crc[2:4] +" " + crc[0:2] + "00 00 00 00 00 00 00"

def commandfull(switch, speed):
    commandshort = bytearray.fromhex( switch + " 00 00 00 00 01 " + '{0:02x}'.format(speed) + " 00 00 00 00 ")
    #data = commandshort
    crcresult = crc(commandshort)
    return commandshort + bytearray.fromhex( crcresult )

command = commandfull("01", 10)
print (command)

This resulted in the above byte array in which I set the fan speed to 10%. The current ESP yaml looks like this: (not completed yet but it works)

esphome:
  name: esp32

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:

esp32_ble_tracker:

#not used global variable but an example of how to store an array for sending a command
globals:
  - id: blecommand
    type: std::vector<unsigned char>
    initial_value: '{0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x64, 0x00, 0x00, 0x00, 0x00, 0x72, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}'

ble_client:
  - mac_address: 49:22:04:02:00:F8
    id: isc007bw
    on_connect:
      then:
        - lambda: |-
            ESP_LOGD("ble_client_lambda", "Connected to BLE device");    

# created one subscription sensor that publishes all the data to the other sensors since it's all in the same message
sensor:
  - platform: ble_client
    type: characteristic
    ble_client_id: isc007bw
    id: fanspeed
    name: "Fan Speed"
    service_uuid: 'FFF0'  # device service
    characteristic_uuid: 'FFF6'  # subsciption
    notify: true
    lambda: |-
      uint8_t speed = x[6];
      uint16_t temp1 = x[0] | x[1] <<8;
      id(isc_probe1).publish_state((float)temp1 /10 );
      uint16_t temp2 = x[2] | x[3] <<8;
      id(isc_probe2).publish_state((float)temp2 /10 );
      uint16_t temp3 = x[4] | x[5] <<8;
      id(isc_probe3).publish_state((float)temp3 /10 );
      return (float)speed ;
    unit_of_measurement: '%'
    icon: "mdi:fan"
  
  - platform: template
    id: isc_probe1
    name: "isc probe 1"
    device_class: "temperature"
    unit_of_measurement: '°F'
  
  - platform: template
    id: isc_probe2
    name: "isc probe 2"
    device_class: "temperature"
    unit_of_measurement: '°F'
  
  - platform: template
    id: isc_probe3
    name: "isc probe 3"
    device_class: "temperature"
    unit_of_measurement: '°F'

#Output for the fan template. I hardcoded only 3 values but that's what I like to change
output:
  - platform: template
    id: isc_output
    type: float
    write_action:
      - if:
          condition:
            fan.is_on: isc_fan
          then:
            - ble_client.ble_write:
                id: isc007bw
                service_uuid: FFF0
                characteristic_uuid: FFF1
                value: !lambda |-
                  if (id(isc_fan).speed == 1)  {return {0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x32, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};}
                  if (id(isc_fan).speed == 2) {return {0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x64, 0x00, 0x00, 0x00, 0x00, 0x72, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};}
                  else {return {0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x9b, 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};}

# Fan for HA. Currently just 10 speed count for testing but I want it for 100
fan:
  - platform: speed
    id: isc_fan
    output: isc_output
    speed_count: 10
    name: "ISC-007BW fan"
    on_turn_on:
      - logger.log: "Fan turned on"     
    on_turn_off:
      - ble_client.ble_write:
          id: isc007bw
          service_uuid: FFF0
          characteristic_uuid: FFF1
          # List of bytes to write.
          value: [0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
      - logger.log: "Fan turned off"
    on_speed_set:
      - logger.log: "Fan speed was changed!"      

# dis/enable the active connection to the device
switch:
  - platform: ble_client
    ble_client_id: isc007bw
    name: "ISC-007BW"
    id: isc007bwswitch
 

I hope anybody can help me out on this. I hoped changing the speed could perhaps just update the correct bytes in a global variable called ‘command’ or something and calculate the correct CRC bytes, and then send that command to ble_client.ble_write. Or if somebody can help me out writing a custom component that would also be great. There are ofcourse more commands and values te read (like setting a timer, setting target temperatures etc, but for now the fan control was my main target).

I’ve found some more settings and characteristics which might be useful.

FFF6 handle are the incoming notifications:

example en byte position:
bf 02 70 17 70 17 0a 00 18 60 00 a6 36 01 07 00 00 ad 62 ff
1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16 17 18 19 20

byte1-2: probe 1 in Fahrenheit (reverse and /10) not influenced by setting to celsius.
byte3-4: probe 2 in Fahrenheit (reverse and /10) not influenced by setting to celsius.
byte5-6: probe 3 in Fahrenheit (reverse and /10) not influenced by setting to celsius.
byte7: current fanspeed in %
byte8: timer in hours
byte9: timer in minutes
byte10-11: unkown looks fixed values
byte12-20: unkown.

write handle FFF1 (settings):
byte1: fan on or off
byte2: 00 = manual fan speed, 01 = automatic fan speed
byte3: 00 = control time off, 01 = control time on
byte4: set timer hours
byte5: set timer minutes
byte6: set unit temperature celsius(01) or Fahrenheit (00)
byte7: set fan speed in %
byte8: set kamado type: 00=kamado 22"; 01=kamado 15"; 02=kettle 22"; 03=kettle 15"; 04=WSM 18"
byte9-10: unkown. always 00
byte12-13: CRC/modbus in reverse
byte14-20: unkown always 00

write FFF3 settings:
byte1: correction probe 1: +1=01 +2=02 etc; -1=FF -2=FE etc.
byte2: correction probe 2
byte3: correction probe3
byte4-5: wanted auto grill temp in celsius (probably dependent of Fahrenheit/celcius setting)
byte6-7: temp 1 alert in celsius
byte8-9 temp 2 alert in celsius
byte10-11: temp3 alert in celsius
byte12-13: crc16/modbus in reverse
byte14-20: unkown fixed 00

write FFF5 setting:
byte1: 24hours = 18; 12hours = 0c
byte2-20: unkown alwasy 00

Finally got it! Basically I use global variables to store the settings of 2 services (FFF1 and FFF3). They get updated via the sensors (handle 1 and handle 3) so that if you change a setting on the device, it also reflects in home assistant. After changing any setting in home assistant, I update the global variables, and call a script. The script calculates the crc values and sends them to the inkbird controller. Now the next step will be to use a PID integration. Or I might give a custom component in HA a try, so you would not have to use an ESP. Not sure yet :slight_smile: I did not include all the settings in sensors but they can be done in the same way, using the mapping of bytes in my post above.

esphome:
  name: esp32

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

esp32_ble_tracker:

globals:
  - id: isc_blecommand1
    type: std::vector<unsigned char>
    initial_value: '{0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x32, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}'
  
  - id: isc_blecommand3
    type: std::vector<unsigned char>
    initial_value: '{0x00, 0x00, 0x00, 0x64, 0x00, 0x64, 0x00, 0x96, 0x00, 0x4e, 0x02, 0x90, 0xf2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}'
    

ble_client:
  - mac_address: 49:22:04:02:00:F8
    id: isc007bw
    on_connect:
      then:
        - lambda: |-
            ESP_LOGD("ble_client_lambda", "Connected to BLE device");    

# input number for setting the wanted grill temp for auto fan-mode.
number:
  - platform: template
    name: "isc_wanted_grill_temp"
    device_class: "temperature"
    min_value: 0
    max_value: 315
    step: 1
    lambda: return (int)id(isc_blecommand3)[3];
    set_action: 
      - lambda: id(isc_blecommand3)[3] = (uint8_t)x;
      - script.execute: isc_command_send


sensor:
  - platform: ble_client
    type: characteristic
    ble_client_id: isc007bw
    id: fanspeed
    name: "Fan Speed"
    service_uuid: 'FFF0'  # device service
    characteristic_uuid: 'FFF6'  # subsciption
    notify: true
    lambda: |-
      uint8_t speed = x[6];
      uint16_t temp1 = x[0] | x[1] <<8;
      id(isc_probe1).publish_state((float)temp1 /10 );
      uint16_t temp2 = x[2] | x[3] <<8;
      id(isc_probe2).publish_state((float)temp2 /10 );
      uint16_t temp3 = x[4] | x[5] <<8;
      id(isc_probe3).publish_state((float)temp3 /10 );
      return (float)speed ;
    unit_of_measurement: '%'
    icon: "mdi:fan"

  - platform: ble_client
    type: characteristic
    ble_client_id: isc007bw
    name: "isc handle 1"
    service_uuid: 'FFF0'  # device service
    characteristic_uuid: 'FFF1'  # subsciption
    update_interval: 60s
    lambda: |-
      for (int i = 0; i < x.size(); i++) {
        id(isc_blecommand1)[i] = (uint8_t)x[i]; }
      return (float)x.size();

  - platform: ble_client
    type: characteristic
    ble_client_id: isc007bw
    name: "isc handle 3"
    service_uuid: 'FFF0'  # device service
    characteristic_uuid: 'FFF3'  # subsciption
    update_interval: 60s
    lambda: |-
      for (int i = 0; i < x.size(); i++) {
        id(isc_blecommand3)[i] = (uint8_t)x[i]; }
      return (float)x.size();
  
  - platform: template
    id: isc_probe1
    name: "isc probe 1"
    device_class: "temperature"
    unit_of_measurement: '°F'
  
  - platform: template
    id: isc_probe2
    name: "isc probe 2"
    device_class: "temperature"
    unit_of_measurement: '°F'
  
  - platform: template
    id: isc_probe3
    name: "isc probe 3"
    device_class: "temperature"
    unit_of_measurement: '°F'

output:
  - platform: template
    id: isc_output
    type: float
    write_action:
      - if:
          condition:
            fan.is_on: isc_fan
          then:
            - lambda: id(isc_blecommand1)[6] = (uint8_t)id(isc_fan).speed;
            - script.execute: isc_command_send

fan:
  - platform: speed
    id: isc_fan
    output: isc_output
    speed_count: 100
    name: "ISC-007BW fan"
    on_turn_on:
      - lambda: id(isc_blecommand1)[0] = (uint8_t)1;
      - script.execute: isc_command_send
      - logger.log: "Fan turned on"     
    on_turn_off:
      - lambda: id(isc_blecommand1)[0] = (uint8_t)0;
      - script.execute: isc_command_send
      - logger.log: "Fan turned off"
    on_speed_set:
      - logger.log: "Fan Speed was changed!"      

select:
  - platform: template
    name: "BBQ type"
    id: "isc_bbq_select"
    lambda: |-
      auto index = int(id(isc_blecommand1)[7]);
      auto val = id(isc_bbq_select).at(index);
      return {val};
    options:
      - "Kamado 22"
      - "Kamado 15"
      - "Kettle 22"
      - "Kettle 15"
      - "WSM 18"
    set_action:
      - lambda: |-
          if (x == "Kamado 22") {
            id(isc_blecommand1)[7]=(uint8_t)0;}
          if (x == "Kamado 18") {
            id(isc_blecommand1)[7]=(uint8_t)1;}
          if (x == "Kettle 22") {
            id(isc_blecommand1)[7]=(uint8_t)2;}
          if (x == "Kettle 15") {
            id(isc_blecommand1)[7]=(uint8_t)3;}
          if (x == "WSM 18") {
            id(isc_blecommand1)[7]=(uint8_t)4;}
          id(isc_bbq_select).publish_state(x);
      - script.execute: isc_command_send


switch:
  - platform: ble_client
    ble_client_id: isc007bw
    name: "ISC-007BW"
    id: isc007bwswitch
  
  - platform: template
    name: "Fan BBQ auto"
    id: "isc_auto_fan_switch"
    lambda: |-
      return id(isc_blecommand1)[1];
    turn_on_action:
      - lambda: id(isc_blecommand1)[1] = 0x01;
      - script.execute: isc_command_send
    turn_off_action:
      - lambda: id(isc_blecommand1)[1] = 0x00;
      - script.execute: isc_command_send
  
script:
  - id: isc_command_send
    then:
      - lambda: |-
          uint16_t crc = 0xFFFF;
          // Calculate CRC for the first 11 bytes
          for (int pos = 0; pos < 11; pos++) {
          crc = crc ^ (uint16_t)(id(isc_blecommand1)[pos] );
          for (int i = 8; i != 0; i--) {
          if ((crc & 0x0001) != 0) {
          crc >>= 1; 
          crc ^= 0xA001;
              }
          else                            // Else LSB is not set
          crc >>= 1;                    // Just shift right
            }
          }
          id(isc_blecommand1)[11] = crc & 0xFF; // little endian
          id(isc_blecommand1)[12] = (crc >> 8) & 0xFF; // little endian
      - ble_client.ble_write:
          id: isc007bw
          service_uuid: FFF0
          characteristic_uuid: FFF1
          value: !lambda 'return id(isc_blecommand1);'
      - lambda: |-
          uint16_t crc = 0xFFFF;
          // Calculate CRC for the first 11 bytes
          for (int pos = 0; pos < 11; pos++) {
          crc = crc ^ (uint16_t)(id(isc_blecommand3)[pos] );
          for (int i = 8; i != 0; i--) {
          if ((crc & 0x0001) != 0) {
          crc >>= 1; 
          crc ^= 0xA001;
              }
          else                            // Else LSB is not set
          crc >>= 1;                    // Just shift right
            }
          }
          id(isc_blecommand3)[11] = crc & 0xFF; // little endian
          id(isc_blecommand3)[12] = (crc >> 8) & 0xFF; // little endian
      - ble_client.ble_write:
          id: isc007bw
          service_uuid: FFF0
          characteristic_uuid: FFF3
          value: !lambda 'return id(isc_blecommand3);'
1 Like

Great work. I’m not familiar with Bluetooth coding, so I have to decipher your code. Do I understand correctly that you can set the wanted temp to the device and use auto fan mode? Why would you want to add PID integration then? The device then is already a PID controller, or doesn’t it function well?
Another possibility is to include it in the inkbird integration. This integration has already other inkbird Bluetooth thermometers.
If you want, I can test it, once I received mine.

Yes correct, this inkbird already has a PID integrated however I feel it could be better: in the beginning it’s very good, however after a while (few hours) it start to act weird. It sort of goes into on / off mode instead of adjusting the fan speed slightly to keep the temperature. And a thing is: there is also a newer version (inkbird isc-027bw) which has for example open lid detection (so when the temp drops it pauses the fan etc) and inkbird refuses to implement that stuff in this version. The new version has a tendency to overshoot the temperature according to reviews though and can not be operated on the device itself (only with the app, but perhaps this ESPHome code also works on it). So I thought bringing the functionality to this device as well and be independent of inkbird servers / apps etc.
The inkbird integration is just for the bluetooth thermometers but doesn’t have support for the controllers.
I’m trying to give a custom component in home assistant a go but it’s difficult for a beginner, and I’m not a programmer. In ESPhome I could trial and error every step of the way but the custom integrations are a lot to understand at once and it doesn’t look you can take small steps.

Ah ok that explains. I received mine and managed to couple it to HA with your code!
Haven’t tried it, but you can also use the entities/sensors from this in a PID controller from HACS like: GitHub - soloam/ha-pid-controller: PID Controller to Home Assistant . There might be others, but I found this through quick search. I think I will try this.

And add an automation to turn of the fan in case of a large temp drop —> open lid detection
And turn on again when temp is increasing

I added the ESP PID in mine, just because it had an autotune button. But I still have to test it out. I did notice a small error on how I set the fan speed on the output sensor. The ‘state’ of this sensor is de fan speed between 0 and 1, so you could use that as well but you have to multiply it by 100 to set it in the ble_command. I now made it so I can set the speed manually with the fan sensor if it’s turned on. If it’s turned off, the PID will control the fan. If I did not make this difference, the PID would keep setting the speed to 0. And it’s still possible to use the inkbird PID :slight_smile:

output:
  - platform: template
    id: isc_output
    type: float
    write_action:
       - if:
          condition:     #if fan is on, use manual fan speed settings
            fan.is_on: isc_fan 
          then:
            - lambda: id(isc_blecommand1)[6] = (uint8_t)id(isc_fan).speed;
            - script.execute: isc_command_send
          else:     #if manual fan is off, use PID settings
            - lambda: id(isc_blecommand1)[6] = (uint8_t)(state*100);
            - script.execute: isc_command_send

Wanted to come here and say thank you for your hard work on this, I am in the process of trying to set this up as well!

Looking good! But how do I set this up without esp? I’ve a Bluetooth adapter directly connected to my HASS server.

Also tried to configure it with Tuya and Local Tuya but the ISC-007BW doesn’t seem te be supported yet.

Hi that’s not possible yet since I could not figure out how to build an integration. I was able to create the python commands etc but not to put that all in an integration.

I figured it out to get it working with Local Tuya. But I’ve no idea how to share a config, got this from .storage/core.config_entries but I think you’ve to configure it by yourself through the integration:

"entities": [
  {
    "friendly_name": "BBQ Stove temperature",
    "unit_of_measurement": "°F",
    "device_class": "temperature",
    "scaling": 0.01,
    "id": 101,
    "platform": "sensor"
  },
  {
    "friendly_name": "BBQ Probe temperature",
    "unit_of_measurement": "°F",
    "device_class": "temperature",
    "scaling": 0.01,
    "id": 102,
    "platform": "sensor"
  },
  {
    "friendly_name": "BBQ Fan Speed",
    "unit_of_measurement": "%",
    "device_class": "speed",
    "id": 105,
    "platform": "sensor"
  },
  {
    "id": 112,
    "friendly_name": "BBQ Stove target temperature",
    "unit_of_measurement": "°C",
    "device_class": "temperature",
    "platform": "sensor"
  }
],

Because most temperatures comes back in Fahrenheit and I like to have Celsius I’ve created some helpers:

  • sensor.bbq_probe_temperature_c with:
{{ (((states.sensor.bbq_probe_temperature.state | float - 32 )) * 5/9) | round(1) }}
  • sensor.bbq_stove_temperature_c with:
{{ (((states.sensor.bbq_stove_temperature.state | float - 32 )) * 5/9) | round(1) }}

And because of this bug: Added optional support for multiple number formats (float, int) by laurensk · Pull Request #1265 · rospogrigio/localtuya · GitHub I created a custom input number: input_number.bbq_stove_target_temperature_setter so I can use that for an automation:

alias: BBQ target temperatuur aanpasser
description: ""
trigger:
  - platform: state
    entity_id:
      - input_number.bbq_stove_target_temperature_setter
condition: []
action:
  - service: localtuya.set_dp
    data:
      device_id: MY_DEVICE_ID
      dp: 112
      value: "{{ states('input_number.bbq_stove_target_temperature_setter') | int }}"
mode: single

With my Lovelace dashboard:

- title: BBQ
  path: bbq
  icon: mdi:grill
  badges: []
  cards:
    - show_name: true
      show_icon: true
      show_state: true
      type: glance
      entities:
        - entity: sensor.bbq_stove_temperature_c
          name: BBQ
        - entity: sensor.bbq_stove_target_temperature
          name: Target
        - entity: sensor.bbq_fan_speed
          name: Fan Speed
        - entity: sensor.bbq_probe_temperature_c
          name: Vlees
    - type: entities
      entities:
        - entity: input_number.bbq_stove_target_temperature_setter
          secondary_info: none
          name: Target aanpassen
      show_header_toggle: false
      state_color: false
    - chart_type: line
      period: 5minute
      type: statistics-graph
      entities:
        - sensor.bbq_stove_temperature_c
        - sensor.bbq_probe_temperature_c
      stat_types:
        - mean
        - min
        - max
      hide_legend: true

Which looks like:


But it’s currently not connected so no values.