What's right to put on bk7231n based IR blaster to make it usable in HA?

I hope someone can point me in the right direction please.

I’ve got a cheap network IR transceiver, thats originally Tuya and has something-esp labelled as ‘CB3S’ in it.
I’m hoping to get it running some form of ESPHome and talking to HA.

I’ve found the pins that the IR LEDs and receiver are physically connected to.

I got a USB>Serial adapter rigged up and read this info using ltchiptool:
Screenshot 2024-03-05 152759
bk7231n, 2MiB

Dumped it’s original firmware, and everythig else I could, with bk7231n ltchiptool.
Did way too much reading and flashing https://github.com/libretiny-eu/esphome-kickstart seemed like the way to go.

Did that OK and after a restart it showed as its own wifi AP.
Connected to it, gave it my wifi creds and it’s connected to my wifi OK.
ESPHome in HA found it and shows it as online.

I’ve been able to set a single pin as output/hi through its device in HA, and its LED lights up… so things are talking OK in a really basic way.
If I browse its IP, I get to the kickstart page that lists a bunch of info, lets me reset it back to it showing as its own AP, and lets me manually toggle pins.

I was thinking kickstart was a kind of stub rom so I could then flash ESPHome through HA, but if I try installing esphome to it through HA it goes through compiling everything then ends up at ERROR Connecting to 192.168.5.43:8266 failed: [Errno 111] Connection refused

I cant find any way to write a config to it, to get it talking to the existing IR hardware…
I’ve read allsorts, found lots of tools to flash lots of things, and really aren’t sure what’s needed. I’m starting to think I’ve flashed the wrong thing to it entirely?

Whats it best to get on it, and how can I send it a config that defines the pins the IR hardware’s connected to?

I realise I’m probably asking in the wrong place, but I cant find any ‘this device can run X that can be flashed using Y’ anywhere (incuding ESPHome — ESPHome).

Huge thanks in advance

A couple of minutes after writing all of that, I found LibreTiny Platform — ESPHome that mentions using
bk72xx: board: generic-bk7231n-qfn32-tuya in the devices config in HA.

Set that and hit install :crossed_fingers:
I got:

INFO Successfully compiled program.
INFO Connecting to 192.168.5.43
INFO Uploading /data/build/kickstart-bk7231n/.pioenvs/kickstart-bk7231n/firmware.uf2 (2938880 bytes)
Uploading: [============================================================] 100% Done...


INFO Upload took 15.82 seconds, waiting for result...
INFO OTA successful
INFO Successfully uploaded program.
INFO Starting log output from 192.168.5.43 using esphome API
INFO Successfully connected to kickstart-bk7231n @ 192.168.5.43 in 19.440s
INFO Successful handshake with kickstart-bk7231n @ 192.168.5.43 in 0.239s

…so hopefully I’m gtting there. Please do let me know if I’m heading down the wrong track though!

1 Like

There’s quite a lot of interest around the forum for ESPHome based IR blasters.

So if you get it all working, consider publishing it as an ESPHome device.

Do you have a link to the product?

It’s one of these:
https://www.aliexpress.com/item/1005005394429651.html
Cost a whole £5.something.

Quite a neat device physically, but out of the box it wasn’t great with Tuya. I’ve got an old MCE/eHome remote that it just wouldn’t play nicely with. (I think it might repeat IR signals in a way that Tuya doesn’t get on with).

Thanks for that link, I’ll put it up there if I get it working properly.
TBH I’ve been struggling to find my way around the ESPHome site, so hadn’t found that list.
I’m hoping it’ll be a lot like this, but maybe different pins.

So far I’ve got it flashing OK through ESPHome in HA and its button’s responding properly.



2 Likes

Did you check to see if the chip van be flashed wirelessly with cloudcutter?

Yeah it can be a little clunky to navigate.

Decent likelihood I’d say…

This is the first time I’ve touched anything-ESP, as you might have guessed. I may well have got it flashed the hard way.

I did stumble across cloudcutter, but wanted a way to back up the original firmware and it didn’t seem to do that bit. I already had the USB/serial adapter and didn’t mind getting my soldering iron out. I would still have had to open it up to check what pins things are connected to anyway.
Been fiddling with the real basics in the devices yaml so far.

It’s reporting it’s WiFi signal strength
It’s button and LED both show for use in HA (disabled my default cos they’re both pretty optional to the IR functionality)
The LEDs acting like a status light if there’s any error
Got it’s web server turned on.
Managed to lock myself out of the thing and had to re-flash it already. (turns out that rolling the VM HA’s in back to a checkpoint that doesn’t have a devices keys was a mistake :rofl:)

I should make it do something useful next really!

I’m currently struggling with figuring out how basic program flow works though tbh.
I like the idea of ending up with it just sending IR signals until you press the button and then it’ll listen for and reporting IR button presses.

I had a play with Arduino stuff nearly 10 years ago… it seems like most, if not all, that goes in the devices yaml is like arduinos void loop()?

1 Like

I think I’ve cracked it. It’s reliably turning my TV and a fan on/off just as I was hoping :partying_face:

Still got to record and then set up a list of other remote buttons, but I’ve got the process for doing that working pretty well.

I may well tweak this a bit more, but in case it mght help out anyone else, this is what I’ve got so far:

esphome:
  name: smartirblaster
  friendly_name: IR_Blaster

bk72xx:
  board: cb3s # https://docs.libretiny.eu/boards/cb3s/

# Enable logging. https://esphome.io/components/logger.html
logger:

# Enable Home Assistant API.
api:
  encryption:
    key: !secret api_encryption_key

ota:
  password: !secret ota_password
  safe_mode: True

# WiFi connection details. Without domain:, defaults to .local. https://esphome.io/components/wifi.html
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  domain: .home
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "${friendly_name} Fallback"
    password: !secret fallback_password
captive_portal:

# Enable web server, browse to http://HOSTNAME or http://IP_Address. https://esphome.io/components/web_server.html
web_server:
  port: 80
  include_internal: true
  local: true

sensor:
  - platform: wifi_signal # Reports the WiFi signal strength in dB. https://esphome.io/components/sensor/wifi_signal.html
    name: "WiFi Signal dB"          # Uncomment to show signal DB in HA
    id: wifi_signal_db
    update_interval: 60s            # Report signal every minute
    entity_category: "diagnostic"
    device_class: "signal_strength"
    disabled_by_default: true       # Shows entity in HA, but disabled by default
  - platform: copy                  # Reports the WiFi signal strength %
    source_id: wifi_signal_db
    name: "WiFi Signal"
    filters:
      - lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
    unit_of_measurement: "%"
    entity_category: "diagnostic"
    device_class: "signal_strength"
    disabled_by_default: true       # Shows entity in HA, but disabled by default
  - platform: uptime
    id: uptime_seconds
    # update_interval: 10s
    internal: true
    name: "Uptime Seconds"
    entity_category: "diagnostic"
    disabled_by_default: true       # Shows entity in HA, but disabled by default

text_sensor:
  - platform: template              # From https://community.home-assistant.io/t/uptime-sensor-in-hours-or-days/116392/28 Thanks JayElDubya & SpikeyGG
    name: Uptime
    # update_interval: 10s
    icon: mdi:timer-outline
    lambda: |-
      int seconds = (id(uptime_seconds).state);
      int days = seconds / (24 * 3600);
      seconds = seconds % (24 * 3600);
      int hours = seconds / 3600;
      seconds = seconds % 3600;
      int minutes = seconds /  60;
      seconds = seconds % 60;
      if ( days > 3650 ) {
        return { "Starting up" };
      } else if ( days ) {
        return { (String(days) +"d " + String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() };
      } else if ( hours ) {
        return { (String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() };
      } else if ( minutes ) {
        return { (String(minutes) +"m "+ String(seconds) +"s").c_str() };
      } else {
        return { (String(seconds) +"s").c_str() };
      }
    entity_category: "diagnostic"
    disabled_by_default: true       # Shows entity in HA, but disabled by default

# Sync time with Home Assistant
time:
  - platform: homeassistant
    id: ha_time

light:
  - platform: status_led      # Used as a status LED during errors, and status_light when OK. https://esphome.io/components/light/status_led
    pin: P8
    id: status_light
    name: LED                 # Makes it show in HA
    disabled_by_default: true # Shows entity in HA, but disabled by default

binary_sensor:                # Button, https://esphome.io/components/binary_sensor/gpio
  - platform: gpio
    pin:
      number: P6
      inverted: true
      mode:
        input: true
        pullup: true          # Holds it high/off, otherwise it flutters on/off
    name: "Button"
    # icon: "mdi:button-pointer"
    # internal: true          # Hides button from HA
    disabled_by_default: true # Shows entity in HA, but disabled by default
    on_press:                 # Switch LED on/off as a test
      - light.turn_on: status_light
    on_release:
      - light.turn_off: status_light

remote_receiver:
  pin:
    number: P7
    inverted: true
    mode:
      # INPUT_PULLUP # Seems to do the same as seperate input & pullup
      input: true
      pullup: true
  tolerance: 10
  # dump: all # Kept giving Pronoto codes that wouldn't send 'Inconsistent data, not sending'
  # dump: pronto
  dump: raw

remote_transmitter:
  pin: P26
  # Infrared remotes use a 50% carrier signal
  carrier_duty_percent: 50%

switch:
  - platform: template
    name: TV Power
    turn_on_action:
      - remote_transmitter.transmit_raw:
          carrier_frequency: 38kHz
          code: [4376, -4566, 492, -1729, 521, -1720, 531, -1723, 528, -594, 531, -594, 538, -588, 531, -594, 500, -625, 531, -1726, 525, -1687, 538, -1711, 531, -594, 531, -600, 524, -594, 531, -594, 531, -594, 531, -593, 531, -1719, 500, -631, 493, -625, 500, -625, 499, -594, 563, -568, 525, -593, 531, -1719, 537, -587, 687, -1563, 531, -1727, 522, -1718, 506, -1743, 500, -1760, 490, -1718, 531]
  - platform: template
    name: Fan On
    turn_on_action:
      - remote_transmitter.transmit_raw:
          carrier_frequency: 38kHz
          code: [1155, -438, 1250, -403, 437, -1250, 1291, -396, 1282, -406, 437, -1260, 428, -1250, 437, -1264, 434, -1240, 437, -1250, 1295, -393, 469, -7947, 1303, -406, 1291, -397, 468, -1219, 1281, -413, 1275, -406, 437, -1250, 441, -1250, 434, -1251, 437, -1263, 425, -1250, 1281, -410, 468, -7948, 1299, -406, 1282, -406, 447, -1240, 1250, -438, 1263, -424, 438, -1278, 409, -1254, 437, -1266, 418, -1250, 444, -1247, 1247, -438, 437, -7979, 1281, -428, 1250, -437, 438, -1263, 1237, -437, 1250, -438, 419, -1268, 407, -1281, 406, -1294, 425, -1250, 406, -1294, 1237, -438, 406]

Please be kind, it’s my first time doing anything with an ESP or ESPHome
I’ve defined the button but aren’t using it.
The LED is just a status LED in case of error, but probably could be used in some other way as well?
Any suggestions are welcome

5 Likes

Great work. Thanks for sharing.

1 Like

Hey man, so following on from here: Open source ESPHome IR blaster? - #41 by WaresMyHass

Just gonna re-paste with some comments


# Home Assistant controlled fake switch - to enable "learning mode"
switch:
  - platform: template
    name: Learn Command
    id: remote_learn_switch
    optimistic: true

# Home Assistant sensor to store remote command data
text_sensor:
  - platform: template
    name: Learned Command
    id: learned_command

remote_receiver:
  pin: 
    number: GPIO25
    inverted: true
  dump: all
  # On all received pronto data
  on_pronto:
    - if:
        condition:
          # If Remote Learn switch is on
          lambda: 'return id(remote_learn_switch).state;'
        then:
          # - Turn learn switch off
          # - Build data string
          # - Publish data string to text sensor
          - lambda: |-
              id(remote_learn_switch).publish_state(false);
              std::string log_output = "Pronto: Data = " + esphome::to_string(x.data);
              id(learned_command).publish_state(log_output.c_str());
          # Blink status LED
          - delay: 80ms
          - light.turn_on: ir_status_light
          - delay: 500ms
          - light.turn_off: ir_status_light
  # Using SONY protocol instead
  # on_sony:
  #   - if:
  #       condition:
  #         lambda: 'return id(remote_learn_switch).state;'
  #       then:
  #         - lambda: |-
  #             id(remote_learn_switch).publish_state(false);
  #             std::string log_output = "Sony: Data = 0x" + esphome::format_hex(x.data) + ", nbits = " + esphome::to_string(x.nbits);
  #             id(learned_command).publish_state(log_output.c_str());
  #         - delay: 80ms
  #         - light.turn_on: ir_status_light
  #         - delay: 500ms
  #         - light.turn_off: ir_status_light

1 Like

Thanks a lot.
I’ve tried to add this to my own though, and can’t get it to compile. I get:

/config/esphome/ir-blaster.yaml: In lambda function:
/config/esphome/ir-blaster.yaml:187:50: error: no match for 'operator+' (operand types are 'const char [16]' and 'esphome::remote_base::ProntoData')
  187 |               std::string log_output = "Pronto: Data = " + x;
      |                                ~~~~~~~~~~~~~~~~~ ^ ~
      |                                |                   |
      |                                const char [16]     esphome::remote_base::ProntoData
In file included from src/esphome/core/entity_base.h:5,
                 from src/esphome/components/binary_sensor/binary_sensor.h:4,
                 from src/esphome/core/controller.h:5,
                 from src/esphome/components/api/api_server.h:9,
                 from src/esphome/components/api/api_connection.h:6,
                 from src/esphome.h:3,
                 from src/main.cpp:3:
src/esphome/core/string_ref.h:118:20: note: candidate: 'std::string esphome::operator+(const char*, const esphome::StringRef&)'
  118 | inline std::string operator+(const char *lhs, const StringRef &rhs) {
      |                    ^~~~~~~~
src/esphome/core/string_ref.h:118:64: note:   no known conversion for argument 2 from 'esphome::remote_base::ProntoData' to 'const esphome::StringRef&'
  118 | inline std::string operator+(const char *lhs, const StringRef &rhs) {
      |                                               ~~~~~~~~~~~~~~~~~^~~
src/esphome/core/string_ref.h:124:20: note: candidate: 'std::string esphome::operator+(const esphome::StringRef&, const char*)'
  124 | inline std::string operator+(const StringRef &lhs, const char *rhs) {
      |                    ^~~~~~~~
src/esphome/core/string_ref.h:124:47: note:   no known conversion for argument 1 from 'const char [16]' to 'const esphome::StringRef&'
  124 | inline std::string operator+(const StringRef &lhs, const char *rhs) {
      |                              ~~~~~~~~~~~~~~~~~^~~
Compiling .pioenvs/smartirblaster/lib295/ESPAsyncWebServer-esphome/WebResponses.cpp.o
*** [.pioenvs/smartirblaster/src/main.cpp.o] Error 1
========================= [FAILED] Took 98.04 seconds =========================

I guess it might be a type mismatch? I’m not sure how to get it going though. Got any idea what I’m doing wrong pls?

Sorry there was a typo on this line
std::string log_output = "Pronto: Data = " + esphome::to_string(x.data);
I updated the post above

That’s got it to compile OK
I might still be getting something basic wrong here though. Sorry to make you sharing something a pain.

I press the Learn Command button in HA, then press a button on a remote. The log shows Pronto data it’s received, but I see nothing happen in HA.
The Learned Command sensor’s there in HA, showing as Unknown and doesn’t change.

I noticed Pronto: Data = doesnt show anywhere in the log, so tried changing that to Pronto: data= (no space before =) that does…
Then I get this in the log:

[17:43:48][D][switch:012]: 'Learn Command' Turning ON.
[17:43:48][D][switch:055]: 'Learn Command': Sending state ON
[17:43:53][D][switch:055]: 'Learn Command': Sending state OFF
[17:43:53][D][text_sensor:064]: 'Learned Command': Sending state 'Pronto: data= 0000 006D 0022 0000 00AA 00AD 0013 0044 0014 0042 0014 0044 0013 0019 0011 001A 0012 001A 0011 0019 0012 0019 0012 0044 0013 0043 0014 0044 0013 0019 0012 0019 0013 0017 0014 0019 0014 0018 0012 0019 0011 001A 0013 0018 0012 0019 0013 0019 0012 0043 0014 0044 0013 0019 0012 0044 0013 0043 0014 0044 0012 0044 0012 0044 0013 0018 0014 0019 0013 0042 0014 0181 06C3'

but see nothing change with Learned Command in HA?
What should happen?

Hmmm I think what’s going on here is that it’s too long for the text sensor and being rejected by HA…

So far I’ve been mostly using it with other protocols which are more compact, I’ll have a think how to handle that better unless you’ve got some ideas?

A text sensor is probably not the best option…

I’m just fumbling my way into ESPHome and ESPs in gneral tbh.
Well up for trying things out but have little to offer that might help, sorry.

So state data is definitely limited to 255 characters: WTH is 255 the max length for states?

A workaround is using state attributes instead of states, but esphome can’t publish to state attributes :upside_down_face: - Is esphome possible to expose entities to ha with attributes?

I don’t think this method I was using will work well for pronto data… If you’re using another protocol from the list which uses shorter data it will, but 9/10 you’re probably using pronto format

Looks like you’ll just have to use dump: all and follow the log output…

I was hoping to get your pronto approach working and then change things to use raw. Raw will probably end up even longer though.

Most/all of my remotes are detected as pronto, but then the data that’s logged seems pretty useless. At least I’ve not found how to use it.
Almost always ends up with Inconsistent data, not sending when I try to send it.
I haven’t been able to find why :man_shrugging:

After I capture a few (5-10) raw button presses, discard any obvious outliers, test sending any that are left and select the best responding, raw’s working spot on. Better than it was working while it was Tuya.
It’s a bit of a pain to do it for evey button, but the results are great so far.
This little thing’s got considrably better range/coverage than some of my original remotes.
I’ve got an aquarium light with a cheap IR remote that’s got to be close and pointed at the light just right for it to work. This little thing just has to be in the same room, can even be the opposite side of the fish tank with a load of wood cabinet in the way!

Aha I think I know what your problem is - your receive data is inverted. And your transmitter possibly is too.

If you’re able to read and broadcast RAW data then both your receiver and transmitter must at least be the wrong way around together… :slight_smile:

Try inverted: false on your receive and inverted: true on your transmit.

Are you sure you need the pullup on your receive too?

Without the pullup I get nothing at all.

Looking at the raw, I found not having the inverted always made codes start with a minus. I gather that’s starting with a delay/off, so has to mean it should be inverted?

Hmm yeah okay codes shouldn’t start with a minus…

Not too sure why that is then right now…