Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save betzrhodes/49f6ac4b1792b7888c67998aee1350e0 to your computer and use it in GitHub Desktop.
Save betzrhodes/49f6ac4b1792b7888c67998aee1350e0 to your computer and use it in GitHub Desktop.
Connection Manager extension that bubbles server.connect connection reason up to connection handlers
#require "ConnectionManager.lib.nut:3.1.1"
class MyConnectionManager extends ConnectionManager {
// Attempts to connect. If the server is already connected, or the
// connection attempt was successful, run the onConnect handler, and
// any other onConnected tasks
function connect() {
// If we're connecting/disconnecting, try again in 0.5 seconds
if (_connecting) return false;
// If we're already connected: invoke the onConnectedFlow and return
if (_connected) {
_onConnectedFlow();
return true;
}
// Otherwise, try to connect...
// Set the _connecting flag at the start
_connecting = hardware.millis();
server.connect(function(result) {
// clear connecting flag when we're done trying to connect
_connecting = false;
if (result == SERVER_CONNECTED) {
// If it worked, run the onConnectedFlow
_connected = true;
_onConnectedFlow(result);
} else {
// Otherwise, restart the connection process
_onTimeoutFlow(result);
}
}.bindenv(this), _connectTimeout);
// Catch a race condition where server.connect() won't throw the callback if its already connected
if (server.isconnected()) {
_connecting = false;
_connected = true;
_onConnectedFlow();
}
return true;
}
// Runs whenever we connect or call connect()
function _onConnectedFlow(connReason = null) {
// Set the BlinkUp State
_setBlinkUpState();
while(_logs.len() > 0) {
local log = _logs.remove(0);
local d = date(log.ts);
local ts = format("%04d-%02d-%02d %02d:%02d:%02d", d.year, (d.month+1), d.day, d.hour, d.min, d.sec);
if (!log.error) {
server.log(ts + " " + log.log);
} else {
server.error(ts + " " + log.log);
}
}
// Run the global onConnected Handler if it exists
if (_onConnect != null) {
// Invoke all the callbacks in the loop
_invokeCallbacks(_onConnect, connReason);
}
_processQueue();
}
// Runs whenever a call to connect times out
function _onTimeoutFlow(connReason = null) {
// Set the BlinkUp State
_setBlinkUpState();
_connecting = false;
_connected = false;
if (_onTimeout != null) {
_invokeCallbacks(_onTimeout, connReason);
}
if (_retryOnTimeout) {
// We have a timeout trying to connect. We need to retry;
imp.wakeup(0, connect.bindenv(this));
}
}
}
// RUNTIME
// --------------------------------------------------------------
server.log("------------------------------------------------------------------");
server.log("Device Running...");
server.log(imp.getsoftwareversion());
server.log("------------------------------------------------------------------");
function getConnReasonDescription(reason) {
switch(reason) {
case NOT_CONNECTED:
return "Not Connected, unknown reason";
case NO_WIFI:
return "Failed to join WiFi or connect via Ethernet";
case NO_IP_ADDRESS:
return "Failed to get an IP address";
case NOT_RESOLVED:
return "The IP address of an Electric Imp server or proxy could not be resolved";
case NO_SERVER:
return "Failed to connect to the Electric Imp server";
case SERVER_CONNECTED:
return "The server is connected";
case NO_PROXY:
return "The imp cannot connect via saved proxy address and port";
case NOT_AUTHORISED:
return "The imp cannot connect because its proxy access credentials have been rejected";
}
return "";
}
// Settings for this test only, not recommended for an application
cm <- MyConnectionManager({
"stayConnected" : true,
"retryOnTimeout" : true,
"connectTimeout" : 10,
"blinkupBehavior" : CM_BLINK_ALWAYS
});
// Register On Connect Handler
cm.onConnect(function(reason = null) {
server.log("In on connect handler...");
if (reason != null) {
server.log("server.connect param: " + reason);
server.log("server.connect param description: " + getConnReasonDescription(reason));
}
}.bindenv(this))
cm.onTimeout(function(reason = null) {
cm.log("In on timeout handler...");
if (reason != null) {
cm.log("server.connect param: " + reason);
cm.log("server.connect param description: " + getConnReasonDescription(reason));
}
}.bindenv(this))
cm.onDisconnect(function(expected) {
cm.log("In on disconnected handler...");
cm.log("Disconnect was expected: " + expected);
}.bindenv(this))
// Trigger On Connect Handler (this should not pass a connection reason to the onConnect handler)
cm.connect();
// Disconnect
imp.wakeup(3, function() {
cm.disconnect();
cm.log("Disconnect called.");
}.bindenv(this))
// Re-connect
imp.wakeup(3, function() {
cm.log("Calling connect.");
cm.connect();
}.bindenv(this))
// Disconnect Wifi Here for at least the ammount of time set in the connectTimeout, then reconnect
// to see cm offline logs.
// PLEASE NOTE: If the device is offline for a long time cm logging can fill up and cause an out
// of memory error which will cause the device to reboot.
#require "ConnectionManager.lib.nut:3.1.1"
class MyConnectionManager extends ConnectionManager {
// Attempts to connect. If the server is already connected, or the
// connection attempt was successful, run the onConnect handler, and
// any other onConnected tasks
function connect() {
// If we're connecting/disconnecting, try again in 0.5 seconds
if (_connecting) return false;
// If we're already connected: invoke the onConnectedFlow and return
if (_connected) {
_onConnectedFlow();
return true;
}
// Otherwise, try to connect...
// Set the _connecting flag at the start
_connecting = hardware.millis();
server.connect(function(result) {
// clear connecting flag when we're done trying to connect
_connecting = false;
if (result == SERVER_CONNECTED) {
// If it worked, run the onConnectedFlow
_connected = true;
_onConnectedFlow(result);
} else {
// Otherwise, restart the connection process
_onTimeoutFlow(result);
}
}.bindenv(this), _connectTimeout);
// Catch a race condition where server.connect() won't throw the callback if its already connected
if (server.isconnected()) {
_connecting = false;
_connected = true;
_onConnectedFlow();
}
return true;
}
// Runs whenever we connect or call connect()
function _onConnectedFlow(connReason = null) {
// Set the BlinkUp State
_setBlinkUpState();
while(_logs.len() > 0) {
local log = _logs.remove(0);
local d = date(log.ts);
local ts = format("%04d-%02d-%02d %02d:%02d:%02d", d.year, (d.month+1), d.day, d.hour, d.min, d.sec);
if (!log.error) {
server.log(ts + " " + log.log);
} else {
server.error(ts + " " + log.log);
}
}
// Run the global onConnected Handler if it exists
if (_onConnect != null) {
// Invoke all the callbacks in the loop
_invokeCallbacks(_onConnect, connReason);
}
_processQueue();
}
// Runs whenever a call to connect times out
function _onTimeoutFlow(connReason = null) {
// Set the BlinkUp State
_setBlinkUpState();
_connecting = false;
_connected = false;
if (_onTimeout != null) {
_invokeCallbacks(_onTimeout, connReason);
}
if (_retryOnTimeout) {
// We have a timeout trying to connect. We need to retry;
imp.wakeup(0, connect.bindenv(this));
}
}
}
class App {
static CONNECT_TIMEOUT = 10;
cm = null;
constructor() {
// Settings for this test only, not recommended for an application
cm = MyConnectionManager({
"stayConnected" : true,
"retryOnTimeout" : true,
"connectTimeout" : CONNECT_TIMEOUT,
"blinkupBehavior" : CM_BLINK_ALWAYS
});
cm.onConnect(onConnected.bindenv(this));
cm.onTimeout(onConnTimeout.bindenv(this));
cm.onDisconnect(onDisconnected.bindenv(this));
}
function run() {
// Trigger On Connect Handler (this should not pass a connection reason to the onConnect handler)
cm.connect();
// Disconnect
imp.wakeup(3, function() {
cm.disconnect();
cm.log("Disconnect called.");
}.bindenv(this))
// Re-connect
imp.wakeup(3, function() {
cm.log("Calling connect.");
cm.connect();
// Give imp time to connect
imp.wakeup(5, function() {
// Disconnect Wifi Here for at least the ammount of time set in the connectTimeout
local msg = "Disconnect WiFi for at least " + CONNECT_TIMEOUT + " seconds.";
(cm.isConnected()) ? server.log(msg) : cm.log(msg);
}.bindenv(this))
}.bindenv(this))
}
function onConnTimeout(reason = null) {
cm.log("In on timeout handler...");
if (reason != null) {
cm.log("server.connect param: " + reason);
cm.log("server.connect param description: " + getConnReasonDescription(reason));
} else {
cm.log("no server.connect param given");
}
}
function onDisconnected(expected) {
cm.log("In on disconnected handler...");
cm.log("Disconnect was expected: " + expected);
}
function onConnected(reason = null) {
server.log("------------------------------------------------------------------");
server.log("In on connect handler...");
if (reason != null) {
server.log("server.connect param: " + reason);
server.log("server.connect param description: " + getConnReasonDescription(reason));
} else {
server.log("no server.connect param given");
}
server.log("------------------------------------------------------------------");
}
function getConnReasonDescription(reason) {
switch(reason) {
case NOT_CONNECTED:
return "Not Connected, unknown reason";
case NO_WIFI:
return "Failed to join WiFi or connect via Ethernet";
case NO_IP_ADDRESS:
return "Failed to get an IP address";
case NOT_RESOLVED:
return "The IP address of an Electric Imp server or proxy could not be resolved";
case NO_SERVER:
return "Failed to connect to the Electric Imp server";
case SERVER_CONNECTED:
return "The server is connected";
case NO_PROXY:
return "The imp cannot connect via saved proxy address and port";
case NOT_AUTHORISED:
return "The imp cannot connect because its proxy access credentials have been rejected";
}
return "";
}
}
// RUNTIME
// --------------------------------------------------------------
server.log("------------------------------------------------------------------");
server.log("Device Running...");
server.log(imp.getsoftwareversion());
server.log("------------------------------------------------------------------");
app <- App();
app.run();
#require "ConnectionManager.lib.nut:3.1.1"
#require "MessageManager.lib.nut:2.4.0"
class MyConnectionManager extends ConnectionManager {
// Attempts to connect. If the server is already connected, or the
// connection attempt was successful, run the onConnect handler, and
// any other onConnected tasks
function connect() {
// If we're connecting/disconnecting, try again in 0.5 seconds
if (_connecting) return false;
// If we're already connected: invoke the onConnectedFlow and return
if (_connected) {
_onConnectedFlow();
return true;
}
// Otherwise, try to connect...
// Set the _connecting flag at the start
_connecting = hardware.millis();
server.connect(function(result) {
// clear connecting flag when we're done trying to connect
_connecting = false;
if (result == SERVER_CONNECTED) {
// If it worked, run the onConnectedFlow
_connected = true;
_onConnectedFlow(result);
} else {
// Otherwise, restart the connection process
_onTimeoutFlow(result);
}
}.bindenv(this), _connectTimeout);
// Catch a race condition where server.connect() won't throw the callback if its already connected
if (server.isconnected()) {
_connecting = false;
_connected = true;
_onConnectedFlow();
}
return true;
}
// Runs whenever we connect or call connect()
function _onConnectedFlow(connReason = null) {
// Set the BlinkUp State
_setBlinkUpState();
while(_logs.len() > 0) {
local log = _logs.remove(0);
local d = date(log.ts);
local ts = format("%04d-%02d-%02d %02d:%02d:%02d", d.year, (d.month+1), d.day, d.hour, d.min, d.sec);
if (!log.error) {
server.log(ts + " " + log.log);
} else {
server.error(ts + " " + log.log);
}
}
// Run the global onConnected Handler if it exists
if (_onConnect != null) {
// Invoke all the callbacks in the loop
_invokeCallbacks(_onConnect, connReason);
}
_processQueue();
}
// Runs whenever a call to connect times out
function _onTimeoutFlow(connReason = null) {
// Set the BlinkUp State
_setBlinkUpState();
_connecting = false;
_connected = false;
if (_onTimeout != null) {
_invokeCallbacks(_onTimeout, connReason);
}
if (_retryOnTimeout) {
// We have a timeout trying to connect. We need to retry;
imp.wakeup(0, connect.bindenv(this));
}
}
function _invokeCallbacks(callbacks, arg = null) {
local cmCtx = this;
foreach (id, clbk in callbacks) {
local ctx = {
"arg" : arg,
"ctxClbk" : clbk
};
if (clbk && typeof clbk == "function") {
// TODO: we assume that null is not a valid value of the argument, it's null only if it's unset.
if (arg == null || (id == MM_CM_HANDLERS_ID && callbacks != cmCtx._onDisconnect) ) {
imp.wakeup(0, function() {ctxClbk();}.bindenv(ctx));
} else {
imp.wakeup(0, function() {ctxClbk(arg);}.bindenv(ctx));
}
}
}
}
}
// RUNTIME
// --------------------------------------------------------------
server.log("------------------------------------------------------------------");
server.log("Device Running...");
server.log(imp.getsoftwareversion());
server.log("------------------------------------------------------------------");
function getConnReasonDescription(reason) {
switch(reason) {
case NOT_CONNECTED:
return "Not Connected, unknown reason";
case NO_WIFI:
return "Failed to join WiFi or connect via Ethernet";
case NO_IP_ADDRESS:
return "Failed to get an IP address";
case NOT_RESOLVED:
return "The IP address of an Electric Imp server or proxy could not be resolved";
case NO_SERVER:
return "Failed to connect to the Electric Imp server";
case SERVER_CONNECTED:
return "The server is connected";
case NO_PROXY:
return "The imp cannot connect via saved proxy address and port";
case NOT_AUTHORISED:
return "The imp cannot connect because its proxy access credentials have been rejected";
}
return "";
}
// Settings for this test only, not recommended for an application
cm <- MyConnectionManager({
"stayConnected" : true,
"retryOnTimeout" : true,
"connectTimeout" : 10,
"blinkupBehavior" : CM_BLINK_ALWAYS
});
mm <- MessageManager({
"connectionManager" : cm
});
// Register On Connect Handler
cm.onConnect(function(reason = null) {
server.log("In on connect handler...");
if (reason != null) {
server.log("server.connect param: " + reason);
server.log("server.connect param description: " + getConnReasonDescription(reason));
}
}.bindenv(this))
cm.onTimeout(function(reason = null) {
cm.log("In on timeout handler...");
if (reason != null) {
cm.log("server.connect param: " + reason);
cm.log("server.connect param description: " + getConnReasonDescription(reason));
}
}.bindenv(this))
cm.onDisconnect(function(expected) {
cm.log("In on disconnected handler...");
cm.log("Disconnect was expected: " + expected);
}.bindenv(this))
// Trigger On Connect Handler (this should not pass a connection reason to the onConnect handler)
cm.connect();
// Disconnect
imp.wakeup(3, function() {
cm.disconnect();
cm.log("Disconnect called.");
}.bindenv(this))
// Re-connect
imp.wakeup(3, function() {
cm.log("Calling connect.");
cm.connect();
}.bindenv(this))
// Disconnect Wifi Here for at least the ammount of time set in the connectTimeout, then reconnect
// to see cm offline logs.
// PLEASE NOTE: If the device is offline for a long time cm logging can fill up and cause an out
// of memory error which will cause the device to reboot.
@betzrhodes
Copy link
Author

This looks like a scope issue, however since the code examples above work for me with no errors I expect this may be an error cause by a different part of the code, likely the code that is making the calls to the library. Can you send me the code you are running that produces the error.

@GuillermoActus
Copy link

Sure,
I'll add it to the support ticket.

@betzrhodes
Copy link
Author

This re-write does have an issue when using ConnectionManager with MessageManager library. Adding a parameter to onConnect will cause an error when other libraries/classes onConnect or onTimeout handlers trigger with a parameter. See v3 file for a possible solution. It is worth noting that any library or class that have dependencies will have an issue if they register onConnect or onTimeout handlers. The above solution only deals with MessageManager.

@GuillermoActus
Copy link

Great!, thank you for taking the time for a such nice explanation and feedback, I really appreciate it, this resolves our issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment