# 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 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:
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 %
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
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 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

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:
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:
- "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

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