/* * IMPORTS * */ // Load up the Rocky web API management #require "Rocky.agent.lib.nut:3.0.0" // Load up the Twilio library #require "Twilio.class.nut:1.0" /* * CONSTANTS: WEB UI HTML * */ const HTML_STRING = @" NetTester

Net Monitor

Device is disconnected

Device IP

WAN IP

Gateway

Netmask

Broadcast IP

Unknown

Unknown

Unknown

Unknown

Unknown


Nearby Networks

 


 

"; /* * CONSTANTS: IMAGES * */ const LOCK_PNG = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52\x00\x00\x00\x80\x00\x00\x00\x80\x08\x03\x00\x00\x00\xF4\xE0\x91\xF9\x00\x00\x00\x04\x67\x41\x4D\x41\x00\x00\xB1\x8F\x0B\xFC\x61\x05\x00\x00\x00\x01\x73\x52\x47\x42\x00\xAE\xCE\x1C\xE9\x00\x00\x00\x24\x50\x4C\x54\x45\x47\x70\x4C\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x14\x7A\x5D\x67\x00\x00\x00\x0B\x74\x52\x4E\x53\x00\xC0\x3F\x58\x90\x13\xF2\x77\xA9\xDF\x2A\x3B\xC8\xA4\xD6\x00\x00\x02\xA0\x49\x44\x41\x54\x78\xDA\xED\x9B\xDB\x72\xEB\x20\x0C\x45\x8D\x10\xF7\xFF\xFF\xDF\x93\x76\x9A\x4E\xE2\x38\x53\xD0\x16\x68\xDA\xC3\x7E\xB7\xB5\x0C\x42\x02\x09\x1F\xC7\x96\x48\xD5\xA7\xEC\x5C\x29\xB1\xC5\x52\x9C\xCB\x89\xC2\x3A\xE3\xC4\x2E\xB6\x57\x95\xEC\x57\x40\xF8\x7C\x65\xFC\x2E\xE7\xE7\x5A\x0F\xA9\xB4\x1F\x14\xD3\xBC\x61\x08\x1C\x5B\x87\x22\x4F\x42\xF0\x5D\xE6\x3F\xBD\x61\xC6\x44\x54\xD7\x06\x94\x83\xDD\xE7\x7F\x0D\x02\xE9\xDA\x4F\x6D\x58\xAA\xD3\x90\x9B\x40\x6C\x6C\x5F\x91\x80\x5B\x33\x25\x48\x4D\xAC\xA4\x12\xF9\x1B\x20\x05\x4F\x0C\xEF\x83\x6F\xBC\x65\x41\xBE\xE9\x96\x16\xDF\x2D\xD2\x58\x67\x39\x60\x61\x5F\x9F\xD3\x33\x5F\x92\x16\x34\x22\xF9\xEB\x68\x4F\xD7\x69\x3A\xEA\x3B\x62\x19\x4A\x36\xE1\x62\xC1\x90\xF6\x0A\x70\x61\x2C\x65\x14\xC8\x03\xE3\xF8\xCA\x4A\x9A\x6B\xF1\xE5\x65\x91\xC6\xDD\x26\x06\x3D\x0F\x88\x24\x71\xDC\xA4\xB7\x04\x48\xF4\x9C\x7C\x08\xB2\xF4\x53\x58\x67\x08\xC2\xD9\xFF\xA5\x73\x57\x94\x66\xA0\x8A\x13\x88\x30\x25\x38\x20\xA6\x65\xE9\xD8\x3D\xEA\x14\x04\x86\x5C\x89\x80\x67\xDF\xBC\x83\x91\xE1\xF3\x0A\x51\x88\x10\x07\x72\xB8\x0B\x8C\x7A\xF2\x29\x8A\x07\xD8\x05\x18\xE3\x27\x38\x0A\x10\x36\x83\x0C\x47\x01\xD0\x87\x1D\xEA\x83\x82\x17\x80\x1F\x70\x8A\xE7\x19\x4D\xA5\xE3\xBB\xD3\x8C\xE6\x13\x34\x12\xC0\x91\x24\x83\x5E\x58\x74\x01\x32\x18\x06\x60\x00\x07\x3A\x31\x0C\x50\xC0\x38\x94\x7E\x3D\x40\xEC\xB3\x5A\xC9\xDF\x75\x4A\xC6\x34\xAC\xE7\x65\x14\xBF\x5F\x4C\xF5\x4D\x66\xAA\x5C\xDA\x22\xB9\x8B\x6A\x66\xC8\x6D\xA5\xE2\x79\x4E\x29\xB6\xC5\x7A\x3E\xB8\xFB\xB6\x5E\xE5\x21\x3D\xD4\xD8\x2C\x08\x42\x47\x19\x66\xAA\xB2\x42\x1D\x0C\x13\xBD\xAD\x02\xAC\x5A\x8E\x76\x1E\xF8\xB4\x59\xCE\x76\x00\xFE\xEA\x0C\xB8\x52\x7C\x75\x06\x5C\xBF\x0E\xEC\xEC\x7F\x79\xE1\x06\xD8\x00\x1B\x60\x03\x20\x2F\xC8\xCC\xB9\x58\x01\x7C\x77\xEA\x2B\x9B\x00\x3C\x36\x4E\xAA\x5B\x0F\x90\x34\xBA\xAB\x00\x40\xFA\xA1\xB0\x3B\x1B\xC0\xF5\xB4\x77\x66\x02\x54\x9D\x16\xAF\x18\xC0\x75\x14\xB7\xA7\x02\x78\xA5\x2E\xBF\x18\xA0\x2A\xB5\xD9\xC5\x00\x41\xA9\xCF\x2E\x06\x38\xAC\x01\x42\x77\xA3\x7B\x12\x00\x29\x5D\xF5\x10\x03\x24\xEB\x55\xE0\xFA\x3A\xDD\x13\x23\x21\xE9\x5C\xB6\xD1\xCC\x05\x75\x71\x36\xE4\xFE\xCB\x2E\x93\xF6\x03\xAC\x60\x1F\xDB\x11\xE5\x87\x78\xEC\x85\xFB\x42\x70\x53\x7A\xDF\x94\x91\xB8\xC2\x80\xEE\x8A\x09\xBC\x6C\xF6\xFB\x01\xFC\x7F\x0F\x90\x0E\xB0\xCA\x85\x02\xB8\x03\xAC\x72\xC1\x67\x43\x02\x0B\x9D\x30\x40\x64\xEF\xB9\x19\x02\xEC\xFA\xC0\x06\xD8\x00\x1B\x60\x03\x6C\x80\x0D\xF0\x47\x00\xCC\xFB\x86\xC5\x0E\x80\xAD\x5B\xB7\xC9\xF6\xFA\xC0\xFD\x68\x55\xCD\xEC\x97\xE3\xB0\x9D\x83\x84\x54\x57\x14\x07\x00\x39\xDC\x29\x78\x80\xDD\x25\x8A\x64\x77\x91\xEB\xC5\xFE\xC7\x5A\x5C\x1C\x0F\x5F\x7F\xC9\xEC\xFC\xA7\x56\xC9\xFC\xF5\xFD\x44\xFA\xF8\x6F\x6D\xBE\x38\xE1\xBF\xC0\xFD\x25\xFD\x03\x75\x8C\xCF\xAC\xD4\x63\xE1\x05\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82"; const SIGNAL_1_PNG="\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52\x00\x00\x00\x80\x00\x00\x00\x80\x08\x03\x00\x00\x00\xF4\xE0\x91\xF9\x00\x00\x00\x04\x67\x41\x4D\x41\x00\x00\xB1\x8F\x0B\xFC\x61\x05\x00\x00\x00\x01\x73\x52\x47\x42\x00\xAE\xCE\x1C\xE9\x00\x00\x00\x06\x50\x4C\x54\x45\x47\x70\x4C\xFF\xFF\xFF\x9F\x94\xA2\x43\x00\x00\x00\x01\x74\x52\x4E\x53\x00\x40\xE6\xD8\x66\x00\x00\x00\x35\x49\x44\x41\x54\x78\xDA\xED\xCE\x41\x0D\x00\x00\x08\x04\xA0\xB3\x7F\x69\x0B\xF8\xD7\x4D\x48\x40\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17\xD4\x40\x40\x40\x40\x40\x40\x40\x40\x40\xE0\x57\x00\xD8\xD4\xF1\xF3\x01\x91\x02\xDF\x44\x14\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82"; const SIGNAL_2_PNG="\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52\x00\x00\x00\x80\x00\x00\x00\x80\x08\x03\x00\x00\x00\xF4\xE0\x91\xF9\x00\x00\x00\x04\x67\x41\x4D\x41\x00\x00\xB1\x8F\x0B\xFC\x61\x05\x00\x00\x00\x01\x73\x52\x47\x42\x00\xAE\xCE\x1C\xE9\x00\x00\x00\x12\x50\x4C\x54\x45\x47\x70\x4C\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x88\x19\x3C\x7F\x00\x00\x00\x05\x74\x52\x4E\x53\x00\xAA\x55\x1B\xEB\x14\x24\xE3\xE1\x00\x00\x00\x53\x49\x44\x41\x54\x78\xDA\xED\xCE\xB9\x11\x00\x20\x0C\xC0\x30\xBE\xEC\xBF\x32\x03\x40\x41\xC1\x85\x02\x69\x00\x9F\x4B\x01\x00\x00\x00\x00\x00\x00\x00\x00\xE0\x44\x1D\xAB\x96\x3A\x10\x2B\x03\x06\x0C\x18\x30\x60\xC0\x80\x01\x03\x06\x0C\x18\xB8\x39\xD0\x37\x52\x07\x36\xF9\x30\x60\xC0\x80\x01\x03\x06\x0C\x18\x30\xF0\xD7\x00\xF0\xD2\x04\xBF\x49\x19\xF8\xD1\xF6\x2A\x78\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82"; const SIGNAL_3_PNG="\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52\x00\x00\x00\x80\x00\x00\x00\x80\x08\x03\x00\x00\x00\xF4\xE0\x91\xF9\x00\x00\x00\x04\x67\x41\x4D\x41\x00\x00\xB1\x8F\x0B\xFC\x61\x05\x00\x00\x00\x01\x73\x52\x47\x42\x00\xAE\xCE\x1C\xE9\x00\x00\x00\x12\x50\x4C\x54\x45\x47\x70\x4C\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x88\x19\x3C\x7F\x00\x00\x00\x05\x74\x52\x4E\x53\x00\x58\xAA\x1B\xEB\x86\xD3\x09\x20\x00\x00\x00\x68\x49\x44\x41\x54\x78\xDA\xED\xCE\xB9\x0D\xC0\x30\x0C\x04\x41\x51\x4F\xFF\x2D\xBB\x00\x31\x24\xA8\xC0\xB3\xF9\x1D\x66\x0C\x49\x92\x24\x49\x92\x24\x49\x2A\x69\x45\x52\x27\x20\xCE\xDD\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x28\xFC\x9F\xFB\x2E\x5A\x01\xC9\x3F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x25\x60\x25\xB5\x02\x92\xF5\x01\x00\x00\x00\x00\x00\x00\x00\x00\xF8\x17\x40\xD2\xCB\x3E\x35\x42\x38\xAF\x45\x41\xF0\x6C\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82"; const SIGNAL_4_PNG="\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52\x00\x00\x00\x80\x00\x00\x00\x80\x08\x03\x00\x00\x00\xF4\xE0\x91\xF9\x00\x00\x00\x04\x67\x41\x4D\x41\x00\x00\xB1\x8F\x0B\xFC\x61\x05\x00\x00\x00\x01\x73\x52\x47\x42\x00\xAE\xCE\x1C\xE9\x00\x00\x00\x12\x50\x4C\x54\x45\x47\x70\x4C\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x88\x19\x3C\x7F\x00\x00\x00\x05\x74\x52\x4E\x53\x00\x58\xAA\xF0\x1B\x86\xE4\xD1\x1B\x00\x00\x00\x7C\x49\x44\x41\x54\x78\xDA\xED\xDA\xB1\x09\xC0\x30\x0C\x45\x41\xCB\x71\xF6\x5F\x39\x0B\x7C\x48\x63\x14\x70\xEE\xF5\x12\x87\x6A\x8D\x21\xE9\x2F\x5D\xA1\x56\xC0\x1D\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x38\x09\xB0\x2A\xD4\x09\xA8\xB0\x7E\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x6C\x04\xCC\xF0\x7F\x51\xAD\x80\x30\x0D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xB0\x13\xB0\x42\xAD\x80\xD7\xF3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1C\x0F\x90\xF4\x65\x0F\xC6\x62\x65\x11\x8F\xB9\xE6\x96\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82"; /* * GLOBALS * */ local webAPI = null; local wifiData = null; local wlanData = null; local wlanIP = null; local savedContext = null; local isConnected = false; local twilioClient = null; local targetNumber = null; /* * FUNCTIONS * */ function debugAPI(context, next) { // Display a UI API activity report - this is useful for agent-served UI debugging server.log("API received a request at " + time() + ": " + context.req.method.toupper() + " @ " + context.req.path.tolower()); if (context.req.rawbody.len() > 0) server.log("Request body: " + context.req.rawbody.tolower()); // Invoke the next middleware (if any) registered with Rocky next(); } function checkSecure(context) { // Verify that the request sent to the agent from a remote source was // made using HTTPS (ie. do not support HTTP) if (context.req.headers["x-forwarded-proto"] != "https") return false; return true; } function sorter(a, b) { if (a.channel < b.channel) return -1; if (a.channel > b.channel) return 1; if (a.ssid.toupper() < b.ssid.toupper()) return -1; if (a.ssid.toupper() > b.ssid.toupper()) return 1; return 0; } function watchdog() { // Record the device state as recorded by the agent local state = device.isconnected(); if (state != isConnected) { // The device state has changed, so send an SMS isConnected = state; if (targetNumber != null) { local message = "Net Monitor is now " + (isConnected ? "connected" : "disconnected"); twilioClient.send(targetNumber, message, function(response) { server.log("Reponse from Twilio: " + response.body + " (code: " + response.statuscode + ")"); }); } } imp.wakeup(15, watchdog); } /* * RUNTIME START * */ // Set the Twilio client twilioClient = Twilio("YOUR_ACCOUNT_SID", "YOUR_AUTH_TOKEN", "YOUR_TWILIO_PHONE_NUMBER"); targetNumber = "YOUR_MOBILE_PHONE_NUMBER"; #import "~/dropbox/programming/imp/codes/twilio.nut" /* * Set handlers for messages sent by the device to the agent */ device.on("send.net.status", function(info) { // The device has sent its WLAN status data, so record ti wlanData = info; }); device.on("set.wlan.list", function(networks) { // The device has reported the list of nearby compatible WiFi networks // We return this list to the UI using the Rocky context we saved earlier // (see below) if (savedContext != null) { networks.sort(sorter); savedContext.send(200, http.jsonencode({"list": networks})); savedContext = null; } }); /* * Set up the API that the agent will serve to drive the web UI */ webAPI = Rocky.init(); // Register the debug readout middleware webAPI.use(debugAPI); // Add a handler for GET requests made to / // This will return the web UI HTML webAPI.get("/", function(context) { context.send(200, format(HTML_STRING, http.agenturl())); }); // Add a handler for GET requests made to /current // This will return status JSON to the web UI. // NOTE The UI asks for this every 20 seconds webAPI.get("/current", function(context) { if (!checkSecure(context)) { context.send(401, "Insecure access forbidden"); return; } local sendData = {}; isConnected = device.isconnected(); sendData.state <- isConnected ? "connected" : "disconnected"; // If we have WLAN status data (we may not yet) send that too if (wlanData != null) { // Add the primary router's WAN IP to the stored info wlanData.wip <- context.getHeader("X-Forwarded-For"); // Add the WLAN data to the requested-data payload sendData.ip <- wlanData.address; sendData.wip <- wlanData.wip; sendData.bc <- wlanData.broadcast; sendData.nm <- wlanData.netmask; sendData.gw <- wlanData.gateway; } // Return the status information to the web UI server.log(http.jsonencode(sendData)); context.send(200, http.jsonencode(sendData)); }); // Add a handler for GET requests made to /list // The web UI has requested a list of WLANs that the device can detect webAPI.get("/list", function(context) { if (!checkSecure(context)) { context.send(401, "Insecure access forbidden"); return; } // Ask the device for a list of WLANs and preserve the Rocky context object // for use when the data comes back from the device. // NOTE We don't do it here, but it is good practice to set a timer that will // respond to the web UI request if the device does not return the list // (it may be disconnnected) device.send("get.wlan.list", true); savedContext = context; }); // Add a handler for GET requests to /images // Any call to the endpoint /images is returned the requested PNG data webAPI.get("/images/([^/]*)", function(context) { // Determine which image has been requested and send the appropriate // stored data back to the requesting web browser local path = context.path; local name = path[path.len() - 1]; local image = LOCK_PNG; if (name == "s1.png") image = SIGNAL_1_PNG; if (name == "s2.png") image = SIGNAL_2_PNG; if (name == "s3.png") image = SIGNAL_3_PNG; if (name == "s4.png") image = SIGNAL_4_PNG; // Make sure we let the browser know what kind of data we're sending // and that it should cache the image data context.setHeader("Content-Type", "image/png"); context.setHeader("Cache-Control", "max-age=86400"); context.send(200, image); }); /* * Start the SMS alert watchdog */ watchdog();