Help deciphering IR remote codes for Lasko CC23161 space heater

I have scoured this forum, read the Pronto docs, tried IRScrutinizer and Sensus. I’m still having issues with decoding the remote for my Lasko CC23161 space heater. I have another Lasko heater for which this was easy; the raw dump gave me all I needed and am using raw and Pronto codes successfully with that heater.

The CC23161 remote is more complex. It has a numeric display that shows the temperature setting it is sending to the device and I’ve noticed that it sends that precise number (not just an up/down request). This seems to be how IR Remote Climate devices work.

My remote_receiver pin is properly inverted as confirmed by testing other remotes but when I dump raw data for only this remote the first digit has negative polarity (and did not work when I tried it), so I tried swapping polarity for each value as described here to no avail.

remote_receiver:
  pin:
    number: GPIO14
    inverted: true
    mode: INPUT_PULLUP
  dump: raw

When I dump raw and press the power button I get something like this. Note the remote.raw:041 and remote.raw:028. I don’t know what it means but it must be a part of my problem. Indeed that first “Received Raw:” is blank every time.

[13:40:57.145][I][remote.raw:041]: Received Raw: 
[13:40:57.166][I][remote.raw:028]: Received Raw: -4649, 764, -680, 740, -681, 743, -678, 741, -681, 740, -680, 740, -681, 740, -681, 740, -681, 740, -2102, 739, -682, 740, -682, 739, -682, 740, -682, 739, -682, 739, -682, 740, -682, 739, -682, 740, -681, 739, -681, 740, -682, 739, -682, 
[13:40:57.188][I][remote.raw:028]:   740, -680, 740, -682, 739, -682, 740, -682, 739, -681, 716, -705, 717, -705, 716, -705, 740, -682, 739, -686, 736, -680, 740, -681, 740, -680, 741, -680, 741, -680, 741, -680, 740, -681, 740, -682, 739, -682, 740, -706, 714, -682, 739, -683, 739, -682, 
[13:40:57.217][I][remote.raw:041]:   739, -2105, 738, -682, 739, -683, 739, -682, 739, -707, 714, -706, 714, -708, 713, -708, 714, -707, 714, -707, 713, -2129, 714, -707, 714, -2128, 714, -707, 714, -707, 714, -708, 713, -2129, 713, -708, 713, -2129, 713, -712, 2002, -695, 607

The Pronto code I get when I dump all has two sets of Pronto data with a little Beo4 and a Drayton included.

[15:29:58.962][I][remote.pronto:229]: Received Pronto: data=
[15:29:58.977][I][remote.pronto:237]: 0000 006D 0001 0000 0180 
[15:29:58.977][D][remote.beo4:086]: Beo4: n_sym=133
[15:29:58.977][I][remote.pronto:229]: Received Pronto: data=
[15:29:59.004][I][remote.pronto:237]: 0000 006D 0043 0000 00B4 001E 001A 001D 001A 001D 001A 001D 001A 001D 001A 001D 001A 001D 001A 001C 001B 001D 0051 001D 001A 001D 001A 001D 001A 001D 001A 001D 0051 001D 0051 001D 001A 001D 001A 001D 001B 001C 001A 001D 001B 001C 
[15:29:59.025][I][remote.pronto:237]: 001B 001C 001B 001C 001B 001C 001B 001C 001B 001C 001B 001C 001B 001C 001B 001C 001B 001C 001B 001C 001B 001C 001B 001C 001B 001C 001B 001C 001B 001C 001B 001C 001B 001C 001B 001C 001B 001C 001B 001C 001B 001C 001B 001C 001B 001C 
[15:29:59.032][I][remote.pronto:237]: 001B 001C 0052 001C 001B 001C 001B 001C 001B 001C 001B 001C 001B 001C 001B 001C 001B 001C 001B 001C 001B 001C 0052 001C 001B 001C 0052 001C 001B 001C 001B 001C 0052 001C 001B 001C 001B 001C 001B 001C 001B 004D 001B 0018 0181 
[15:29:59.048][V][remote.drayton:187]: Decode Drayton: Fail 2, - -683 739 -681

