This InfraRed project can be configured as an EasyNet IR Sensor node, or EasyNet IR Emitter node, or both.
Configured as an IR sensor node it can read IR remote-controller button-press signals and echo them to a remote IR emitter node.
Optionally it could recognise IR button presses and directly control the shared functionality of remote EasyNet nodes to eg: turn relays on and off.
Also optionally, it could act as a local IR sensor to recognise specific local IR button signals for controlling its own local functionality.
Configured as an IR emitter node it recognises the instruction 'IRblaster' (followed by the IR signal info), allowing it to be the remote IR blaster of an IR extender pair. Usage: Targetname IRBLASTER IRcode=code IRtype=type BITS=bits
One IR sensor node could route all its IR signals to
multiple IR emitter nodes which would each 'action' their own relevant
signals. Conversely, one IR emitter node might be controlled by multiple remote IR sensor nodes from several different locations. An emitter node is not obligated to emit all IR signals though, it might look for some specific incoming IR messages to
control its local functionality from remote button presses. To put things into context: A Porch PIR or Driveway Radar sensor might send an IRBLASTER code to a Lounge IR Emitter to mute the TV while triggering a doorbell relay. A Bedroom IR Sensor node might use the same Lounge IR Emitter to send button presses to control a lounge satellite receiver or set-top box. You can get IR emitter dongles (99p) that just plug into a mobile phone for use with free APPs which turn the phone into an IR remote controller (search for "3.5mm Plug Infrared Remote").
Those IR emitter dongles just consist of an infrared LED (without any current limiting resistor)
connected to a 3.5mm jack plug, and LED polarity is 'don't care'
because phones drive them with what is effectively an audio signal. They
can be used on an ESP device without resistor because the brief
averaged power dissipation of a short pulse train should not overload
anything, but obviously the polarity must be correct for it to work.
It is simple to make your own, in which case 2 LEDs could be connected back to back to ensure 1 will always be correct polarity. An
IR blaster is basically a higher-powered IR emitter, which can be made by using several IR diodes in parallel (such as a CCTV night illuminator) but be aware the esp8266 gpio outputs are only rated for 12mA max, so use eg: a transistor driver for loads greater than about 10mA.
Assuming at least a TV IR remote controller is needed anyway, an option could be a "universal learning remote" available from ebay for under £5.
It gives ability to 'learn' the essential buttons of all of the other IR remote controllers onto switchable 'pages' of the one universal remote control.
We keep universal remotes sat in the lounge, office and bedroom - all programmed with the same buttons from various remote controllers - it allows us to control the TV, Satellite, DVR, Lights, Mains Relays, CCTV camera selection, PTZ camera control, etc... and all just using 1 remote controller from any of those 3 locations (all the original remotes can be thrown in a cupboard for safe-keeping).
Script notes:
The same script is used as an IRsensor and/or an IRemitter, by selecting the appropriate IR.INIT instruction to enable the required hardware.
'ir.init 4,5 'uses IR detector on gpio4 input AND IR emitter on gpio5 output
ir.init 4 'uses IR detector only on gpio4, without any emitter 'ir.init 4 OFF,5 'IR detector is turned OFF, so is using just IR emitter only, In the IRsensor: branch there is a commented 'wlog' line that can be uncommented to show all IR signals that the detector 'thinks' it has received.
Uncomment that line to display all received signals, then press your IR remote controllers buttons - it should become obvious what 'type' and 'bits' your genuine signals are, which will allow you to mask out just those types and bit lengths you are interested in on the line below it.
This was certainly necessary on the IR receivers I tried, which gave a continuous stream of unwanted spurious signals which could quickly flood the processing if not filtered out ... but the genuine signals were always hidden in amongst the noise. Didn't seem to make any difference whether they were powered from 3.3v or 5v, they still seemed to keep generating a busy stream of IR interrupts which can quickly fill up the Annex buffers.
Pull-up resistor did not help, either. So allow time for buffers to settle down after clicking STOP before attempting to do anything else.
I got into the habit of doing a Select All, then Copy, then doing a Reconnect, then Paste back contents, then Save.
Also slow the Retry timer to something more reasonable like 5000 (5 secs).
When able to filter out just the signals you are interested in, they can all be routed on to any waiting IRblaster receiving nodes... SENDQ "ALL IRblaster IRcode=" + IRcode$ + " IRtype=" + type$ + " BITS=" + bits$
(substitute 'ALL' for your target nodename or groupname)
You can also take note of any specific button codes you may wish to individually recognise and act on.
Basic:
title$ = "EasyNet IR v1.0, by Electroguard"
nodename$ = "" 'Assign a unique node name of your choice (if you forget, it will be called "Node" + its node IP) groupname$ = "IRsensor/IRemitter" 'concatenated group names are searched for a partial match localIP$ = WORD$(IP$,1) netIP$ = WORD$(localIP$,1,".") + "." + WORD$(localIP$,2,".") + "." + WORD$(localIP$,3,".") + "." nodeIP$ = WORD$(localIP$,4,".") udpport = 5001 'change to suit your own preference, but don't forget to do the same for all nodes if nodename$ = "" then nodename$ = "Node" + nodeIP$ instructionslist$ = ucase$("Reply Relay1ON Relay1OFF Relay1Toggle IRblaster ") 'local shared instruction subdirs RXmsg$ = "" 'variable to hold incoming message instruction$ = "" 'variable to hold incoming instruction data$ = "" 'variable to hold any incoming data after the instruction retryq$ = "" 'variable to hold all unexpired messages still waiting to be acknowledged qdelimiter$ = "|" 'separates messages in the retryq time2live = 2 * 60 'sent-message unacknowledged lifetime in seconds ID$ = "" 'unique msg ID consists of send date+time + time2live - also acts as msg 'expire' time flag 'ir.init 4,5 'uses IR detector on gpio4 input AND IR emitter on gpio5 output ir.init 4 'uses IR detector only on gpio4, no emitter attached 'ir.init 4 OFF,5 'uses IR emitter only, IR detector is turned OFF oninfrared IRsensor ledpin = 13: pin.mode ledpin, output: ledoff = 1: pin(ledpin) = ledoff relay1pin = 12: pin.mode relay1pin, output: pin(relay1pin) = 0 'using active high qpio12 for relay1 buttonpin = 0: pin.mode buttonpin, input, pullup 'using active low gpio0 button interrupt buttonpin, pressed start=0: stop=0 'used by button-pressed subroutine to differentiate between short and long presses timer1 5000, Retry 'periodic timer to keep resending unACKed msgs until they expire udp.begin(udpport) onudp udpRX wlog "OK" wait IRsensor: IRcode$ = ir.get$ 'actual remote-controller button-press code type$ = ir.get$(1) 'code type 'address = val(ir.get$(2)) 'available if needed 'cmd = val("&h" + ir.get$(3)) 'available if needed bits$ = ir.get$(4) 'code bit length 'uncomment the next line to show all received IR codes in wlog window, note any of your remote controller button codes of interest. wlog " " + IRcode$ + ", " + type$) + ", " + ir.get$(2) + ", " + str$(val("&h" + ", " + ir.get$(3))) + ", " + bits$ + ", " + ir.get$(5) if (val(type$) = 3) and (val(bits$) = 32) then 'filter out everything except the type and bit length of codes you are interested in wlog IRcode$ SENDQ "ALL IRblaster IRcode=" + IRcode$ + " IRtype=" + type$ + " BITS=" + bits$ 'send IR info to ALL nodes, change ALL for your particular IRblaster receiver node name if IRcode$ = "FF30CF" then 'optionally look for specific button codes, could use Select Case statements to look for multiple buttons wlog "My button 1" sendq "all relay1toggle" 'toggle relay on remote nodes as demo gosub relay1toggle 'toggle local relay as a demo endif end if return IRblaster: IRcode$ = "" WordParse IRcode$, data$, "IRcode=", " " 'parse out IR code WordParse type$, data$, "IRtype", " " 'parse out code type WordParse bits$, data$, "BITS", " " 'parse out code length IR.send val(type$), IRcode$, val(bits) 'emit IR code on IR LED return udpRX: RXmsg$ = udp.read$ if ucase$(word$(RXmsg$,1)) = "ACK" then gosub ACK 'echoed reply from successfully received message, original msg can be removed from queue else target$ = ucase$(word$(RXmsg$,1)) 'Target may be NodeName or GroupName or "ALL" or localIP address if (target$=localIP$) OR (target$=ucase$(nodename$)) OR (instr(ucase$(groupname$),target$)>0) OR (target$="ALL") then instruction$ = trim$(ucase$(word$(RXmsg$,2))) 'Instruction is second word of message data$ = "": getdata data$,RXmsg$," ",2 'extract any data that follows the instruction if word.find(ucase$(instructionslist$),instruction$) > 0 then if (ucase$(instruction$) <> "ACK") and (instr(ucase$(data$),"ID=") > 0) then udp.reply "ACK " + RXmsg$ 'ACKnowledge the incoming msg endif gosub instruction$ 'branch to action the corresponding instruction subroutine else udp.reply RXmsg$ + " INSTRUCTION NOT RECOGNISED" endif 'word.find endif '(target$=localIP$) endif 'ACK return ACK: msg$ = "": getdata msg$, RXmsg$, " ", 1 wlog "Ack recvd for " + msg$ pos = word.find(retryq$,msg$,qdelimiter$) if pos > 0 then retryq$ = word.delete$(retryq$,pos,qdelimiter$) return RETRY: if word.count(retryq$, qdelimiter$) > 0 then if retryq$ <> "" then wlog "queue=" + retryq$ msg$ = word$(retryq$,1,qdelimiter$) 'grab first unACKed msg in the queue retryq$ = word.delete$(retryq$,1,qdelimiter$) 'chop msg off front of queue expire$ = "" WordParse expire$, msg$, "ID=", " " 'parse out ID= expire time if msg$ <> "" then 'compare expire time to current unix time if dateunix(date$) + timeunix(time$) > val(expire$) then Send "LOG ERROR: Node " + Nodename$ + " FAILED SEND - " + msg$ + " not ACKnowledged" else retryq$ = retryq$ + msg$ + qdelimiter$ udp.write netip$ + "255", udpport, msg$ wlog "retry " + msg$ endif endif endif return sub SendQ(sendmsg$) sendmsg$ = sendmsg$ + " ID=" + str$(dateunix(date$) + timeunix(time$) + time2live, "%10d", 1) retryq$ = retryq$ + sendmsg$ + qdelimiter$ udp.write netip$ + "255", udpport, sendmsg$ end sub sub Send(sendmsg$) udp.write netip$ + "255", udpport, sendmsg$ end sub sub GetData(ret$, v$, sep$, pos) 'extracts everything from the msg after the Instruction and puts into data$ (thanks cicciocb) local i, p, q p = 1 for i = 1 to pos p = instr(p + 1, v$, sep$) if p > 0 then p = p + len(sep$) next i if p = 0 then ret$ = "" else q = instr(p+1, v$, sep$) if q = 0 then q = 999 ret$ = mid$(v$, p) end if end sub sub WordParse(ret$, full$, search$, sep$) 'extracts value from option=value (thanks cicciocb) local p, b$ p = instr(full$, search$) if p <> 0 then b$ = mid$(full$, p + len(search$)) ret$ = word$(b$, 1, sep$) else ret$ = "" end if end sub REPLY: udp.reply "Reply from " + Nodename$ return Relay1ON: pin(relay1pin) = 1 return Relay1OFF: pin(relay1pin) = 0 return Relay1Toggle: if pin(relay1pin) = 1 then pin(relay1pin) = 0 else pin(relay1pin) = 1 return PRESSED: if pin(buttonpin) = 0 then start = millis else stop = millis if stop > start then if stop - start < 2000 then sendq "All relay1toggle" 'short press else send "ALL Reply" 'long press endif endif return END '-------------------- End --------------------- |