Self-monitor anti-corruption health check
Posted: Wed Feb 01, 2017 8:38 am
Mmiscool, Esp_Basic can do a lot with just a few lines of code, as shown by the many excellent examples.
But once the script starts growing, there comes a point as more code is added when doing a SAVE starts causing ESP reboots. Once this begins happening, the chances keep increasing that the SAVEd script has been corrupted, and the 'heavier' the script, the more likely that the script will malfunction if it is RUN straight after a rebooted SAVE.
Such problems seem to be significantly reduced by doing a 2nd SAVE before trying to RUN.
Doing a 2nd (non-rebooted) SAVE allows things to keep progressing for a while, but eventually strange unpredictable symptoms can start appearing, typically showing up as incorrectly displayed web components in the browser (see the attached screendumps).
Sometimes the same symptoms persist until doing another 2nd SAVE... suggesting the symptoms were being caused by a corrupted script until it was subsequently overwritten.
But other times, merely doing a browser Back and re-RUN may fix the problems or change the symptoms...
...suggesting the changeable symptoms are caused by something more sinister than script corruption.
Sometimes this 'quantum weirdness' can be bypassed by repeatedly doing subsequent saves, but other times the symptoms persist even after multiple saves - which could be due to a script bug, but by now is likely to be caused by a corrupted Esp_Basic interpreter.
I understand that the size and complexity of my dev projects can show up symptoms which might not otherwise be apparent, and I've dealt with the situation as best I can for a long time. But the progressively worsening problems and inevitable outcome is proving an insurmountable obstruction to making the most of Esp_Basic, and if left unchecked I fear it will be a growing deterrent causing people to avoid attempting very much... which would be a great shame.
So I feel the onus is on me to bring the matter to your attention in case it's something you would wish to know.
It would obviously be very beneficial if Esp_Basic could elegently "End due to insufficient resources" rather than lose control and cause script or interpreter corruption.
So in case it may be useful if an investigation is considered, I have enclosed a script which exhibits all of those unpredictable and illogical symptoms mentioned above. I've been using 1Mb V3 A65 and Firefox because it is better at handling the frequent reboot disconnections. The hacked state of the script contents is not important, it's merely offered to demonstrate the interpreter problems.
There are 3 different functionality flags at the top of the script for enabling 'memguage' (shows ramfree), 'debugmode' (shows a debug message window), 'showtime' (shows elapsed time).
Currently all 3 are commented out so the script has a better chance of initially being SAVEd and RUN ok.
If any 1 feature is enabled by uncommenting it, it can usually be persuaded to work ok on its own by doing a 2nd clean SAVE straight after the inevitable rebooted save.
But if more than 1 feature is enabled at the same time, the script will not RUN for me, but instead will crash and reboot every time. So the difference of enabling more than 1 feature is evidently pushing Esp_Basic over some sort of invisible threshold which is causing it to fall off a cliff.
Is it possible that the invisible 'Safe/Unsafe' threshold might have some tangible value which could be monitored by Esp_Basic for flagging up its own sanity check and error message to prevent it from jumping off the cliff?
Also, weird symptoms of interpreter corruption are a particularly difficult problem to notice if you're busy chasing the usual mixed bag of dev bugs.
So it would be very handy if Esp_Basic could include its own self-diagnosis anti-corruption health-check facility - perhaps each different build might include its own checksum hash, and the interpreter could simply scan memory to create a live checksum for comparison... if not done automatically at start-up, then perhaps on demand from a 'Scan for Corruption' button on the SETUP page?
But once the script starts growing, there comes a point as more code is added when doing a SAVE starts causing ESP reboots. Once this begins happening, the chances keep increasing that the SAVEd script has been corrupted, and the 'heavier' the script, the more likely that the script will malfunction if it is RUN straight after a rebooted SAVE.
Such problems seem to be significantly reduced by doing a 2nd SAVE before trying to RUN.
Doing a 2nd (non-rebooted) SAVE allows things to keep progressing for a while, but eventually strange unpredictable symptoms can start appearing, typically showing up as incorrectly displayed web components in the browser (see the attached screendumps).
Sometimes the same symptoms persist until doing another 2nd SAVE... suggesting the symptoms were being caused by a corrupted script until it was subsequently overwritten.
But other times, merely doing a browser Back and re-RUN may fix the problems or change the symptoms...
...suggesting the changeable symptoms are caused by something more sinister than script corruption.
Sometimes this 'quantum weirdness' can be bypassed by repeatedly doing subsequent saves, but other times the symptoms persist even after multiple saves - which could be due to a script bug, but by now is likely to be caused by a corrupted Esp_Basic interpreter.
I understand that the size and complexity of my dev projects can show up symptoms which might not otherwise be apparent, and I've dealt with the situation as best I can for a long time. But the progressively worsening problems and inevitable outcome is proving an insurmountable obstruction to making the most of Esp_Basic, and if left unchecked I fear it will be a growing deterrent causing people to avoid attempting very much... which would be a great shame.
So I feel the onus is on me to bring the matter to your attention in case it's something you would wish to know.
It would obviously be very beneficial if Esp_Basic could elegently "End due to insufficient resources" rather than lose control and cause script or interpreter corruption.
So in case it may be useful if an investigation is considered, I have enclosed a script which exhibits all of those unpredictable and illogical symptoms mentioned above. I've been using 1Mb V3 A65 and Firefox because it is better at handling the frequent reboot disconnections. The hacked state of the script contents is not important, it's merely offered to demonstrate the interpreter problems.
There are 3 different functionality flags at the top of the script for enabling 'memguage' (shows ramfree), 'debugmode' (shows a debug message window), 'showtime' (shows elapsed time).
Currently all 3 are commented out so the script has a better chance of initially being SAVEd and RUN ok.
If any 1 feature is enabled by uncommenting it, it can usually be persuaded to work ok on its own by doing a 2nd clean SAVE straight after the inevitable rebooted save.
But if more than 1 feature is enabled at the same time, the script will not RUN for me, but instead will crash and reboot every time. So the difference of enabling more than 1 feature is evidently pushing Esp_Basic over some sort of invisible threshold which is causing it to fall off a cliff.
Is it possible that the invisible 'Safe/Unsafe' threshold might have some tangible value which could be monitored by Esp_Basic for flagging up its own sanity check and error message to prevent it from jumping off the cliff?
Also, weird symptoms of interpreter corruption are a particularly difficult problem to notice if you're busy chasing the usual mixed bag of dev bugs.
So it would be very handy if Esp_Basic could include its own self-diagnosis anti-corruption health-check facility - perhaps each different build might include its own checksum hash, and the interpreter could simply scan memory to create a live checksum for comparison... if not done automatically at start-up, then perhaps on demand from a 'Scan for Corruption' button on the SETUP page?
Code: Select all
memclear
'Un-comment any of the 3 below to show that any of them work ok by themselves, but not enough memory for 2 or more.
'debugmode = "y" 'Un-comment to show debug messages
'memguage = "y" 'Un-comment to show memory usage guage
'showtime = "y" 'Un-comment to show elapsed time
if memguage = "y" then
max = ramfree()
min = 1000
memfree = ramfree()
endif
localIP = ip()
pos = instrrev(localIP,".")
netIP = left(localIP,pos)
nodeIP = mid(localIP,pos+1)
title = "EasyNet"
localname = "" 'Populate for clarity, else will be uniquely named "NODE" + last IP address byte
if localname = "" then localname = "Node" & nodeIP
localname = upper(localname) 'Names are deliberately case-insensitive to offer simplest ease of use
groupname = upper("some") 'Optional way of being addressed by sub groups (ie: PIRs, LIGHTs, etc)
globalname = upper("all") 'Optional way of being addressed by large groups or systems (ie: CCTV, HouseAlarm, etc)
udpport = 5001 'Dev default, so change to suit, different ports could be used for different separated systems
udp = "y"
serial1 = "y"
serial2 = "y"
buttonmode = "MultiMode" 'Quick press (<2s) to toggle led, long press (>2s)to blink ip
buttonpin = 0 'Uses gpio00 flashing button by default, change to suit (needs pullup resistor).
buttonoff = 1 'Default botton OFF state
ledpin = 1 'Uses onboard gpio01 blue led by default, change to suit
ledoff = 1 'Default led pin off state (allows configuring led pin for active high or active low operation)
pulsepin = 16 'Optional Pulse timer led showing device is alive
relaypin = 2 'Set to the required Relay control pin
relayoff = 0 'Default relay OFF state - Allows for use of active high or active low relay (or alternative output)
led2pin = 15 'Led2 gpio01
led2off = 1 'Default led2 pin off state (allows configuring led pin for active high or active low operation)
relay2pin = 12 'Set to the required Relay2 control pin
relay2off = 0 'Default relay OFF state - Allows for use of active high or active low relay (or alternative output)
if ledoff = 1 then io(po,ledpin,1) else io(po,ledpin,0) 'initialise led to its off state
if relayoff = 1 then io(po,relaypin,1) else io(po,relaypin,0) 'initialise relay to its off state
if led2off = 1 then io(po,led2pin,1) else io(po,led2pin,0) 'initialise led2 to its off state
if relay2off = 1 then io(po,relay2pin,1) else io(po,relay2pin,0) 'initialise relay2 to its off state
'if ledpin = 1 then gpio1reset()
ondelay = 0
ondurat = 3000
offdelay = 1000
offdurat = 4000
on2delay = 2000
on2durat = 6000
off2delay = 0
off2durat = 5000
usercmds = "MyCommand " 'Add your own user commands here, remember to add a corresponding uppercase named branch
commoncmds = " Toggle Toggle2 Relay Relay2" 'Common commands
systemcmds = " Blinks BlinkIP ? Help ! Ping Debug MemGuage Exit Reboot " 'System commands
vocabulary = usercmds & " " & commoncmds & " " & systemcmds
payload = ""
sentq = ""
qos = 0 'quality of service, number of unacknowledged transmit retries before giving up
qid = 0 'unique id of sentq msgs
ims = "" 'input message stream - local, serial, serial2, udp
oms = "local" 'output message stream - local, serial, serial2, udp
source = "" 'optional source name or address
target = "" 'list of intended subcribers names, group names, and/or IP addresses
words = 0
start = 0 'used by MultiMode button-pressed timer
stop = 0 'used by MultiMode button-pressed timer
numblinks = 5
etime = ""
days = 0
hours = 0
mins = 0
secs = 0
if serial1 = "y" then serialbranch [SERIAL1MSG]
if serial2 = "y" then serial2branch [SERIAL2MSG]
if udp = "y" then
udpbegin udpport
udpbranch [UDPMSG]
endif
interrupt buttonpin, [PRESSED]
[HOME]
cls
html |<!DOCTYPE html>|
html |<h2 style="width:100%;color: blue;text-align:center;margin:auto;display:block;">| & title & |</h2>|
html |<hr>|
if memguage = "y" then
html |<table style="width:100%;margin:auto;text-align:center;">|
html | <tr style="top-padding:0px;bottom-padding:0px;">|
html | <td style="text-align:left;color: red;">1000 min </td>|
html | <td style="color:orange;text-align:center;">avail |
textbox memfree
cssid htmlid(), "color:black;text-align:center;"
html |</td>|
html | <td style="text-align:right;color:green;">max | & max & |</td>|
html | </tr>|
html |</table>|
meter memfree, min, max ' *************** MEMORY METER *******************
cssid htmlid(), "width:100%; height:10px; color:red; background-color:blue;display:block;"
endif
html "<BR>"
html |<table style="font-size: 20px; color:rgb(128,128,128);">|
html | <tr>|
html | <td>Localname: </td>|
html | <td style="color: darkblue;"><b>| & localname & |</b></td>|
html | </tr>|
html | <tr>|
html | <td>Groupname: </td>|
html | <td style="color: darkviolet;">| & groupname & |</td>|
html | </tr>|
html | <tr>|
html | <td>Globalname: </td>|
html | <td style="color: orange;">| & globalname & |</td>|
html | </tr>|
html | <tr>|
html | <td>IP address: </td>|
html | <td style="color: darkgreen;">| & localIP & |</td>|
html | </tr>|
html | <tr>|
html | <td>UDP port: </td>|
html | <td style="color: darkred;">| & udpport & |</td>|
html | </tr>|
if showtime = "y" then
html | <tr>|
html | <td>Elapsed time: </td>|
html | <td style="color: grey;">|
tmp = "width:20px;padding:0px;text-align:right;border:0;font-size:18px;color: grey;"
textbox days
cssid htmlid(), tmp
html |d: |
textbox hours
cssid htmlid(), tmp
html |h: |
textbox mins
cssid htmlid(), tmp
html |m: |
textbox secs
cssid htmlid(), tmp
html |s|
html | </td>|
html | </tr>|
endif
html | <tr>|
html | <td> </td>|
html | </tr>|
html |</table>|
html |<br><br>|
html |<table style="width:100%;font-size:20px;color:rgb(128,128,128);">|
html | <tr style="top-padding:0px;bottom-padding:0px;">|
html | <td style="width:200px;text-align:left;">Received Message: </td>|
html | <td style="background-color: GhostWhite;color:gray;;width:50%;">|
'payload = "display incoming messages"
textbox payload
tmp = "font-size:20px;background-color:cyan;color:gray;width:100%;text-align:center;padding:6px;border-radius:13px;"
cssid htmlid(), tmp
html | </td>|
html | <td style="padding-right:20px;width:200px;text-align:right;cursor:pointer;"> </td>|
html | </tr>|
html | <tr style="top-padding:0px;bottom-padding:0px;">|
html | <td style="width:200px;text-align:left;"> </td>|
html | <td style=""> </td>|
html | <td style="width:200px;text-align:left;height:40px;"> </td>|
html | </tr>|
html | <tr style="top-padding:0px;bottom-padding:0px;">|
html | <td style="width:200px;text-align:left;">Transmit Message: </td>|
html | <td style="background-color: white;color:blue;">|
messageout = "Toggle2"
textbox messageout
tmp = "font-size:20px;color:darkblue;width:100%;margin:auto;padding:6px;text-align:center;background-color:GhostWhite;"
cssid htmlid(), tmp
html | </td>|
html | <td style="width:200px;padding:6px;text-align:left"> |
oms="Local"
dropdown oms, "Local,Serial1,Serial2,UDP"
cssid htmlid(), "background-color:yellow;padding:2px;"
button "Send", [SEND]
cssid htmlid(), "padding-left:3px;height:27px;"
html | </td>|
html | </tr>|
html |</table>|
if debugmode == "y" then
debugmsg = ""
html |<table style="width:100%;font-size:20px;color:rgb(128,128,128);position: fixed; bottom: 20;">|
html | <tr style="top-padding:0px;bottom-padding:0px;">|
html | <td style="width:200px;text-align:left;">Debug Message: </td>|
html | <td style="">|
textbox debugmsg
cssid htmlid(),"font-size:20px;background-color:whitesmoke;color:red;width:100%;height:40px;margin:auto;padding:6px;"
html | </td>|
memfree = ramfree()
html | <td style="padding-right:20px;width:200px;text-align:right;">|
'tmp = "font-size:16px;color:yellow;margin:auto;padding:2px;text-align:center;background-color:red;"
'textbox memfree
'cssid htmlid(), tmp
html | </td>|
html | </tr>|
html |</table>|
endif
html "<BR><BR>"
udpwrite netIP & "255", udpport, "Node " & Localname & " started."
if memguage = "y" then memfree = ramfree()
timer 1000, [PULSE]
wait
[PULSE]
'if io(laststat,pulsepin) = 1 then io(po,ledpin,0) else io(po,ledpin,1)
secs=secs + 1
counter1 = counter1 + 1
if io(laststat,pulsepin) = 1 then
io(po,pulsepin,0)
else
io(po,pulsepin,1)
endif
if secs > 59 then
secs = 0
mins = mins + 1
endif
if mins > 59 then
mins = 0
hours = hours + 1
endif
if hours > 23 then
hours = 0
days = days + 1
endif
'etime = days & "days : " & hours & "hours : " & mins & "mins :" & secs & "secs"
'if secs = 10 then debugmsg = etime
if memguage = "y" then memfree = ramfree()
wait
[SEND]
target = ""
qos = 0
payload = messageout
if oms="Local" then
if instr(payload,"target=") = 0 then payload = trim(payload) & " target=" & localname
gosub [PARSER]
else
gosub [PUBLISH]
endif
if memguage = "y" then memfree = ramfree()
wait
[PUBLISH]
'debugmsg = payload & " oms=" & oms
if qos>0 then
' retries = qos
qid = mins + secs
payload = payload & " " & qid'
start retries timer
sentq = payload & "*" & sentq
debugmsg = "Publish " & sentq
serialprintln "PUBLISH sentq=" & sentq
endif
if oms="Local" then
target = localname
gosub [PARSER]
else
if oms="Serial1" then
serialprintln payload
else
if oms="Serial2" then
serial2println payload
else
if oms="UDP" then
udpwrite netIP & "255", udpport, payload
endif
endif
endif
endif
if memguage = "y" then memfree = ramfree()
wait
[SERIAL1MSG]
serialinput payload
if asc(right(payload,1)) <33 then payload = left(payload,len(payload)-1)
ims="serial1"
gosub [PARSER]
if memguage = "y" then memfree = ramfree()
return
[SERIAL2MSG]
serial2input payload
if asc(right(payload,1)) <33 then payload = left(payload,len(payload)-1)
ims="serial2"
gosub [PARSER]
if memguage = "y" then memfree = ramfree()
return
[UDPMSG]
payload = udpread()
ims="udp"
gosub [PARSER]
if memguage = "y" then memfree = ramfree()
return
[PARSER]
if memguage = "y" then memfree = ramfree()
payload = trim(payload)
if payload = "" then return
if asc(right(payload,1)) < 33 then payload = left(payload,len(payload) - 1
qos = 0
source = ""
target = ""
words = 0
'serialprintln "Before Payload="&payload
do
words = words + 1
tmp = word(payload,words)
' serialprintln "Words count="&words &", Tmp="&tmp
' serialprintln "Target="&target&", QOS="&qos&", ims="&ims&", oms="&oms&", source="&source
if tmp <> "" then
pos = instr(tmp,"target=")
if pos > 0 then
target = word(tmp,2,"=")
' payload = replace(payload," " & tmp,"")
endif
pos = instr(tmp,"qos=")
if pos > 0 then
qos = word(tmp,2,"=")
payload = replace(payload," " & tmp,"")
endif
pos = instr(tmp,"qid=")
if pos > 0 then
qid = word(tmp,2,"=")
' payload = replace(payload," " & tmp,"")
endif
pos = instr(tmp,"ims=")
if pos > 0 then
ims = word(tmp,2,"=")
payload = replace(payload," " & tmp,"")
endif
pos = instr(tmp,"source=")
if pos > 0 then
source = word(tmp,2,"=")
payload = replace(payload," " & tmp,"")
endif
else
' serialprintln "Blank tmp. Words count="&words &", Tmp="&tmp
words = words - 1
endif
loop while tmp <> ""
' serialprintln "After Payload="&payload
' serialprintln "Words count="&words &", Tmp="&tmp
' serialprintln "Target="&target&", QOS="&qos&", ims="&ims&", oms="&oms&", source="&source
if upper(target)=upper("Local") then target = localname
target = upper(target)
'debugmsg = "Parser payload='"&payload&"', Target='"&target&"', Qos='"&qos&"', ims='"&ims&"', Source='"&source
if instr(target,localname)>0 or instr(target,groupname)>0 or instr(target,globalname)>0 or target=localIP then
if instr(upper(payload)," ACK") <> 0 then gosub [ACKIN] else gosub [SUBSCRIBE]
else
debugmsg = "'"&payload & "': Not a target for '" & target &"', Qos='"&qos
endif
if memguage = "y" then memfree = ramfree()
return
[SUBSCRIBE]
debugmsg = "SUBSCRIBE, payload=" & payload
tmp = upper(word(payload,1))
if debugmode = "x" then html "Subscribe command=" & tmp & ", length=" & len(tmp) & "<BR>"
if instr(upper(vocabulary),tmp) > 0 then
if qos > 0 then gosub [ACKOUT]
gosub "[" & tmp & "]"
else
tmp = "ERROR: Command (" & tmp & ") not recognised in Payload (" & payload & ")"
if debugmode == "x" then html tmp & "<BR>"
if ims="udp" then udpreply tmp
if ims="serial1" then serialprintln tmp
if ims="serial2" then serial2println tmp
endif
if memguage = "y" then memfree = ramfree()
return
[ACKOUT]
if ims="udp" then udpreply payload & " ACK"
if ims="serial1" then serialprintln payload & " ACK"
if ims="serial2" then serial2println payload & " ACK"
if memguage = "y" then memfree = ramfree()
return
[ACKIN]
'sentq = "" 'dev test xxxxxx
'debugmsg = "ACKIN, payload=" & payload & ", SentQ=" & sentq
'end
words = 0
do
words = words + 1
tmp = word(sentq,words,"*")
' serialprintln "Words count="&words &", Tmp="&tmp
' serialprintln "Target="&target&", QOS="&qos&", ims="&ims&", oms="&oms&", source="&source
if instr(tmp, qid) <> 0 then sentq = replace(sentq,tmp & "*","")
loop while tmp <> ""
serialprintln "ACKOUT sentq=" & sentq
'Target="&target&", QOS="&qos&", ims="&ims&", oms="&oms&", source="&source'clear all vars
debugmsg = "ACKIN: sentq=" & sentq
if memguage = "y" then memfree = ramfree()
return
[PRESSED]
if io(laststat,buttonpin) = 0 then start = millis() else stop = millis()
if stop > start then
if stop-start < 2000 then gosub [TOGGLE2] else gosub [BLINKIP]
endif
if memguage = "y" then memfree = ramfree()
wait
[PING]
[!]
udpreply localname & " " & localIP & " PING acknowledged"
serialprintln localname & " " & localIP & " PING acknowledged"
html localname & " " & localIP & " acknowledged<BR>"
if memguage = "y" then memfree = ramfree()
return
[BLINKS]
let oldstate = io(laststat,ledpin) 'Remember LED state before blinking
io(po,ledpin,ledoff)
if val(word(payload,2)) > 0 and val(word(payload,2)) < 99 then numblinks = val(word(payload,2))
data = ""
delay 200
for count = 1 to numblinks
if ledoff = 0 then io(po,ledpin,1) else io(po,ledpin,0)
delay 600
if ledoff = 0 then io(po,ledpin,0) else io(po,ledpin,1)
delay 400
next count
delay 2000
io(po,ledpin,oldstate) 'Restore LED state to its prior value
if memguage = "y" then memfree = ramfree()
return
[BLINKIP]
oldstate = io(laststat,ledpin) 'remember original state
blinkon = 200
blinkoff = 400
blinkpause = 1000
blinkgap = 1400
if ledoff = 0 then io(po,ledpin,0) else io(po,ledpin,1) ' turn led off
delay blinkpause
for pos = 1 to len(localIP)
digitchr = mid(localIP,pos,1)
if digitchr = "." then
delay blinkgap
else
if digitchr = "0" then digit = val("10") else digit = val(digitchr)
for count = 1 to digit
if ledoff = 0 then io(po,ledpin,1) else io(po,ledpin,0)
delay blinkon
if ledoff = 0 then io(po,ledpin,0) else io(po,ledpin,1)
delay blinkoff
next count
delay blinkpause
end if
next pos
delay blinkgap
io(po,ledpin,oldstate) 'restore to original state
if memguage = "y" then memfree = ramfree()
return
[EXIT]
end
[REBOOT]
reboot
[HELP]
[?]
return
[TOGGLE]
if io(laststat,ledpin) = 1 then io(po,ledpin,0) else io(po,ledpin,1)
if io(laststat,relaypin) = 1 then io(po,relaypin,0) else io(po,relaypin,1)
if memguage = "y" then memfree = ramfree()
return
[RELAY]
if upper(word(payload,2)) = "ON" then gosub [RELAYON]
if upper(word(payload,2)) = "OFF" then gosub [RELAYOFF]
if upper(word(payload,2)) = "TOGGLE" then gosub [TOGGLE]
if upper(word(payload,2)) = "CYCLEON" then gosub [CYCLEON]
if upper(word(payload,2)) = "CYCLEOFF" then gosub [CYCLEOFF]
if memguage = "y" then memfree = ramfree()
return
[RELAYON]
if io(laststat,relaypin) <> relayoff then return
if relayoff = 0 then io(po,relaypin,1) else io(po,relaypin,0)
if ledoff = 0 then io(po,ledpin,1) else io(po,ledpin,0)
if memguage = "y" then memfree = ramfree()
return
[RELAYOFF]
if io(laststat,relaypin) = relayoff then return
if relayoff = 0 then io(po,relaypin,0) else io(po,relaypin,1)
if ledoff = 0 then io(po,ledpin,0) else io(po,ledpin,1)
if memguage = "y" then memfree = ramfree()
return
[CYCLEON]
if ondelay <> 0 then delay ondelay
if relayoff = 0 then io(po,relaypin,1) else io(po,relaypin,0)
if ledoff = 0 then io(po,ledpin,1) else io(po,ledpin,0)
if ondurat = 0 then return
delay ondurat
gosub [RELAYOFF]
if memguage = "y" then memfree = ramfree()
return
[CYCLEOFF]
if offdelay <> 0 then delay offdelay
if relayoff = 0 then io(po,relaypin,0) else io(po,relaypin,1)
if ledoff = 0 then io(po,ledpin,0) else io(po,ledpin,1)
if offdurat = 0 then goto return
delay offdurat
goto [RELAYON]
if memguage = "y" then memfree = ramfree()
return
[RELAY2]
if upper(word(payload,2)) = "ON" then gosub [RELAY2ON]
if upper(word(payload,2)) = "OFF" then gosub [RELAY2OFF]
if upper(word(payload,2)) = "TOGGLE" then gosub [TOGGLE2]
if upper(word(payload,2)) = "CYCLEON" then gosub [CYCLE2ON]
if upper(word(payload,2)) = "CYCLEOFF" then gosub [CYCLE2OFF]
return
[RELAY2ON]
if io(laststat,relay2pin) <> relay2off then return
if relay2off = 0 then io(po,relay2pin,1) else io(po,relay2pin,0)
if led2off = 0 then io(po,led2pin,1) else io(po,led2pin,0)
if memguage = "y" then memfree = ramfree()
return
[RELAY2OFF]
if io(laststat,relay2pin) = relay2off then return
if relay2off = 0 then io(po,relay2pin,0) else io(po,relay2pin,1)
if led2off = 0 then io(po,led2pin,0) else io(po,led2pin,1)
if memguage = "y" then memfree = ramfree()
return
[TOGGLE2]
if io(laststat,led2pin) = 1 then io(po,led2pin,0) else io(po,led2pin,1)
if io(laststat,relay2pin) = 1 then io(po,relay2pin,0) else io(po,relay2pin,1)
if memguage = "y" then memfree = ramfree()
return
[CYCLE2ON]
if on2delay <> 0 then delay on2delay
if relay2off = 0 then io(po,relay2pin,1) else io(po,relay2pin,0)
if led2off = 0 then io(po,led2pin,1) else io(po,led2pin,0)
if on2durat = 0 then return
delay on2durat
gosub [RELAY2OFF]
if memguage = "y" then memfree = ramfree()
return
[CYCLE2OFF]
if off2delay <> 0 then delay off2delay
if relay2off = 0 then io(po,relay2pin,0) else io(po,relay2pin,1)
if led2off = 0 then io(po,led2pin,0) else io(po,led2pin,1)
if off2durat = 0 then goto return
delay off2durat
gosub [RELAY2ON]
if memguage = "y" then memfree = ramfree()
return