This was a progression from the Controlled Relay. It too was put on the shelf for more than a year, but is being published now because it appears to be mostly working ok with the latest Annex (currently 1.39).
I think there was still something I wasn't happy with, but I can't remember what it was - so be aware that it may not be 100%, but better something than nothing.
This Smart Switch demo video should give some idea of what it can do.
The intention was to create a Smart Switch for controlling mains lighting etc using cheap Sonoff devices which already incorporate the required MAINS hardware - so was just a matter of using TX or RX gpio, or soldering a pair of thin flexible wires across the onboard gpio0 button, which would be routed out away from the MAINS for connecting to alternative switches or sensors.
If those original Sonoffs are unavailable, almost any other Annex compatable switched Mains device could be used instead.
The Smart Switch script provides for an input to trigger a local relay and/or send instructions to control remote devices if wished.
The input trigger can be bi-state On or Off, or momentary push-button, allowing almost any types of switches or sensors to be used (a few switches, beam-breaks and radar sensors shown on top right).
Amongst many other uses, it offered an easy low cost method for converting existing lighting into a 'smart' system by disconnecting the existing wall switch wires in the ceiling space and adding a Sonoff in their place to do the actual mains switching, then re-using the original disconnected switch and wiring to control the Sonoff gpio0 button (or other gpio).
This allows the converted lights to still be operated locally by the original switch, but also offers opportunity for intelligent sensor-controlled lights and appliances.
(the skills needed for doing it are mainly common-sense and reasonable competence, but the legalities of doing it would depend on the country and what qualifications were required)
Although the project details are based on the low-cost Sonoff Mains Relay Module, the principles are easily adapted to other devices.
Sonoff devices typically have vacant flashing holes, so will probably need pins soldered in for connecting a UART to reflash the device with Annex.
After flashing, the TX (gpio01) and/or RX (gpio03) can be used as an input or output if wished (see this TX RX Hack for more info).
Some Sonoffs also have gpio14 available, as well as the usual gpio0 button - so there are a few alternative gpio pins available to use for the switch input pin.
Doesn't have to be a Sonoff of course, the hardware can be any Annex device that suits your needs, from a battery-powered ESP-01 to a MAINS relay module.
Using the TX & RX pins as well as the gpio flashing button offers any device (even an ESP-01) to provide at least 3 gpio's to choose from.
Although the Smart Switch can function locally all by itself (even with wifi disabled), it offers much greater capability when used remotely and interactively.
Eg: replacing On/Off flip switches with momentary push-buttons allows devices to be remotely toggled On and Off from multiple locations without special wiring.
EasyNet was included to allow devices to be remotely controlled using UDP, either individually, or as a group where multiple devices can all be controlled together.
Devices could be controlled manually using the Toolkit UDP Console, or UDP instructions embedded into devices to allow them to automatically control others.
Towards the top of the script you can find...
instructionslist$ = "Reply BlinkIP Blink Save Load " 'List of system subdir branches available as remote triggers
instructionslist$ = instructionslist$ + "Relay1On Relay1Off Toggle1 Cycle1On Cycle1Off" 'List of user subdir branches available as remote triggers
All those listed words have a corresponding gosub branch named after them, which effectively turns them into local 'instructions' which can be actioned remotely.
So in a nutshell, if a local device recognises its own Node name, or Group name, or IP address, or "ALL", in any incoming UDP message, and if that message contains an 'instruction' which it can recognise from its instructionlist$ it will gosub to the corresponding branch name to action the code for that instruction.
Grasp that concept and you'll realise how easy it is to add your own network 'instructions' simply by adding a word to instructionlist$ and creating a subroutine with the same name to branch to and 'action' it (and if you wish to pass parameters with your instructions, take a look at the blink: subroutine).
Consider a few possible UDP messages...
"ALL Relay1OFF" could turn all devices with relays to Off.
"STAIRS Cycle1ON" could turn On all devices with "STAIRS" included in their group name for a predetermined duration, then cycle them back Off again.
"ROUTER Cycle1OFF" could 'bounce' a 'slow' wifi router by turning it Off for several seconds then back On again.
"192.168.0.126 Toggle1" could change the state of whatever that device was controlling.
Basically, any Smart Switch sensors could control any remote devices, and a Smart Switch could itself be controlled by other remote devices.
An ensuing Smart Socket project will let Sonoff S20 or successors or any equivalent plug-in MAINS relays, also join the party.
And a little battery-powered Wemos D1 Mini with BIG button could make a handy match-box sized Smart Switch remote controller.
(the TTGO equivalent even has onboard LIPO connector plus charger and ext aerial socket)
Note: EasyNet interactive remote control is entirely optional and transparent... so simply ignore it if not wanted (it will still always be available if ever needed)
Hidden Extras
So the Smart Switch has already earned its name... but it also has many hidden smart extras.
The additional functionality is disabled by default for a couple of reasons...
Firstly, some was only partially implemented when it was shelved - but rather than rip it all out, it's being left for others to continue from if they wish (eg: Load and Save settings to INI, etc).
More importantly, it would be both pointless and potentially dangerous to have more than one functionality enabled at any one time which might cause a conflict situation where eg: scheduled alarms could interfere with thermostat control or vice versa.
Also, the 1 sec 'ticker' should manage anything, but could struggle with everything.
So the screen dump (right) shows what has been included for maximum versatility and flexibility, but by default those extra 'frills' are disabled for safety reasons.
Controlling MAINS equipment can have serious potential consequences - so although most of the additional hidden functionality can be enabled at the top of the script, you do so entirely at your own risk... and make sure to read Note 6: 'Panic Button' below.
Notice the 7-segment displays for the onscreen clock and temperature readouts, which can take advantage of an embedded digital font if present - simply upload the font file from the bottom of this page into "/font", or upload all 3 fonts from the zip and change the name of the font file in the script for a choice of fonts.
(more info is available from the Skin Clock project) ... (the temperature readout will show -127 if no Dallas sensor is available)
Buttonmode and switchmode can be configured towards the top of the script to allow the hardware button/switch/sensor contacts on buttonpin and/or switchpin to either operate in On/Off flip switch mode (eg: to show when a door is open or closed) or act as a momentary push-button.
The hardware button can only distinguish between short (toggle relay) and long (blink IP address) button presses if Buttonmode=0 (momentary)
Selecting the checkbox just underneath the temperature display turns the thermometer into a thermostat - the setpoint can be moved up or down, and 'fan' or 'heater' mode selected from the listbox... fan mode turns relay On when temp is above the setpoint, heater mode turns relay On when temp is below the setpoint.
The 'Send' button is just a test mechanism for sending the predetermined contents of 'sendmsg$' via UDP to check if it does as expected... used for embedded control of remote EasyNet devices. Probably easier to understand if considered the other way round, ie: a remote device sending an embedded instruction to control this local device... if this local device recognises its own Node name, or Group name, or IP address, or "ALL", in any incoming UDP message, and it matches the incoming 'instruction' to a word it recognises in its instructionlist$, then it should gosub to the corresponding branch name to execute that code.
So the send button is for manually sending and testing instructions from a remote device prior to embedding those instructions into the appropriate sending code.
By default the script is configured for Sonoff pins, so change pinouts to suit your own requirements.
Ledpin = 13, ledoff = 1 (normally high, going active low)
Relaypin = 12, relayoff = 0 (normally low, going active high)
Buttonpin = 0, normally pulled high, going low when pressed, buttonmode = 1 changes the behavour to use On/Off type switches instead of a momentary pushbutton
Switchpin = 1, switchoff = 1 (normally high, going active low) uses RX as input contacts, use switchoff = 0 for active high triggers, switchmode changes the behaviour
Dallaspin = 3 uses gpio3 for an optional Dallas 1-wire temperature sender if that gpio is not being used by switchpin (or a DHT could be used instead of the Dallas).
Note 1: Switchmode changes the behaviour of whatever is connected to switchpin, whereas buttonmode changes the behaviour of the gpio0 flashing button.
Note 2: Showmodes = 1 displays onscreen buttonpin and switchpin checkboxes for changing their modes in real time to check resulting behaviours.
Note 3: If either gpio1 TX or gpio0 flashing button are low at bootup it will prevent normal startup, so bear that in mind if not using momentary contacts.
Note 4: If the weak esp pullup is unreliable because of sensor wire lengths etc, use a stronger (1K to 10K) hardware pullup resistor.
Note 5: If LED is available it will show any pending CycleOn or CycleOff changes.
Note 6: Using Toggle acts like a manual 'Panic Button' which disables Thermostat control and any pending Scheduled alarms.
Note: Developed on 1.39 beta 1, and does not work on version 1.39 beta 2 because of a firmware bug, but should hopefully be fixed in the next release.
Basic:
title$ = "EasyNet Smart Switch/Socket - by Electroguard - developed on Annex 1.39 beta 1"
nodename$ = "" 'Assign a unique node name of your choice (if you forget, it will be called "Node" + its node IP)
groupname$ = "Sonoff\Smart\Switch\Socket\Relay" '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$
showsettings = 0
showtitle = 1
showID = 0 '=1 to show local identity info
showbuttons = 1 '=1 to show onscreen system buttons
showclock = 0 '=1 to show clock and schedule options
showscheduled = 0 '=1 to show scheduled alarm On and Off times
showcycled = 0 '=1 to show cycleOn and cycleOff options
showswmodes = 0 '=1 to show harware button and switch mode checkboxes
showtools = 0 '=1 to show onscreen system buttons
showtherm = 0 '=1 to show temperature display
showstat = 0 '=1 to enable thermostat options
'filename$ = word$(BAS.FILENAME$,1,".") + ".ini" 'Un-comment this line to save settings to 'this scriptname'.inifilename$ = "/program/smartswitch.ini" 'Uncomment this line to save to a specfied filefontpath$ = "/font/" 'path to optional font filefontfile$ = "dig7monoitalic.ttf" 'filename.ext of optional font fileinstructionslist$ = "Reply Report BlinkIP Blink Save Load " 'List of Subdir branches available as remote triggersinstructionslist$ = instructionslist$ + "Relay1On Relay1Off Toggle1 Cycle1On Cycle1Off" 'List of Subdir branches available as remote triggerssetpoint = 22 'thermostat setpointstatlist$ = "fan,heater" statmode$ = "fan" 'fan switches relay on if temp above setpoint, heater switches relay on if temp below setpoint.dallaspin = 1 'Dallas 1-wire temperature sensor pintemp$ = str$(val(tempr$(dallaspin,1)),"%2.1f")newtemp$ = ""enablestat = 0 '=1 to enable thermostat switchingenablescheduledOn = 0 '=1 to enable scheduled On timeenablescheduledOff = 0 '=1 to enable scheduled Off timeontime$ = "8:01"offtime$ = "8:02"unitslist$ = "secs, mins, hours, days"secs = 1: mins = secs * 60: hours = mins * 60: days = hours * 24enabledelayon = 0 '=1 enable cycleOn delayondelay = 7 'cycleOn delayondelunits$ = "days"ondelaycountdown = -1enabletimedon = 1 '=1 enable cycleOn durationonduration = 1 'cycleOn durationondurunits$ = "mins"ondurationcountdown = -1enabledelayoff = 1 '=1 enable cycleOff delayoffdelay = 10 'cycleOff durationoffdelunits$ = "secs"offdelaycountdown = -1enabletimedoff = 1 '=1 enable cycleOff durationoffduration = 30 'cycleOff durationoffdurationcountdown = -1offdurunits$ = "secs"instruction$ = "" 'variable to hold incoming instructionRXmsg$ = "" 'variable to hold incoming messagedata$ = "" 'variable to hold any incoming data which follows the instructionretryq$ = "" 'variable to hold all unexpired messages still waiting to be acknowledgedqdelimiter$ = "|" 'separates messages in the retryqtime2live = 30 'sent-message unacknowledged lifetime in secondsled1pin = 13: led1off = 1: pin.mode led1pin, output: pin(led1pin) = led1offrelay1pin = 12: relay1noff = 0: pin.mode relay1pin, output: pin(relay1pin) = relay1noff 'using active high qpio12 for relay1switchpin = 3: switchoff = 1: pin.mode switchpin, input, pullup 'switch normally high active lowswitchmode = 1 '1=bi-state lever switch, 0=momentary press to toggle buttoninterrupt switchpin, switchedbuttonpin = 0: pin.mode buttonpin, input, pullup 'using active low gpio0 buttonbuttonmode = 0 '1=bi-state lever switch, 0=momentary press to toggle buttoninterrupt buttonpin, pressedstart=0: stop=0 'used by button-pressed subroutine to differentiate between short and long pressesdebounce = 100longpress=3000 'longpress set high at 3 secs to minimise accidental triggering of blinkIPledstat$ = "green"blinks = 10 'blink default number of blinks, can be over-ridden by sending "nodename blink number_of_blinks"'gosub load 'ini file mechanism is not fully implementedgosub paintonhtmlchange changedonhtmlreload painttimer0 1000, ticker timer1 1000, Retry 'periodic timer to keep resending unACKed msgs until they expire udp.begin(udpport)onudp udpRXwlog "Started: " + time$ + " on " + date$waitpaint:clsautorefresh 1000a$ = a$ + |<br><div id='message' data-var='clicked' onclickx='cmdButton(this)' style='display: table; margin-right:auto;margin-left:auto;text-align:center;'>|if showtitle = 1 then a$ = a$ + title$ + "<br><br>"if showID = 1 then a$ = a$ + |<table align='center'><tr><td>| a$ = a$ + |Node name:</td><td>| + textbox$(nodename$,"tbname") + |</td></tr><tr><td>| a$ = a$ + cssid$("tbname", "color:Darkcyan;font-size:1.2em;width:150px;") a$ = a$ + |local IP:</td><td>| + localIP$ + |</td></tr><tr><td>| a$ = a$ + |UDP port:</td><td>| + textbox$(udpport,"tb40") + |</td></tr></td></tr></table><br><br>|endifif showbuttons = 1 then a$ = a$ + button$("Instant On",relay1on) + string$(9," ") + button$("Toggle", toggle1, "butled") + string$(9," ") + button$("Instant Off",relay1off) + |<br><br>| a$ = a$ + cssid$("butled", "height:3em; font-size:1.5em; border-radius:.4em; padding:.5em; color:white; background:" + ledstat$ + ";")endifif showclock = 1 then a$ = a$ + |<div style='display: table; margin-right:auto;margin-left:auto;text-align:center;borderx:1px solid gray;'>| a$ = a$ + |<style> @font-face { font-family: myfont; src: url('| + fontpath$ + fontfile$ + |');} </style><br>| a$ = a$ + |<div id='clock' style='font-family:myfont;background:lightcyan;color:dimgray;font-size:2.9em;border:1px solid gray;text-align:center;| a$ = a$ + |display: table; margin-right:auto;margin-left:auto;padding-left:.4em;padding-right:.4em;'>| + time$ + |</div><br>|endifif showscheduled = 1 then a$ = a$ + |<div style='display: table; margin-right:auto;margin-left:auto;text-align:center;borderx:1px solid gray;'>| a$ = a$ + checkbox$(enablescheduledon) + " " + |On Time:| + textbox$(ontime$,"tb40") + string$(9," ") a$ = a$ + checkbox$(enablescheduledoff) + " " + |Off Time:</td><td>| + textbox$(offtime$,"tb40") + |<br>| a$ = a$ + |</div>|endifhtml a$pause 200a$ = ""if (showtherm = 1) or (showstat = 1) then a$ = a$ + |<div style='display: table; margin-right:auto;margin-left:auto;text-align:center;borderx:1px solid gray;'>| a$ = a$ + |<br><div id='temp' style='font-family: myfont;background:LightGoldenRodYellow ;color:dimgray;font-size:1.8em;text-align:center;border:1px solid gray;| a$ = a$ + |display: table; margin-right:auto;margin-left:auto;border-radius:3em;padding-left:.4em;padding-right:.3em;'>| + temp$ + "˚" + |</div>| a$ = a$ + "<br>" if showstat = 1 then a$ = a$ + button$(" < ", statdown) + " " + textbox$(setpoint,"tb30") + " " + button$(" > ", statup) + "<br>" a$ = a$ + checkbox$(enablestat) + listbox$(statmode$,statlist$,"tb80") + "<br><br>" endif a$ = a$ + |</div>|endifif showcycled = 1 then a$ = a$ + |<div style='display: table; margin-right:auto;margin-left:auto;text-align:center;borderx:1px solid gray;'>| a$ = a$ + |<table align='center'><tr><td>| a$ = a$ + button$("Controlled On ", cycle1on) + string$(9," ") + |</td><td>| + "On delay " + |</td><td>| a$ = a$ + " " + checkbox$(enabledelayon) + |</td><td>| + " " + textbox$(ondelay,"tb40") + |</td><td>| + listbox$(ondelunits$,unitslist$,"tb60") + |</td><td>| a$ = a$ + string$(9," ") + " On duration " + |</td><td>| + checkbox$(enabletimedon) + |</td><td>| + " " + textbox$(onduration,"tb40") + |</td><td>| + listbox$(ondurunits$,unitslist$,"tb60") + |</td></tr><br><tr><td>| a$ = a$ + button$("Controlled Off ", cycle1off) + string$(9," ") + |</td><td>| + "Off delay " + |</td><td>| a$ = a$ + " " + checkbox$(enabledelayoff) + |</td><td>| + " " + textbox$(offdelay,"tb40") + |</td><td>| + listbox$(offdelunits$,unitslist$,"tb60") + |</td><td>| a$ = a$ + string$(9," ") + " Off duration " + |</td><td>| + checkbox$(enabletimedoff) + |</td><td>| + " " + textbox$(offduration,"tb40") + |</td><td>| + listbox$(offdurunits$,unitslist$,"tb60") + |</td></tr></table><br>| a$ = a$ + |</div>|endifif showtools = 1 then a$ = a$ + |<div style='display: table; margin-right:auto;margin-left:auto;text-align:center;borderx:1px solid gray;'>| a$ = a$ + "<br>" + button$("Blink", blink) + " " + textbox$(blinks,"tb40") + string$(9," ") a$ = a$ + button$("Blink IP", blinkip) + string$(9," ") a$ = a$ + button$("Send", sendudp) + string$(9," ") a$ = a$ + button$("Save settings", save) + "<br>" a$ = a$ + |</div>|endifif showswmodes = 1 then a$ = a$ + "<br> gpio0 On/Off flip switch " + checkbox$(buttonmode) + " or momentary toggle button<br>" a$ = a$ + " gpio" + str$(switchpin) + " On/Off flip switch " + checkbox$(switchmode) + " or momentary toggle button<br>"endifa$ = a$ + "<br>ShowSettings:" + checkbox$(showsettings)if showsettings = 1 then a$=a$+", ShowTitle:"+checkbox$(showtitle)+", ShowID:"+checkbox$(showid)+", Buttons:"+checkbox$(showbuttons) a$=a$+", ShowClock:"+checkbox$(showclock)+", ShowScheduled:"+checkbox$(showscheduled) a$=a$+", ShowTherm:"+checkbox$(showtherm)+", ShowStat:"+checkbox$(showstat) a$=a$+", ShowCycled:"+checkbox$(showcycled)+", ShowTools:"+checkbox$(showtools)+", ShowSWmodes:"+checkbox$(showswmodes)+"<br>"+"<br>"endifa$ = a$ + cssid$("tb30", "width:30; text-align:center; color:teal; background:GhostWhite;")a$ = a$ + cssid$("tb40", "width:40; text-align:center; color:teal; background:GhostWhite;")a$ = a$ + cssid$("tb60", "width:60; text-align:center; color:teal; background:GhostWhite;")a$ = a$ + cssid$("tb80", "width:80; text-align:center; color:teal; background:GhostWhite;")a$ = a$ + |</div>|html a$a$ = ""returnsettings:if visibility$ = "Hide" then visibility$ = "Show" else visibility$ = "Hide"refreshreturnchanged:ch$ = HtmlEventVar$if instr(ch$,"show") = 1 then gosub paintif len(word$(ontime$,2,":")) = 1 then ontime$ = replace$(ontime$,":",":0")if len(word$(offtime$,2,":")) = 1 then offtime$ = replace$(offtime$,":",":0")returnticker:if showclock = 1 then jscall |_$('clock').innerHTML = "| + time$ + |"| ' updates the digital clock displayif (showtherm = 1) or (enablestat = 1) then newtemp$ = str$(val(tempr$(dallaspin,1)),"%2.1f") if (newtemp$ <> temp$) then temp$ = newtemp$ jscall |_$('temp').innerHTML = "| + temp$ + "˚" + |"| ' updates the temp display endifendifif enablestat = 1 then if val(temp$) < setpoint then 'turn heater on or fan off if (statmode$ = "heater") and (pin(relay1pin) = relay1noff) then if relay1noff = 0 then pin(relay1pin) = 1 else pin(relay1pin) = 0 html cssid$("butled", "background:red;") endif if (statmode$ = "fan") and (pin(relay1pin) <> relay1noff) then pin(relay1pin) = relay1noff html cssid$("butled", "background:green;") endif endif if (val(temp$) > setpoint) then if (statmode$ = "heater") and (pin(relay1pin) <> relay1noff) then pin(relay1pin) = relay1noff html cssid$("butled", "background:green;") endif if (statmode$ = "fan") and (pin(relay1pin) = relay1noff) then pin(relay1pin) = 1 - relay1noff html cssid$("butled", "background:red;") endif endifendifif (enablescheduledon = 1) and (pin(relay1pin) = relay1noff) then if (val(word$(ontime$,1,":")) = val(word$(time$,1,":"))) and (val(word$(ontime$,2,":")) = val(word$(time$,2,":"))) then gosub relay1onendifif (enablescheduledoff = 1) and (pin(relay1pin) <> relay1noff) then if (val(word$(offtime$,1,":")) = val(word$(time$,1,":"))) and (val(word$(offtime$,2,":")) = val(word$(time$,2,":"))) then gosub relay1offendifif ondelaycountdown >= 0 then if pin(led1pin) = 0 then pin(led1pin) = 1 else pin(led1pin) = 0 if ondelaycountdown > 0 then ondelaycountdown = ondelaycountdown -1 else ondelaycountdown = -1 gosub relay1on COMMAND "m=" + ondurunits$ if enabletimedon = 1 then ondurationcountdown = onduration * m endifendifif ondurationcountdown >= 0 then if pin(led1pin) = 0 then pin(led1pin) = 1 else pin(led1pin) = 0 if ondurationcountdown > 0 then ondurationcountdown = ondurationcountdown -1 else ondurationcountdown = -1 gosub relay1off endifendifif offdelaycountdown >= 0 then if pin(led1pin) = 0 then pin(led1pin) = 1 else pin(led1pin) = 0 if offdelaycountdown > 0 then offdelaycountdown = offdelaycountdown -1 else offdelaycountdown = -1 gosub relay1off COMMAND "m=" + offdurunits$ if enabletimedoff = 1 then offdurationcountdown = offduration * m endifendifif offdurationcountdown >= 0 then if pin(led1pin) = 0 then pin(led1pin) = 1 else pin(led1pin) = 0 if offdurationcountdown > 0 then offdurationcountdown = offdurationcountdown -1 else offdurationcountdown = -1 gosub relay1on endifendifreturnstatup:setpoint = setpoint + 1newtemp$ = ""refreshreturnstatdown:setpoint = setpoint - 1if setpoint < 0 then setpoint = 0newtemp$ = ""refreshreturnudpRX: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 'ACKreturnACK:msg$ = "": getdata msg$, RXmsg$, " ", 1pos = word.find(retryq$,msg$,qdelimiter$)if pos > 0 then retryq$ = word.delete$(retryq$,pos,qdelimiter$)returnRETRY:if word.count(retryq$, qdelimiter$) > 0 thenif 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 endifendifreturnsendudp:sendmsg$ = sendmsg$ + " ID=" + str$(dateunix(date$) + timeunix(time$) + time2live, "%10d", 1)retryq$ = retryq$ + sendmsg$ + qdelimiter$udp.write netip$ + "255", udpport, sendmsg$returnsub SendQ(sendmsg$) sendmsg$ = sendmsg$ + " ID=" + str$(dateunix(date$) + timeunix(time$) + time2live, "%10d", 1)retryq$ = retryq$ + sendmsg$ + qdelimiter$udp.write netip$ + "255", udpport, sendmsg$end subsub Send(sendmsg$)udp.write netip$ + "255", udpport, sendmsg$end subsub GetData(ret$, v$, sep$, pos) 'extracts everything from the msg after the Instruction and puts into data$ (thanks cicciocb)local i, p, qp = 1for i = 1 to pos p = instr(p + 1, v$, sep$) if p > 0 then p = p + len(sep$)next iif p = 0 then ret$ = ""else q = instr(p+1, v$, sep$) if q = 0 then q = 999 ret$ = mid$(v$, p)end if end subsub 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 ifend subload:' Loads settings from file ... not yet fully implementeda$ = ""if FILE.EXISTS(filename$) > 0 then a$ = FILE.READ$(filename$) if WORD.GETPARAM$(a$,"nodename") <> "" then nodename$ = WORD.GETPARAM$(a$,"nodename") if WORD.GETPARAM$(a$,"ondelay") <> "" then ondelay = val(WORD.GETPARAM$(a$,"ondelay")) if WORD.GETPARAM$(a$,"onduration") <> "" then onduration = val(WORD.GETPARAM$(a$,"onduration")) if WORD.GETPARAM$(a$,"offdelay") <> "" then offdelay = val(WORD.GETPARAM$(a$,"offdelay")) if WORD.GETPARAM$(a$,"offduration") <> "" then offduration = val(WORD.GETPARAM$(a$,"offduration")) if WORD.GETPARAM$(a$,"udpport") <> "" then udpport = val(WORD.GETPARAM$(a$,"udpport"))endifreturnsave:' Saves settings to file ... not yet fully implementeda$ = ""if FILE.EXISTS(filename$) > 0 then a$ = FILE.READ$(filename$)WORD.SETPARAM a$, "nodename", nodename$WORD.SETPARAM a$, "ondelay", str$(ondelay)WORD.SETPARAM a$, "onduration", str$(onduration)WORD.SETPARAM a$, "offdelay", str$(offdelay)WORD.SETPARAM a$, "offduration", str$(offduration)WORD.SETPARAM a$, "udpport", str$(udpport)FILE.SAVE filename$, a$returnblink:if data$ <> "" then blinks = val(data$)ledstate = pin(led1pin)pin(led1pin) = led1offpause 200for count = 1 to blinksif led1off = 1 then pin(led1pin) = 0 else pin(led1pin) = 1pause 800pin(led1pin) = led1offpause 200next countpause 2000pin(led1pin) = ledstate 'Restore LED state to its previous statereturnblinkip:ledstate = pin(led1pin)blinkon = 150blinkoff = 300blinkpause = 1000blinkgap = 1400pin(led1pin) = led1offpause blinkpausefor pos = 1 to len(localIP$) digitchr$ = mid$(localIP$,pos,1) if digitchr$ = "." then pause blinkgap else if digitchr$ = "0" then digit = 10 else digit = val(digitchr$) for count = 1 to digit if led1off = 0 then pin(led1pin) = 1 else pin(led1pin) = 0 pause blinkon if led1off = 0 then pin(led1pin) = 0 else pin(led1pin) = 1 pause blinkoff next count pause blinkpause end ifnext pospause blinkgappin(led1pin) = ledstatereturnREPLY:udp.reply "Reply from " + Nodename$returnREPORT:udp.reply "Report from " + Nodename$ + " " + instructionslist$returnpressed:interrupt buttonpin, offpause debounceif pin(buttonpin) = 0 then start = millis else stop = millisif buttonmode = 1 then if pin(buttonpin) = 0 and (pin(relay1pin) = relay1noff) then gosub relay1on if pin(buttonpin) = 1 and (pin(relay1pin) <> relay1noff) then gosub relay1offelse if stop > start then if stop - start < longpress then gosub toggle1 else gosub blinkip endifendifinterrupt buttonpin, pressedreturnswitched:interrupt switchpin, offpause debounceif pin(switchpin) <> switchoff then if switchmode = 1 then gosub relay1on else gosub toggle1else if switchmode = 1 then gosub relay1offendifinterrupt switchpin, switchedreturnrelay1on:if pin(relay1pin) = relay1noff then if relay1noff = 0 then pin(relay1pin) = 1 else pin(relay1pin) = 0endifindicator = 0pin(led1pin) = 1 - led1offhtml cssid$("butled", "background:red;")ondelaycountdown = -1offdelaycountdown = -1ondurationcountdown = -1offdurationcountdown = -1returnrelay1off:if pin(relay1pin) <> relay1noff then pin(relay1pin) = relay1noffindicator = 1pin(led1pin) = led1offhtml cssid$("butled", "background:green;")ondelaycountdown = -1offdelaycountdown = -1ondurationcountdown = -1offdurationcountdown = -1returncycle1on:COMMAND "m=" + ondelunits$if enabledelayon > 0 then ondelaycountdown = ondelay * m else ondelaycountdown = 0returncycle1off:COMMAND "m=" + offdelunits$if enabledelayoff > 0 then offdelaycountdown = offdelay * m else offdelaycountdown = 0returntoggle1:if pin(relay1pin) = relay1noff gosub relay1on else gosub relay1offenablescheduledon = 0enablescheduledoff = 0enablestat = 0enabledelayon = 0enabledelayoff = 0enabletimedon = 0enabletimedoff = 0ondelaycountdown = -1offdelaycountdown = -1ondurationcountdown = -1offdurationcountdown = -1returnEND '-------------------- End ---------------------