And if I run that larger set of Pronto data through the IRScrutinizer it comes back with an error. The CCF is invalid since it has an odd number (137) of durations. I have converted the raw output to Pronto with IRScrutinizer and with Sensus but that didn’t work. I also tried the Pronto (and raw) I logged when switching the pin to inverted: false.

If I hold the power button down the signal does not repeat. The samples I have provided in this post are good examples of what I log every time I press the button. I have tried two separate ESP8266 modules and one ESP32, each with its own completely unique components, all well-powered with 5V, and the logs are all the same. My main board is the one below. The problem is surely the complexity of the remote. I wonder if anyone has any ideas.

Edit: Here is my current yaml, based on raw data (each value’s polarity swapped).

remote_transmitter:
  pin: GPIO4
  carrier_duty_percent: 50%


button:
  - platform: template
    name: "Test1"
    id: test1
    on_press:
      - remote_transmitter.transmit_raw:
          code: [4649, -764, 680, -740, 681, -743, 678, -741, 681, -740, 680, -740, 681, -740, 681, -740, 681, -740, 2102, -739, 682, -740, 682, -739, 682, -740, 682, -739, 682, -739, 682, -740, 682, -739, 682, -740, 681, -739, 681, -740, 682, -739, 682, -740, 680, -740, 682, -739, 682, -740, 682, -739, 681, -716, 705, -717, 705, -716, 705, -740, 682, -739, 686, -736, 680, -740, 681, -740, 680, -741, 680, -741, 680, -741, 680, -740, 681, -740, 682, -739, 682, -740, 706, -714, 682, -739, 683, -739, 682, -739, 2105, -738, 682, -739, 683, -739, 682, -739, 707, -714, 706, -714, 708, -713, 708, -714, 707, -714, 707, -713, 2129, -714, 707, -714, 2128, -714, 707, -714, 707, -714, 708, -713, 2129, -713, 708, -713, 2129, -713, 712, -2002, 695, -607]
          carrier_frequency: 38kHz

Reverse engineering fun.

For interest, see what koyaanisqatsi is doing in his journey of discovery.
So far he is doing ASK/OOK codes, but will be going into exploring IR codes for his HVAC remote. The thread might give you the insights to try different supplementary tools, approaches, or opportunity to collaborate.

Idea: Would an IR sender receiver pair on an existing spare ESP32 configured with something like Tasmota assist to unravel some of those codes?

1 Like

Post the output of this for just single press (don’t hold the button)

remote_receiver:
  pin:
    number: GPIO14
    #inverted: true
    mode: INPUT_PULLUP # Use only if you surely know it's needed
  dump: raw
  buffer_size: 4096
  idle: 50000us

I created an IR Receiver and I could never get it to work trying to use Pronto codes because they weren’t consistent when pushing same button. I had to add this to my YAML so that it would use the NEC infrared transmission protocol:

on_nec:
    then:
      - lambda: |-
          char buf[32];
          sprintf(buf, "%d %d", x.address, x.command);
          id(irreceiver_last_ir_code).publish_state(buf);
      - delay: 200ms
      - lambda: |-
          id(irreceiver_last_ir_code).publish_state("00");

After doing so, it would output consistent values. I don’t understand the details; I had artificial help. Evidently, there are a few different IR transmission protocols that you can try.

My next problem was that I couldn’t figure out was how to make it work so that HA would react to repeated pushes of same button in my Node Red automation. My workaround is publishing the state of ‘00’ 200ms later.

1 Like

Can’t help with your issue, but can maybe point you in the right direction.

Read earlier that the latest Esphome 2025.11 release has added support for 3 new IR protocols.
Maybe one of the new ones might give you an easier time?

1 Like

That did the trick! I took the output from that, which still gave me negative polarity, and I pasted the code into a spreadsheet where I could swap the polarity for each value. And it works! You’ll notice a subtle difference between the first and second set of values. The first has a -10130, 4675, -769, 2075 and the second has a 653. One is power On and one is power Off. I have created two buttons, accordingly (with polarity swapped: 10130, -4675, 769, -2075…) and will continue with other settings.

This is great! Thank you so much!

Edit: I see I can just invert the pin and get the correct polarity. I’m on my way!

[07:44:14.382][I][remote.raw:028]: Received Raw: -10130, 4675, -769, 2075, -768, 654, -768, 656, -766, 654, -769, 653, -768, 654, -769, 654, -768, 654, -768, 2100, -743, 679, -744, 678, -743, 678, -744, 679, -743, 2102, -743, 2100, -743, 679, -744, 677, -744, 679, -743, 679, -744, 678, 
[07:44:14.405][I][remote.raw:028]:   -744, 678, -743, 679, -743, 680, -744, 678, -743, 679, -744, 679, -743, 680, -744, 678, -743, 679, -743, 680, -742, 679, -743, 679, -743, 679, -743, 680, -743, 680, -742, 680, -742, 680, -743, 680, -743, 679, -743, 679, -743, 680, -743, 681, -742, 679, 
[07:44:14.429][I][remote.raw:041]:   -743, 680, -742, 2102, -742, 682, -741, 678, -743, 680, -743, 680, -742, 679, -743, 680, -743, 680, -743, 680, -743, 679, -742, 2102, -742, 680, -743, 679, -742, 680, -743, 680, -743, 2102, -742, 680, -742, 680, -743, 680, -743, 683, -2033, 669, -634
[07:44:17.429][I][remote.raw:028]: Received Raw: -10133, 4675, -767, 653, -768, 654, -769, 653, -769, 654, -768, 653, -773, 648, -769, 654, -768, 654, -768, 2075, -772, 650, -768, 655, -768, 654, -768, 655, -767, 2077, -767, 2076, -767, 654, -768, 653, -768, 655, -767, 655, -768, 657, 
[07:44:17.454][I][remote.raw:028]:   -765, 678, -743, 679, -743, 679, -743, 680, -742, 679, -742, 679, -743, 679, -742, 680, -743, 679, -743, 680, -743, 679, -742, 680, -742, 680, -743, 680, -743, 679, -743, 679, -746, 678, -741, 679, -742, 679, -743, 680, -743, 680, -742, 680, -742, 681, 
[07:44:17.477][I][remote.raw:041]:   -742, 679, -743, 2101, -742, 680, -742, 680, -742, 679, -742, 680, -743, 680, -742, 681, -742, 679, -742, 679, -742, 681, -742, 2101, -742, 681, -742, 2102, -742, 680, -743, 683, -738, 2102, -742, 680, -742, 680, -742, 681, -742, 683, -2032, 667, -636

That’s what I was looking for.
Protocol is simple: 700, -700 is zero and 700, -2100 is one.
But there’s very uncommon tail at the end 2000, -700.

So if I replace all the values with +/-700 and -2100 accordingly the transmit code will be cleaner?

Just curious, would you expect the code to work if I convert it to Pronto? Now that I’ve been playing with tools I just wonder what the result would be.

The increased buffer size of 4096 makes sense. Is the idle: 50000us setting a good setting to keep for all future devices I would try to decode? Or was there something about the output I pasted in my initial post that led you to believe idle: 50000us would be beneficial in this case?

It doesn’t have to be cleaner.
The tolerances here are huge. That IC on your original remote is usually dead simple and the timing can be easily 15% off. If you put fresh battery, you might have shorter pulses… The receiver does demodulation and filtering and can add noticeable “error” to the timings you receive.

Could easily be that the timings coded to the remote were 9000, -4500 header with 650, -650 / 650, -1950 0/1-pulses for example.
It’s more important to understand the “structure” of these signals.
Yours could be decoded as 64-bit NEC, the timings fit in the tolerances.

I suggested to use large buffer, because Esp8266 has default buffer size smaller than typically long AC signals. It wasn’t needed here.

I suggested to use long idle to be sure to capture whole signal in one piece, many protocols have long gaps in signal. It wasn’t needed here.

1 Like

Do you have any idea why there is remote.raw:028 and remote.raw:041?

Afaik it doesn’t mean anything.
Esphome splits long signals on log and 041 likely identify that it’s last part of it.

1 Like

So what’s your next step? You can go with raw signals if you just have certain quantity of commands you need to send. Adding more, at some point you run to memory issues and your only option is to decode it at least partially. There the fun begins…

The heater temp settings go all the way down to 39°F which is peculiar since it has no cooling function. I only need it to work from 70° up to its max of 90° so I added buttons with raw code for 70, 75, 80, 85, and 90°. There are a few other settings (oscillation has separate start/stop signals, as does the vent blinds that go up and down, and there is a fan-only vs heater capability).

I was able to add the raw code for all the features for this large Lasko heater as well as the simple Lasko I mentioned in my initial post. I did try replacing the dump: raw with dump: nec and no codes came through, so I just used raw.

It works great. If you have a suggestion of another protocol to try since nec didn’t work, I’d be willing to give it another try if you’re curious.

So it doesn’t work like air conditioner/ heatpump protocols, which send all the parameters on every signal. Good for you.

Nec is 32bit protocol and yours is 64bit plus that weird tail I mentioned.
I don’t think it’s worth to play at this point since you were able to do it all as raw.
Situation was different if it was like AC protocols, just 5 different temps combined with oscillation, vent blinds and fan would make 40 different signals instead of 11.

I see. This heater is new to me so I did not know much about it. Nor do/did I know much about IR protocols so perhaps I misled the community a bit.

You will not be surprised to know that if the unit is oscillating and I change another setting, the oscillation stops. So the remote is more simple than I had proposed.

It is good to know that remotes can be much more complex than this.

Oh yeah. Some protocols are 400+ bits. I decoded few AC protocols long time ago and after staring at the bits for days/weeks my eyes we so red only Dracula could beat.

1 Like

If the idle: 50000us was long enough for my 64bit signal, would idle: 0.35s likely enable someone with a 400bit signal to pull the raw code in the same manner as I did (perhaps with an ESP32 with more memory)? I mean to say, so long as the idle is sufficiently long and other settings are properly configured, can these devices always pull raw code that will work?

Edit: Part two of this question:

I had considered soldering an IR receiver onto an ATTINY85 and pulling codes from my Windows PC. I don’t know whether such a device would be able to work with IRScrutinizer but I suspect it would. It seems that it would at least work with the Arduino IDE. Would such a method have any advantage over doing it with an ESP board?

Long idle is needed only to read signal that has long gaps in one piece, it’s not related to the bit-count. Longest gaps I remember I have seen were 30ms.
Buffer size is parameter for bit-count.

If receiver is good quality and carrier frequency is matching, generally yes.

Using Attiny85 for long signals would just complicate your life, it doesn’t have enough memory to save the complete raw timings in buffer. You would need to compress them or decode on the fly or print them out in pieces.
What you mean with “pulling codes from PC”?

I presumed the Attiny85 would pass the IR signal to the PC, for the PC to do the processing, with no need for the Attiny85 to be involved such as to require a buffer. I thought the PC could be used to “pull the code” this way.

So perhaps an ESP32 with an IR receiver attached is the best way, since it has a default buffer of 10kb in ESPHome. The ESP32 I had used in my attempts earlier was not designated as only an IR receiver and perhaps the other sensors I had connected to it impacted its ability to provide the correct code. The IR receiver is on GPIO26.

Maybe, if you know how to code that on both sides…