Saturday, October 15, 2022

IR-controlling the new air conditioner in the vintage server room

Computers are hot. No, I mean, they're hot. They heat our house in the winter here in primarily sunny Southern California (not as much as my wife would like, but that's another story for another day).

Previously we talked about monitoring the portable air conditioning unit keeping the server room cool. The SMS gateway, basically an overgrown Raspberry Pi, uses a USB decibel meter to report ambient noise volume in the room, from which we can extrapolate the state of the air conditioner's compressor and fan. The Sawtooth Power Mac G4 file server (and radio station) logs the temperature and humidity with a USB sensor of its own. The portable A/C unit exhausts warm air through a duct to the roof so the room can remain secure, and a turbine spins the hot air away into the atmosphere.

Unfortunately, what all this monitoring showed was the A/C unit was crapping out. The house central A/C's compressor died abruptly this summer after over two decades, naturally during the hottest part of the year, and it took nearly a week and $14,000 to get it dealt with (it was an old R-22 system and thus everything had to be replaced). The portable A/C was itself almost 11 years old by this time, and after that bad week running nearly non-stop started making an ominous unbalanced low warble from its exhaust fan. This got to the point where it was unable to clear its own condensate and a couple days where I had to empty the drain pan about every 12 hours, versus never having to before (not a great deal of humidity here). I like old computers, but I try not to accumulate old broken computers, and that goes double for old broken air conditioners. It was time for a replacement.

When sizing replacement A/C units, remember that in the United States manufacturers only reported the ASHRAE BTU cooling capacity until 2017 (this is a nice explanation). The old LG was a 11,000 BTU unit (LP1111WXR) using R-401A which I bought off-the-rack from Home Depot and installed and insulated the duct myself, suitable to cool the volume of a medium-sized bedroom. Or, a medium sized bedroom with a whole bunch of computers in it. Fortunately the heat doesn't end in my corner of sunny So Cal until around November, so when I went shopping Home Depot still had a selection of portable A/Cs in stock even this "late" in the season. Although the new one I selected (an LG LP0721WSR) says it's "only" 7,000 BTU, that's actually using U.S. Department of Energy standards, which is the newer measurement. Convert it back to ASHRAE BTUs and it's a 12,000 BTU unit per LG's spec sheet — but full tilt pulls "just" 970W as opposed to the 1200W of the old unit, and is about 75% the size. The difference is not only better technology but the greater efficiency of R-32, requiring 40% less refrigerant for the same cooling and having almost 13% greater cooling capacity. Unlike vintage computers and vintage nerds, vintage air conditioning units just don't age well. (* A note here: Home Depot and LG are not sponsors. I'm just a customer telling you what I bought.)

It's important to keep in mind, though, that a consumer portable A/C unit is for normies. You know, people who play PS5 and watch Netflix and maybe read a book. Normies get up and fiddle with the controls and turn the A/C off when they leave. Normies don't have a room with an IBM POWER6, Sawtooth G4 (and its FireWire RAID), Mac mini G4, Macintosh IIci, Alpha Micro Eagle 300, Cobalt RaQ and associated IoT devices and network backbone infrastructure running non-stop (to say nothing of the Apple Network Server 500 and HP 9000/350 that also occasionally come out to play).

The previous unit nevertheless worked well enough, even if occasionally it would be seized with a desire to freeze the room and plunge it down into the high 60s Fahrenheit until I noticed it or turned off its remotely controllable power outlet (which then caused it to forget its settings when it turned back on, though with a 72°F default setpoint it would at least "fail cool"). In particular it had a very nice energy saver feature which essentially turned off the fan entirely when the unit was idle: when a cooling cycle stopped, it shut down everything and was practically silent until the setpoint caused it to turn back on the compressor again. This meant I could leave the portable A/C on with the server room's door open overnight and use little power during early spring and late fall when it could passively cool, but not have its fan pointlessly add irregular air noise over the computers' orderly fan noise. After all, we're trying to sleep just a couple doors down. (In winter I simply turned it off completely.)

The new one doesn't have an "energy saver" mode per se, because it already has several energy saving features. Besides being more efficient in general, it can run its compressor at half-speed (the old one was either Arctic blast or bust), and automatically turns the fan down — but not off — between cooling cycles. The modes have distinctly different decibel volumes which can be easily distinguished on the USB audiometer, too. And it's paying off: I'm seeing about 15% less power usage compared to the old one despite similar outdoor temperatures during the past couple weeks. But it also makes it less liveable because its fan is more shrill and I can't turn it off completely without turning the entire unit off completely, and it also has a tendency to overchill the room due to heat patches I could see with my infrared thermometer (and since it's slightly more powerful than the old one, it can do so with even less effort). There weren't many options for repositioning it due to the exhaust duct, and putting in a small circulation fan to "help" guide cold air return to its thermostat didn't seem to do much of anything except make the room noisier. Yes, there's an automatic shutdown and startup timer, but that requires me to actually set it each time, and I'm a really lazy dude. Plus, even though I could remotely turn off its power outlet if necessary, doing so was basically the home power nerd equivalent of a rolling blackout and didn't seem particularly good for the hardware as a regular means of control.

At this point I realized the solution to my woes was literally in the palm of my hand:

Most portable A/Cs and split systems come with infrared remote controls, and so did this one. Surely there was some way to get the Sawtooth Power Mac G4 that was already recording the room temperature to send an IR signal to control the A/C unit ... right?

To do this I would need something to actually receive the signal (an IR receiver), and then something to transmit it (an IR blaster). Because I was hoping to connect it to an old Power Mac running Mac OS X Tiger, that meant drivers were going to be an issue. On Linux many people doing this use lirc, and lirc supports a great many devices, but the MacPorts version supports rather fewer and doesn't seem to be tested on PowerPC at all. After looking around a few places I ran across irblaster.info, who sells a varied selection of IR doodads. Again, they aren't a sponsor, but it's clear this is a small business that takes pride in its products and Mike, the operator, replied to all my questions and had no problem with me (spoiler alert) hacking the units he builds and sells.

Notably in addition to the more typical USB and RS-232 units the site offers two Arduino Nano-based solutions, both a receiver and a blaster. The advantage of these is the Arduino has all the intelligence (using IRLib2, itself based on Ken Shirriff's IRemote — hi Ken!), so you just tell it what to send over a serial port; no mucking about with a lirc install is necessary. Because the Arduino Nano's USB serial port uses a CH340G, for which no drivers exist on Power Macs (there are drivers for Prolific and FTDI chipsets, but not those), I bought the true RS-232 version of the Arduino blaster. This provides a regular DE-9 serial port with a level shifter and I planned to use that with a PL-2303 USB to serial converter, for which I had known-working Power Mac drivers. The regular Arduino USB receiver with my Raptor Talos II workstation running Fedora would suffice for actually getting the IR codes.

This is the receiver. The Arduino Nano is neatly housed in a little 3-D printed case with a 30-60kHz receiver connected to its GPIO pins. It connected right up to my Talos II and appeared as /dev/ttyUSB1.

IR encoding consists of groups of IR pulses at a chosen fixed signal frequency consolidated into marks (high) and spaces (low). This is necessary so that infrared radiation from other sources like, I dunno, you, doesn't trigger a sensor; it has to be presented as a specific sort of modulated signal. On top of that, IR-controlled devices have their own protocols such as how long a mark followed by a space or vice versa counts as a 1 or a 0, their particular identification and command sequences, and so on in order that they won't interfere with each other. You can always play back a raw sequence of marks and spaces but lirc and IRLib recognize specific manufacturer sequences and can generate them on the fly for particular commands.

Naïvely (because if I'd gotten this right, this would be a much shorter and less interesting story), I figured that the LG would do the same. I plugged in the device, started cu on that port at 9600bps, pointed the remote at it and pressed a random button to see what it would do.

% sudo cu -l/dev/ttyUSB1 -s9600
[sudo] password for censored: 
Connected.
Ready to receive IR signals

#define RAW_DATA_LEN 100
uint16_t rawData[RAW_DATA_LEN]={
                                        3118, 1562, 582, 1022, 586, 1014, 590, 222, 
        630, 226, 578, 262, 590, 990, 590, 262, 
                                                        562, 266, 590, 990, 610, 998, 582, 266, 
                        562, 1014, 594, 258, 542, 262, 590, 1014, 
                                                                        590, 1014, 590, 262, 566, 1014, 586, 1018, 
                                        586, 266, 562, 266, 562, 1014, 566, 266, 
        586, 262, 566, 1014, 562, 266, 590, 262, 
                                                        562, 266, 562, 262, 542, 266, 586, 262, 
                        566, 262, 566, 262, 562, 266, 538, 266, 
                                                                        586, 266, 562, 262, 566, 266, 562, 262, 
                                        566, 262, 566, 262, 566, 262, 566, 266, 
562, 262, 562, 266, 562, 1018, 586, 262, 
                                                566, 262, 566, 1000};


Decoded Unknown(0): Value:0 Adrs:0 (0 bits) 
Raw samples(100): Gap:48610
  Head: m3118  s1562
0:m582 s1022    1:m586 s1014             2:m590 s222    3:m630 s226             
4:m578 s262     5:m590 s990              6:m590 s262    7:m562 s266             
8:m590 s990     9:m610 s998              10:m582 s266   11:m562 s1014           
12:m594 s258    13:m542 s262             14:m590 s1014  15:m590 s1014           

16:m590 s262    17:m566 s1014            18:m586 s1018  19:m586 s266            
20:m562 s266    21:m562 s1014            22:m566 s266   23:m586 s262            
24:m566 s1014   25:m562 s266             26:m590 s262   27:m562 s266            
28:m562 s262    29:m542 s266             30:m586 s262   31:m566 s262            

32:m566 s262    33:m562 s266             34:m538 s266   35:m586 s266            
36:m562 s262    37:m566 s266             38:m562 s262   39:m566 s262            
40:m566 s262    41:m566 s262             42:m566 s266   43:m562 s262            
44:m562 s266    45:m562 s1018            46:m586 s262   47:m566 s262            

48:m566
Extent=55078
Mark  min:538    max:630
Space min:222    max:1022

The Arduino Nano's default sketch as shipped in the receiver is a hybrid of IRLib2's demonstration raw receive and dump sketches, which emits the raw samples and then tells you if the library can make any sense out of it. However, it looks like IRLib apparently couldn't recognize the encoding (if it had, it would display it in the Decoded stanza). No problem, I said! I have the raw data and the blaster supports raw transmission, so I took this transcript and hooked up the RS-232 blaster to the Sawtooth G4 with the PL-2303 dongle. A separate USB plug provides power.

And here it is with the blaster dangling in front facing the A/C. The fire extinguisher is for the R-32. (Just kidding! It's actually for the computers! Still just kidding! Hopefully!)
Mac OS X 10.4 does not have cu. It does have GNU screen, and that will work as a serial terminal, but I wanted something a little less like hitting a fly with a Buick and potentially more easily scriptable. Fortunately, there's a simple terminal program out there that will run on pretty much any computer ever made (I even used it myself on my Commodore 128 for dialup access before I got my Mac IIsi): good old Kermit. And of course Kermit comes in a single-binary all-in-one-file for Mac OS X 10.4 on PowerPC.

By default the Arduino Nano in the blaster runs this sketch. Because it's just a little two-wire bitbanger and it doesn't echo what you type, we'll configure Kermit accordingly. It also communicates at 9600bps.

% kermit -l /dev/tty.usbserial -b 9600
C-Kermit 9.0.300 OPEN SOURCE:, 30 Jun 2011, for Mac OS X 10.4.11
 Copyright (C) 1985, 2011,
  Trustees of Columbia University in the City of New York.
Type ? or HELP for help.
(/Users/censored/) C-Kermit>set carrier-watch off
(/Users/censored/) C-Kermit>set local-echo on
(/Users/censored/) C-Kermit>connect
Connecting to /dev/tty.usbserial, speed 9600
 Escape character: Ctrl-\ (ASCII 28, FS): enabled
Type the escape character followed by C to get back,
or followed by ? to see other options.
----------------------------------------------------
irblaster.info RS232 IR TX 0.2
RAW,100,3118,1562,582,1022,586,1014,590,222,630,226,578,262,590,990,590,262,562,266,590,990,610,998,582,266,562,1014,594,258,542,262,590,1014,590,1014,590,262,566,1014,586,1018,586,266,562,266,562,1014,566,266,586,262,566,1014,562,266,590,262,562,266,562,262,542,266,586,262,566,262,566,262,562,266,538,266,586,266,562,262,566,266,562,262,566,262,566,262,566,262,566,266,562,262,562,266,562,1018,586,262,566,262,566,1000
RAW IR Sent

Aaaaaand ... nothing happened. Just to make sure I did it right, I plugged the receiver into my M1 MacBook Air (Apple provides CH340G drivers), took it into the server room, pointed the blaster at it and sent the sequence again. The receiver faithfully reported all the samples as entered but the A/C completely ignored them. So what gives?

One thing I did know is that unlike TVs and such, A/C and split system remotes don't send individual keypresses. Instead, to ensure everything is set correctly, every button press on the remote causes the entire state (mode, thermostat set point, fan speed, etc.) to be retransmitted to the unit. If the unit gets out of sync with the remote such as after, say, a power failure, the next time the remote sends a command, the unit will get the entire new state with that command and thus its settings will correctly match what the remote says they should be.

My working theory was that it wasn't sampling the full command, so I had to figure out how much capacity IRLib2 has for acquiring raw samples. It turns out by default it maxes out at 100, so odds were good my theory was correct because it would be very unusual indeed for a random vendor's remote to emit exactly that many samples. After increasing RECV_BUF_LENGTH to 255 I merged those receiver sketches and wrote my own, changing the format of the raw samples so that I just had to cut and paste to send to the blaster, and tested it with another sequence. This time we got something rather longer:

RAW,228,3122,1558,586,1018,586,1018,586,222,606,266,562,222,606,1018,586,266,538,262,566,1038,566,1042,586,226,578,1038,566,266,586,266,562,1018,586,1018,590,254,574,1014,562,1042,586,226,606,222,606,1014,590,222,582,262,590,1014,586,226,606,262,566,222,606,262,538,266,590,262,538,266,586,266,566,262,538,266,562,266,590,266,558,226,578,266,590,262,566,222,582,262,590,226,578,262,590,262,566,1014,590,262,562,266,538,1042,586,1018,590,262,566,222,606,262,562,226,606,262,562,226,606,262,538,266,590,1014,586,226,578,266,590,262,562,226,578,266,590,262,566,1014,586,226,578,1042,590,1014,590,1014,590,262,566,222,606,262,562,226,606,262,566,222,606,262,538,266,590,262,562,226,606,262,538,266,614,222,578,226,578,266,586,266,562,262,566,222,606,222,606,266,562,266,562,262,566,222,606,262,566,222,606,262,566,1014,590,222,606,262,566,262,566,1014,590,222,582,1038,590,1014,590,1014,590,1018,590,1010,590,222,606,262,566,266,562,1018,586,222,606

Decoded Unknown(0): Value:0 Adrs:0 (0 bits) 
Raw samples(228): Gap:13626
  Head: m3122  s1558
0:m586 s1018	1:m586 s1018		 2:m586 s222	3:m606 s266		 
4:m562 s222	5:m606 s1018		 6:m586 s266	7:m538 s262		 
8:m566 s1038	9:m566 s1042		 10:m586 s226	11:m578 s1038		 
12:m566 s266	13:m586 s266		 14:m562 s1018	15:m586 s1018		 

16:m590 s254	17:m574 s1014		 18:m562 s1042	19:m586 s226		 
20:m606 s222	21:m606 s1014		 22:m590 s222	23:m582 s262		 
24:m590 s1014	25:m586 s226		 26:m606 s262	27:m566 s222		 
28:m606 s262	29:m538 s266		 30:m590 s262	31:m538 s266		 

32:m586 s266	33:m566 s262		 34:m538 s266	35:m562 s266		 
36:m590 s266	37:m558 s226		 38:m578 s266	39:m590 s262		 
40:m566 s222	41:m582 s262		 42:m590 s226	43:m578 s262		 
44:m590 s262	45:m566 s1014		 46:m590 s262	47:m562 s266		 

48:m538 s1042	49:m586 s1018		 50:m590 s262	51:m566 s222		 
52:m606 s262	53:m562 s226		 54:m606 s262	55:m562 s226		 
56:m606 s262	57:m538 s266		 58:m590 s1014	59:m586 s226		 
60:m578 s266	61:m590 s262		 62:m562 s226	63:m578 s266		 

64:m590 s262	65:m566 s1014		 66:m586 s226	67:m578 s1042		 
68:m590 s1014	69:m590 s1014		 70:m590 s262	71:m566 s222		 
72:m606 s262	73:m562 s226		 74:m606 s262	75:m566 s222		 
76:m606 s262	77:m538 s266		 78:m590 s262	79:m562 s226		 

80:m606 s262	81:m538 s266		 82:m614 s222	83:m578 s226		 
84:m578 s266	85:m586 s266		 86:m562 s262	87:m566 s222		 
88:m606 s222	89:m606 s266		 90:m562 s266	91:m562 s262		 
92:m566 s222	93:m606 s262		 94:m566 s222	95:m606 s262		 

96:m566 s1014	97:m590 s222		 98:m606 s262	99:m566 s262		 
100:m566 s1014	101:m590 s222		 102:m582 s1038	103:m590 s1014		 
104:m590 s1014	105:m590 s1018		 106:m590 s1010	107:m590 s222		 
108:m606 s262	109:m566 s266		 110:m562 s1018	111:m586 s222		 

112:m606
Extent=119730
Mark  min:538	 max:614
Space min:222	 max:1042

Now we're getting somewhere! While IRLib2 still didn't know how to interpret the signals, the fact it didn't max out the sample buffer suggested it was getting the full sequence of pulses.

To see how I wanted to handle all this, I started with the remote with the power on, fan high and swing on at its current temperature of 81°F, and decided to see if I could make sense out of the pulses by generating minimal pairs. Ignoring the long mark and space at the beginning (used to prime the receiver's gain), and given that the mark durations were otherwise all very similar and the sequence was bookended completely by marks, it was likely that individual bits were being actually encoded by the spaces. A quick-and-dirty script to turn space durations into binary sequences, et voila:

1100010011010011011001001000000000000000000001001100000000100000101000000000000000000000000000001000101101001000 power off
1100010011010011011001001000000000000000001001001100000000100000101000000000000000000000000000001000101101101000 power on
                                   on ----^

1100010011010011011001001000000000000000001001001100000000100000101000000000000000000000000000001000101101101000 swing off
1100010011010011011001001000000000000000001001001100000000100000101111000000000000000000000000001000101101110010 swing on
                                                                   ^^^---- swing on --------------------------^

1100010011010011011001001000000000000000001001001100000011110000010000000000000000000000000000000011110110010000 med fan
1100010011010011011001001000000000000000001001001100000011110000101000000000000000000000000000000011110100110000 hi fan
                                                                ^^^

1100010011010011011001001000000000000000001001001100000011110000101000000000000000000000000000000011110100110000 60 F
1100010011010011011001001000000000000000001001001100000011110000101000000000000000000000000000001011110110110000 61 F
1100010011010011011001001000000000000000001001001100000010010000101000000000000000000000000000000001001101001000 72 F
1100010011010011011001001000000000000000001001001100000000100000101000000000000000000000000000001000101101101000 81 F
1100010011010011011001001000000000000000001001001100000001000000101000000000000000000000000000001010101100011000 85 F

There is a clearly conserved sequence at the beginning which undoubtedly represents the vendor identification. Power off and on are also easily distinguished with a simple change in one bit. It is interesting, though, that the vent swing and fan speed twiddle multiple bits, and the temperature seems to set values in two regions, potentially as a checksum (other historical LG A/C encodings have used CRCs in certain blocks, though these CRCs were based on multiple parts of the sequence, not just temperature). This is different from other LG encodings that have been documented, even for relatively recent units, and might also be based on vagaries of how I chose to interpret the signal.

However, we have a simpler way to handle control. I already know that setting the A/C to 81°F will easily cool the room well below 75°F (whether I want it to or not), and I also know it doesn't need to cool beyond that point. If the room is cooler than 75°, we'll just completely turn the A/C off. If it's over 80°F, turn it on to 81°F, high fan, no swing. As a fail-safe, if the room gets over 82°F, then set it to 60°F, high fan and no swing, and as the room cools back down it'll "just work" and turn it back to 81. This means we just need to acquire those particular sequences and be able to play them back.

As a test, let's see if we can transmit that long sequence we just acquired.

% kermit -l /dev/tty.usbserial -b 9600
C-Kermit 9.0.300 OPEN SOURCE:, 30 Jun 2011, for Mac OS X 10.4.11
 Copyright (C) 1985, 2011,
  Trustees of Columbia University in the City of New York.
Type ? or HELP for help.
(/Users/censored/) C-Kermit>set carrier-watch off
(/Users/censored/) C-Kermit>set local-echo on
(/Users/censored/) C-Kermit>connect
Connecting to /dev/tty.usbserial, speed 9600
 Escape character: Ctrl-\ (ASCII 28, FS): enabled
Type the escape character followed by C to get back,
or followed by ? to see other options.
----------------------------------------------------
RAW,228,3122,1558,586,1018,586,1018,586,222,606,266,562,222,606,1018,586,266,538,262,566,1038,566,1042,586,226,578,1038,566,266,586,266,562,1018,586,1018,590,254,574,1014,562,1042,586,226,606,222,606,1014,590,222,582,262,590,1014,586,226,606,262,566,222,606,262,538,266,590,262,538,266,586,266,566,262,538,266,562,266,590,266,558,226,578,266,590,262,566,222,582,262,590,226,578,262,590,262,566,1014,590,262,562,266,538,1042,586,1018,590,262,566,222,606,262,562,226,606,262,562,226,606,262,538,266,590,1014,586,226,578,266,590,262,562,226,578,266,590,262,566,1014,586,226,578,1042,590,1014,590,1014,590,262,566,222,606,262,562,226,606,262,566,222,606,262,538,266,590,262,562,226,606,262,538,266,614,222,578,226,578,266,586,266,562,262,566,222,606,222,606,266,562,266,562,262,566,222,606,262,566,222,606,262,566,1014,590,222,606,262,566,262,566,1014,590,222,582,1038,590,1014,590,1014,590,1018,590,1010,590,222,606,262,566,266,562,1018,586,222,606

This time, double nothing happened: I didn't even get the blaster's acknowledgement of RAW IR Sent. When I tried sending it again, I got invalid type instead.

Ah, I said, I get it. I need to increase the number of samples in its buffer to 255, and also increase the amount of characters it can take as part of one command (I must have overflowed its buffer). Time to crack open the blaster and program it.

Because this blaster has the RS-232 port connected, you need to remove the wires to the TX and RX pins before you can program it over USB. But there was an even more basic problem:
Increasing the command and sample buffer exceeded the amount of working memory available (2KB, in the Arduino Nano). There was no way I was going to get that number of samples in one entry.

So, if you can't get them in one command, put them in multiple commands. Samples are simply stored as uint16_t, so I could have a full array of those and use a series of shorter commands. Using fixed hex encoding to reduce the buffer size (since commas wouldn't be needed) and a command character to send or wait for more data, we add a couple hex bytes to set where in the sample buffer to deposit the samples and then use multiple commands to construct the buffer in pieces. Here's what I settled on for encoding a full set of samples to transmit off, 81°F, no swing, high fan:

P,00400BE6065E01FE044601FE044601FE013E01FA014201FE013E01FA044601FE014201FA013E01FE044601FE044601FE013E01FE044601FE013E01FA014201FA044A01FA044A01FA013E01FE044601FE044601FE013E01FE013E01FE044601FA014201FE013E01FA044A01FE013E01FE013E01FA013E01FE014201FA013E01FE013E
P,408001FE013E01FA014201FE013E01FA014201FA013E01FE013E01FE013E01FE013E01FE013E01FE013E01FE013E01FA014201FA014201FA013E01FE044A01FA013E01FE013E01FE044601FE044601FE013E01FE013E01FE013E01FE013E01FA014201FE013E01FA014201FA014201FA044601FE013E01FE013E01FE013E01FE013E
P,80C001FE013E01FA044A01FE013E01FA044601FE014201FA013E01FE013E01FE013E01FE013E01FE013E01FE013E01FE013E01FE013E01FE013E01FE013A01FE014201FA013E01FE013E0202013A01FE013E01FE013E01FE013E01FE013A01FE014201FA013E01FE013E01FE013E01FE013E01FE013E01FE013E01FE013A01FE0142
S,C0E301FE013A01FE044601FE013E01FE013E01FE013E01FE044601FE013E01FA044601FE044601FE013E01FE044601FE013E01FE013E01FE044201FE013E02160126021601260216

The entire 227-count sample is generated section-by-section with this sequence of four commands. You can see the windows in the first two hex bytes (start and end+1 within the buffer), followed by up to 64 unsigned shorts as four hex nybbles each. The P is the command character for "partial" and the S for "send." By including the comma, it makes it visually distinct from the other commands that the blaster can accept, and doesn't match any of the blaster's existing commands which will still be accepted. The final end with the S command tells the blaster the total length of the samples to pass to IRLib to transmit. (IRLib2 includes a gap sample at index 0, but this isn't part of what we actually emit — we start with the long mark/space.)

Here are the Arduino sketches I used for the blaster and the receiver (you'll need IRLib2 to build them, and you'll need to have RECV_BUF_LENGTH to 255 in IRLib2/IRLibGlobals.h. Note that there are other known bugs in them and I'm just giving you what works for me. I also provide an updated tool (in Perl) to show you the binary sequence the hex commands encode; just provide those commands over standard input.

And here's what it looks like in Kermit:

% kermit -l /dev/tty.usbserial -b 9600
C-Kermit 9.0.300 OPEN SOURCE:, 30 Jun 2011, for Mac OS X 10.4.11
 Copyright (C) 1985, 2011,
  Trustees of Columbia University in the City of New York.
Type ? or HELP for help.
(/Users/censored/) C-Kermit>set carrier-watch off
(/Users/censored/) C-Kermit>set local-echo on
(/Users/censored/) C-Kermit>connect
Connecting to /dev/tty.usbserial, speed 9600
 Escape character: Ctrl-\ (ASCII 28, FS): enabled
Type the escape character followed by C to get back,
or followed by ? to see other options.
----------------------------------------------------
irblaster.info RS232 IR TX 0.2ck
P,00400BE6065E01FE044601FE044601FE013E01FA014201FE013E01FA044601FE014201FA013E01FE044601FE044601FE013E01FE044601FE013E01FA014201FA044A01FA044A01FA013E01FE044601FE044601FE013E01FE013E01FE044601FA014201FE013E01FA044A01FE013E01FE013E01FA013E01FE014201FA013E01FE013E
Packet Stored
P,408001FE013E01FA014201FE013E01FA014201FA013E01FE013E01FE013E01FE013E01FE013E01FE013E01FE013E01FA014201FA014201FA013E01FE044A01FA013E01FE013E01FE044601FE044601FE013E01FE013E01FE013E01FE013E01FA014201FE013E01FA014201FA014201FA044601FE013E01FE013E01FE013E01FE013E
Packet Stored
P,80C001FE013E01FA044A01FE013E01FA044601FE014201FA013E01FE013E01FE013E01FE013E01FE013E01FE013E01FE013E01FE013E01FE013E01FE013A01FE014201FA013E01FE013E0202013A01FE013E01FE013E01FE013E01FE013A01FE014201FA013E01FE013E01FE013E01FE013E01FE013E01FE013E01FE013A01FE0142
Packet Stored
S,C0E301FE013A01FE044601FE013E01FE013E01FE013E01FE044601FE013E01FA044601FE044601FE013E01FE044601FE013E01FE013E01FE044201FE013E02160126021601260216
HEX IR Sent

And with a triumphant beep, the A/C turned off! Success!

Our last step is to automate this process. A cron job runs every 10 minutes to add another entry to the temperature and humidity log. We'll just add some extra code to "do something" based on the temperature.

The "something" will be to automate the process with Kermit, and for that we'll use another tool that comes with OS X 10.4, the ever-useful expect. Here's our expect script to automate sending the off sequence through Kermit.

#!/usr/bin/expect

spawn /home/censored/bin/kermit -l /dev/tty.usbserial -b 9600

expect "C-Kermit>"
send "set carrier-watch off\r"
expect "C-Kermit>"
send "set local-echo on\r"
expect "C-Kermit>"
send "connect\r"
expect -- "-----\r\n"
send "\r\rP,00400BE6065E01FE044601FE044601FE013E01FA014201FE013E01FA044601FE014201FA013E01FE044601FE044601FE013E01FE044601FE013E01FA014201FA044A01FA044A01FA013E01FE044601FE044601FE013E01FE013E01FE044601FA014201FE013E01FA044A01FE013E01FE013E01FA013E01FE014201FA013E01FE013E\r"
expect "Stored\r"
send "P,408001FE013E01FA014201FE013E01FA014201FA013E01FE013E01FE013E01FE013E01FE013E01FE013E01FE013E01FA014201FA014201FA013E01FE044A01FA013E01FE013E01FE044601FE044601FE013E01FE013E01FE013E01FE013E01FA014201FE013E01FA014201FA014201FA044601FE013E01FE013E01FE013E01FE013E\r"
expect "Stored\r"
send "P,80C001FE013E01FA044A01FE013E01FA044601FE014201FA013E01FE013E01FE013E01FE013E01FE013E01FE013E01FE013E01FE013E01FE013E01FE013A01FE014201FA013E01FE013E0202013A01FE013E01FE013E01FE013E01FE013A01FE014201FA013E01FE013E01FE013E01FE013E01FE013E01FE013E01FE013A01FE0142\r"
expect "Stored\r"
send "S,C0E301FE013A01FE044601FE013E01FE013E01FE013E01FE044601FE013E01FA044601FE044601FE013E01FE044601FE013E01FE013E01FE044201FE013E02160126021601260216\r"
expect "Sent\r"
send "S,C0E301FE013A01FE044601FE013E01FE013E01FE013E01FE044601FE013E01FA044601FE044601FE013E01FE044601FE013E01FE013E01FE044201FE013E02160126021601260216\r"
expect "Sent\r"
send "\034C"
expect "C-Kermit>"
send "quit\r"

If you're unfamiliar with an expect script, the basic notion is to start a dependent program and control it as if you were typing into it from a terminal. You tell expect what to expect (hence the name), and then tell it what to send. Here, we start Kermit and wait for its prompt, set the terminal settings appropriately, and then send the sequences, waiting for Stored after each packet and Sent after the final one. The "\034C" sequence sends CTRL-backslash and a C to Kermit to exit terminal mode so that we can quit cleanly. There are three scripts, one to turn it off and one to turn it on at 81°F (high fan, no swing), and one to turn it on at 60°F (high fan, no swing), which the cron job chooses between.

There are a couple bits of paranoia in these scripts for reliability. \r is the same as it is in C, i.e., as if you had sent a RETURN character. We do it twice at the beginning of sending the first packet in case there are any partial commands still in the buffer (we'll just get an invalid type if it's unrecognized, which this script ignores). At the end we also give the final send command twice: the buffer is still intact, and this ensures that any glitch about the A/C receiving the infrared signal is likely compensated for. Since the A/C command sequences are idempotent by dint of specifying the entire desired state, transmitting the same sequence twice doesn't harm anything.

One last wrinkle: we have a special case in our temperature algorithm for whipping the A/C harder if the room isn't getting cooler, but if the room is already below the minimum temperature (like in the middle of winter), the cron job will faithfully still send an "off" sequence anyway — even if the A/C was off to begin with. This wouldn't be a problem except that this makes the unit beep in response over and over every 10 minutes, which is not nice when you're sleeping and the server room door is open to passively cool.

To handle this, the cron job simultaneously notes the name of the last expect script it ran. We want to resend the cooling sequences at 80°F+ and 82°F+ to ensure the A/C gets flogged to get the temperature down, but if the last script that was run was the off script, then we suppress running it again until some other script is run. On the unlikely chance the initial off signals (we send them twice) were never received, it is likely that the human who lives in the house and pays the electric bill and notices the A/C running full blast in the server room turning it into Longyearbyen will simply turn the unit off himself.

You know, like normies. Normies with a vintage server room. As you do.

No comments:

Post a Comment

Comments are subject to moderation. Be nice.