Project - InfraRed

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.

UsageTargetname   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.

As an IR emitter it can also be controlled by any other EasyNet nodes, perhaps to emit specific IR control signals depending on sensor alerts etc.
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.

Using your phone as an IR controller can be a bit of a pain if you answer the phone then need to mute the TV volume etc,  and a phone controller is dependent on battery charge of course, and will never be as quick and convenient to use as a ready-to-use IR remote controller.

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.

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                        
onudp udpRX
wlog "OK"

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
end if

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

RXmsg$ =$
if ucase$(word$(RXmsg$,1)) = "ACK" then
 gosub ACK   'echoed reply from successfully received message, original msg can be removed from queue  
 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
   gosub instruction$                                       'branch to action the corresponding instruction subroutine
  endif  'word.find
 endif   '(target$=localIP$)
endif '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$)

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"
   retryq$ = retryq$ + msg$ + qdelimiter$
   udp.write netip$ + "255", udpport, msg$
   wlog "retry " + msg$

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$ = ""
 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$)
 ret$ = ""
end if
end sub

udp.reply "Reply from " + Nodename$

pin(relay1pin) = 1

pin(relay1pin) = 0

if pin(relay1pin) = 1 then pin(relay1pin) = 0 else pin(relay1pin) = 1

if pin(buttonpin) = 0 then start = millis else stop = millis
if stop > start then
 if stop - start < 2000 then
  sendq "All relay1toggle"       'short press
  send "ALL Reply"                 'long press

END   '-------------------- End ---------------------