Skip to content

Instantly share code, notes, and snippets.

@alferz
Last active March 2, 2024 17:46
Show Gist options
  • Save alferz/38b6027c070ed0f2865baf2fd3a9de57 to your computer and use it in GitHub Desktop.
Save alferz/38b6027c070ed0f2865baf2fd3a9de57 to your computer and use it in GitHub Desktop.
LG Air Conditioner Infrared Control Python Script with pigpio
#!/usr/bin/env python
# lgairctrl.py
# 2024-03-01
# Public Domain
"""
A utility to send an infrared command to certain, unknown models of LG Air Conditioners equipped with infrared. This utility currently supports the United States model LW1022IVSM with Fahrenheit temperature control (and probably many others). This script is meant to be run on a Raspberry Pi with pigpiod installed and an infrared LED module, or a bare IR LED with NPN transistor base connected to one of the GPIO pins.
To send a command use the following format. All options are required.
./lgairctrl.py -g <gpionumber> -m <mode> -t <temp> -f <fanspeed>
where mode is one of
o -- off
m -- max cool
e -- energy saver
s -- sleep mode (only works when unit is on. Send 'm' first, then 's' to enable sleep mode)
c -- clear sleep mode (puts unit back in previous mode, ie 'm')
f -- fan only
d -- dry (dehumidify)
temp is a number between and including 60 and 86 (fahrenheit)
and fanspeed is one of
1 -- low
2 -- med
3 -- high
"""
import time
import json
import os
import argparse
import pigpio # http://abyz.co.uk/rpi/pigpio/python.html
p = argparse.ArgumentParser()
p.add_argument("-g", "--gpio", help="GPIO for TX", required=True, type=int)
p.add_argument("-m", "--mode", help="o/off, m/max, e/enrgy svr, s/sleep, c/clr sleep, f/fan, d/dry", required=True)
p.add_argument("-t", "--temp", help="60-86 in f", required=True)
p.add_argument("-f", "--fan", help="1/lo 2/md 3/hi", required=True)
p.add_argument("-v", "--verbose", help="Be verbose", action="store_true")
args = p.parse_args()
GPIO = args.gpio
MODE = args.mode
TEMP = args.temp
FAN = args.fan
VERBOSE = args.verbose
FREQ = 38.0
GAP_MS = 100
GAP_S = GAP_MS / 1000.0
def printCmd(cmd):
outstring = """FXD FXD SPCL MODE TEMP FANS CKSM\n"""
for i, v in enumerate(cmd):
outstring += str(v)
if (i+1)%4 == 0:
outstring += " "
print(outstring)
def getChecksum(cmd): #LG checksum is the sum of each 4 bit group, discarding binary overflow (ie last 4 least significant bits)
totalSum = 0
for i in range(0, len(cmd), 4):
sum4 = cmd[i]*8 + cmd[i+1]*4 + cmd[i+2]*2 + cmd[i+3]*1
totalSum += sum4
cksumArr = [int(x) for x in bin(totalSum%16)[2:]] #Get array of least 4 significant bits by modding by 16
for i in range (0, 4-len(cksumArr)): #Pad array out to 4 digits with leading zeroes
cksumArr = [0] + cksumArr
if VERBOSE:
print('checksum array: ' + str(cksumArr))
return cksumArr
def carrier(gpio, frequency, micros):
"""
Generate carrier square wave.
"""
wf = []
cycle = 1000.0 / frequency
cycles = int(round(micros/cycle))
on = int(round(cycle / 2.0))
sofar = 0
for c in range(cycles):
target = int(round((c+1)*cycle))
sofar += on
off = target - sofar
sofar += off
wf.append(pigpio.pulse(1<<gpio, 0, on))
wf.append(pigpio.pulse(0, 1<<gpio, off))
return wf
def xmitcmd(cmd):
pi = pigpio.pi() # Connect to Pi.
pi.wave_clear() #Clear any previous stored waveforms
if not pi.connected:
print('Fatal error, could not connect to pigpiod!')
exit(0)
pi.set_mode(GPIO, pigpio.OUTPUT) # IR TX connected to this GPIO.
pi.wave_add_new()
emit_time = time.time()
if VERBOSE:
print("Playing")
# Create wave, twice as many as the cmd plus 2 preamble bits and 1 ending bit
wave = [0]*((len(cmd)*2)+3) #Add two bits for the preamble and one for ending bit
#First add the preamble mark/space
wf = carrier(GPIO, FREQ, 3122) #Preamble mark is 3122
pi.wave_add_generic(wf)
#print('add mark 3122')
wave[0] = pi.wave_create()
pi.wave_add_generic([pigpio.pulse(0, 0, 9661)]) #Preamble space is 9661
#print('add space 9661')
wave[1] = pi.wave_create()
pi.wave_add_generic([pigpio.pulse(0, 0, 507)]) #Short space is a pulse of 507
SPACE_SHORT = pi.wave_create()
pi.wave_add_generic([pigpio.pulse(0, 0, 1542)]) #Long space is a pulse of 1542
SPACE_LONG = pi.wave_create()
wf = carrier(GPIO, FREQ, 509) #Mark is a pulse of 509
pi.wave_add_generic(wf)
MARK = pi.wave_create()
for i in range(0, len(cmd)):
wave_index = i*2 + 2
wave[wave_index] = MARK
if(cmd[i] == 0):
wave[wave_index+1] = SPACE_SHORT
else:
wave[wave_index+1] = SPACE_LONG
#Add ending MARK bit
wave[len(wave)-1] = MARK
delay = emit_time - time.time()
if delay > 0.0:
time.sleep(delay)
pi.wave_chain(wave) #This actually sends the entire command
while pi.wave_tx_busy():
time.sleep(0.002)
emit_time = time.time() + GAP_S
pi.stop() # Disconnect from Pi.
code_parts = {
'FXD1': [1,0,0,0], #This part never changes for LG
'FXD2': [1,0,0,0], #This part never changes for LG
'SPCL': [1,1,0,0], #This is the default for the off command
'MODE': [0,0,0,0], #This is the default for the off command
'TEMP': [0,0,0,0], #This is the default for the off command
'FANS': [0,1,0,1] #This is the default for the off command
}
if(args.mode == 'o'):
#This is the off cmd, which is already preconfigured in code_parts. Send it as is.
print('Sending command: off')
else:
resText = 'Sending command: '
code_parts['SPCL'] = [0,0,0,0]
tempMap = { #digits 1-4 are the values in the TEMP array, the 5th digit goes in position 2/idx1 in SPCL as a kicker
'60': [0,0,0,1,0],
'61': [0,0,0,1,1],
'62': [0,0,1,0,0],
'63': [0,0,1,0,1],
'64': [0,0,1,1,0],
'65': [0,0,1,1,1],
'66': [0,1,0,0,0],
'67': [0,1,0,0,1],
'68': [0,1,0,1,0],
'69': [0,1,0,1,1],
'70': [0,1,1,0,0],
'71': [0,1,1,0,1],
'72': [0,1,1,1,0],
'73': [0,1,1,1,1],
'74': [1,0,0,0,0],
'75': [1,0,0,0,1],
'76': [1,0,0,1,0],
'77': [1,0,1,0,0],
'78': [1,0,1,1,0],
'79': [1,0,1,1,1],
'80': [1,1,0,0,0],
'81': [1,1,0,0,1],
'82': [1,1,0,1,0],
'83': [1,1,0,1,1],
'84': [1,1,1,0,0],
'85': [1,1,1,0,1],
'86': [1,1,1,1,0]
}
code_parts['TEMP'] = tempMap[args.temp][0:4]
code_parts['SPCL'][1] = tempMap[args.temp][4]
resText += args.temp + 'f / '
match args.mode: #m, e, s, c, f, d
case 'm':
code_parts['MODE'] = [0,0,0,0]
resText += 'max cool / '
case 'e':
code_parts['MODE'] = [0,1,1,0]
resText += 'energy save / '
case 's':
code_parts['SPCL'] = [1,0,1,0]
code_parts['MODE'] = [0,0,0,0]
resText += 'sleep mode / '
case 'c':
code_parts['SPCL'] = [1,0,1,1]
code_parts['MODE'] = [0,0,0,0]
resText += 'clear sleep mode / '
case 'f':
code_parts['MODE'] = [0,1,0,1]
resText += 'fan only / '
case 'd':
code_parts['MODE'] = [0,0,0,1]
resText += 'dry mode / '
match args.fan:
case '1':
code_parts['FANS'] = [0,0,0,0]
resText += 'low fan'
case '2':
code_parts['FANS'] = [0,0,1,0]
resText += 'med fan'
case '3':
code_parts['FANS'] = [0,1,0,0]
resText += 'hi fan'
print(resText)
cmd = []
for i, (k,v) in enumerate(code_parts.items()):
cmd += v
cmd += getChecksum(cmd) #Add the checksum
printCmd(cmd)
xmitcmd(cmd)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment