All EasyNet nodes offer facility to send the changed status of any local switches or sensors.
This Alerts Monitor offers a central display of incoming sensor alerts from remote EasyNet nodes - it is configurable for 1 to 24 (or more) channels.
Usage: Target_name ALERT channel_number (lights the RED corresponding channel indicator LED)
Usage: Target_name CLEAR channel_number (optional GREEN OFF for bi-state sensors, momentary sensors extinguish after timeout)
To use, embed 'SendQ Target_name Alert channel_number' into the relevant event trigger subroutines of your EasyNet sensor nodes.
The unit is designed to be stand-alone using neo-pixels as channel
indicators, therefore the screen display is secondary and not essential.
The neo-pixels could either be in-line sticks or strips to give a similar looking result to the screen display (above).
Or neo-pixel strings could be strategically placed behind a
pictorial representation of the sensor locations (perhaps a floor plan printout, or aerial view, etc) to 'map' the positions
of the triggers.
Every channel can be assigned a name, eg: channel 1 might be 'Driveway', channel 2 might be 'Garage' etc.
If an EasyNet Voice Announcer was available it could also speak the
Alert and Clear messages, eg: "Driveway Alert", "Garage Door Open"
etc.
A default 'NoRepeatTimeout' prevents re-triggering annoyance for the specified duration, plus channels can be assigned individual timeouts. So the 'Front Porch' might have a 3 minute delay to give
sufficient time to attend, before re-notifying of the callers presence
in case it was missed.
A mailbox sensor might notify of the original delivery, then just keep reminding of a delivery once every hour until emptied.
Bi-state switches etc which send both an On state and an Off state switch the channel display from Alert red to Clear green.
Momentary red Alerts from PIRs etc will automatically extinguish after their timeout delay, unless a Clear is received to turn them green for off.
Re-occuring Alerts re-triggered during the no-retrigger timeout period will show as orange alerts, not red - but anything can be changed to suit.
The blue led of the 4-channel monitor above is using the channel 1 indicator to display the status of a local switch (eg: monitor On/Off).
The Setup button is left in the script in case it
comes in useful (rename it to suit), but it doesn't actually do anything other than write a
wlog entry.
You could create your own EasyNet instruction names to add into
instructionslist$ with a corresponding subroutine branch that reads data$
to get the channel number for offering facility to remotely change
timeouts of a particular channel, or even enable /disable the monitor or
just a channel.
Basic:
title$ = "EasyNet Alert Monitor 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$ = "Alerts\Monitor\Alarms" '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 Rename Load Save Restart Relay1ON Relay1OFF Relay1Toggle Alert Clear Enable Disable") 'local shared subdirs filename$ = word$(BAS.FILENAME$,1,".") + ".ini" 'comment this and next line if you don't want to load/save settings to ini file channels = 6 dim name$(channels + 1) dim led(channels + 1) dim lastalert(channels + 1) dim norepeat(channels + 1) norepeatdefault = 1 * 1000 * 20 'default no-retrigger delay (mins) to prevent repeat triggers during specified timeout for c = 1 to channels name$(c) = "" lastalert(c) = 0 norepeat(c) = norepeatdefault led(c) = 1 next c 'Assign individual names and no-repeat timeouts manually if wished 'name$(1) = " Door ": norepeat(1) = 3 *1000 *60 '(3 min) 'name$(3) = " Gate ": norepeat(3) = 1 *1000 *60 '(1 min) 'name$(7) = " Workshop ": norepeat(7) = 30 *1000 *60 '(30 mins) neo = channels 'change to match the number of neopixels in use, set to 0 if none neo.setup 13 'change to your desired neo-pixel driver pin neo.strip 0, neo -1, 0,0,0 'set all neopixels off neopercent = 10 'percentage brightness R = cint(255/100 * neopercent): G = cint(255/100 * neopercent): B = cint(255/100 * neopercent) gosub Load 'reads nodename$ from file if available 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 = 60 'sent-message unacknowledged lifetime in seconds ID$ = "" 'unique msg ID consists of send date+time + time2live - also acts as msg 'expire' time flag 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 button1pin = 0: pin.mode button1pin, input, pullup 'using active low gpio0 button interrupt button1pin, button1 sensor1pin = 14: pin.mode sensor1pin, input, pullup interrupt sensor1pin, triggered timer0 1000, heartbeat ' timer1 1000, Retry 'periodic timer to keep resending unACKed msgs until they expire udp.begin(udpport) onudp udpRX wlog "OK" onhtmlreload screen gosub screen wait screen: refresh cls a$ = "<div id='alerts' style='display: table; margin-right: auto; margin-left: auto;'>" a$ = a$ + "<BR><BR><table id='monitor' border='1'><tr>" a$ = a$ + "<td style='color:dimgray;'>" + "Alerts" + "</td>" for c = 1 to channels a$ = a$ + "<td>" + "<svg height='20' width='30'><circle cx='15' cy='10' r='9' stroke='black' fill='darkgray' id='led" + str$(c) + "'/></svg>" + "</td>" next c a$ = a$ + "</tr>" a$ = a$ + "<td style='color:dimgray;'>" + "channel" + "</td>" 'a$ = a$ + "<td>" + button$("Setup", setup, "but1") + "</td>" for c = 1 to channels if name$(c) <> "" then a$ = a$ + "<td>" + name$(c) + "</td>" else a$ = a$ + "<td>" + str$(c) + "</td>" next c a$ = a$ + "</tr></table>" a$ = a$ + cssid$("monitor", "font: 15px arial, sans-serif;padding:10;background-color: lightgrey; color:blue; text-align:center;") a$ = a$ + "</div>" html a$ a$ = "" return setup: wlog "Setup" return heartbeat: 'wlog str$(ramfree) for c = 1 to channels if lastalert(c) > 0 then if millis - norepeat(c) > lastalert(c) then lastalert(c) = 0 'comment the next 2 lines if you prefer not to extinguish after the timeouts TRACE "csshtml" + CSSID$("led" + str$(c), "fill: darkgray;") neo.pixel c -1, 0,0,0 endif endif next c 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 RENAME: if data$ <> "" then nodename$ = data$ return LOAD: 'Load settings from file, by default is only looking for nodename, add your own saved parameters to look for if wished a$ = "" if FILE.EXISTS(filename$) > 0 then a$ = FILE.READ$(filename$) if WORD.GETPARAM$(a$,"nodename$") <> "" then nodename$ = WORD.GETPARAM$(a$,"nodename$") return SAVE: 'Save settings to file, by default will only save nodename, add your own parameters to save if wished a$ = "" if FILE.EXISTS(filename$) > 0 then a$ = FILE.READ$(filename$) WORD.SETPARAM a$, "nodename$", nodename$ FILE.SAVE filename$, a$ return RESTART: reboot 'causes a device restart 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$ + " Ramfree=" + str$(ramfree) + " Flashfree=" + str$(flashfree) 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 button1: data$ = str$(2) 'set to required channel if pin(button1pin) = 0 then gosub Alert else gosub Clear 'comment out this line if you wish the button to act as momentary instead of bi-state endif return TRIGGERED: if pin(sensor1pin) = 1 then 'active high trigger ' sendq "All relay1ON" pin(ledpin) = 1 - ledoff pause 5000 ' sendq "ALL Relay1Off" pin(ledpin) = ledoff endif return alert: zone = val(data$) 'wlog "Alert:" + str$(zone) if millis - norepeat(zone) > lastalert(zone) then TRACE "csshtml" + CSSID$("led" + str$(zone), "fill: red;") neo.pixel zone -1, R,0,0 lastalert(zone) = millis else TRACE "csshtml" + CSSID$("led" + str$(zone), "fill: orange;") neo.pixel zone -1, R,G/4,0 lastalert(zone) = millis endif return clear: zone = val(data$) 'wlog "Clear:" + str$(zone) TRACE "csshtml" + CSSID$("led" + str$(zone), "fill: green;") neo.pixel zone -1, 0,G,0 lastalert(zone) = 0 return END '-------------------- End --------------------- Note to self: incorporate watchdog timer facility into Alerts Monitor to flag up any non communicating sensor nodes. |