Skip to content

Add GSM and Ethernet conn_manager + add fallback UDP based getTime() #31

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Feb 26, 2019
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
db14c67
GSM: implement full state machine
facchinm Feb 12, 2019
68d2c3c
GSM: add fallback getTime retrieve
facchinm Feb 12, 2019
1324ac6
Merge branch 'master' into gsm_proper_connmanager
facchinm Feb 13, 2019
19c688e
Create NTP util class to handle getTime() fallback
facchinm Feb 13, 2019
e0abeb8
Restructure GSM connection manager
facchinm Feb 13, 2019
75ddfcc
Automatically handle getTime fallback in IoTCloud class
facchinm Feb 13, 2019
da7179b
Add EthernetConnectionManager
facchinm Feb 13, 2019
e01a981
[EthConnectionManager] Make "now" const
aentinger Feb 18, 2019
4014095
- added link to NPT on Arduino Tutorial and WikiPedia
ubidefeo Feb 20, 2019
a243573
- refactor GSMConnectionManager
ubidefeo Feb 21, 2019
096022a
- cleanup
ubidefeo Feb 21, 2019
98efd08
- cleanup WiFi and GSM ConnectionManager
ubidefeo Feb 21, 2019
e905238
- whitespace cleanup
ubidefeo Feb 21, 2019
8646505
- more cleanup and minor refactoring
ubidefeo Feb 21, 2019
8eefcc0
- reworked sentence
ubidefeo Feb 21, 2019
e4e11e5
- extended example with GSM compatibility options
ubidefeo Feb 21, 2019
20cbb75
Applying suggestions by A. Catozzi upon internal review
ubidefeo Feb 22, 2019
7fc775b
- switch state cleanup in ArduinoIoTCloud
ubidefeo Feb 23, 2019
bff994e
Merge branch 'master' into gsm_proper_connmanager
ubidefeo Feb 23, 2019
ae7dae9
- applied per1234's suggestions on #31
ubidefeo Feb 24, 2019
f86f04f
- one more missing from per1234's list
ubidefeo Feb 24, 2019
961a71c
- implemented some of the changes requested by @ilcato on PR #37
ubidefeo Feb 25, 2019
fb161bd
- `ArduinoIoTConnectionStatus` cleanup as per @ilcato 's suggestion
ubidefeo Feb 25, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
When the potentiometer (or sensor) value changes the data is sent to the Cloud.
When you flip the switch in the Cloud dashboard the onboard LED lights gets turned ON or OFF.


IMPORTANT:
This sketch will work with both WiFi and GSM enabled boards supported by Arduino IoT Cloud.
By default settings for WiFi are chosen. If you prefer to use a GSM board take a look at thingProperties.h arduino_secrets.h,
to make sure you uncomment what's needed and comment incompatible instructions.

*/
#include "arduino_secrets.h"
#include "thingProperties.h"
Expand Down
14 changes: 13 additions & 1 deletion examples/ArduinoIoTCloud_LED_switch/arduino_secrets.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
/*
Fill in your login credentials:

The following lines are used for WiFi enabled boards (MKR1000, MKR WiFi 1010)
*/
#define SECRET_SSID "YOUR_WIFI_NETWORK_NAME"
#define SECRET_PASS "YOUR_WIFI_PASSWORD"

/*
If you prefer using a MKR GSM 1400 comment the lines above and uncommet the following.
PIN, APN, Login and Password are supplied by your Cellular Data provider.
*/
//#define SECRET_PIN ""
//#define SECRET_APN ""
//#define SECRET_LOGIN ""
//#define SECRET_PASS ""
19 changes: 15 additions & 4 deletions examples/ArduinoIoTCloud_LED_switch/thingProperties.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
#include <ArduinoIoTCloud.h>
/*
The following include line is used for WiFi enabled boards (MKR1000, MKR WiFi 1010)
*/
#include <WiFiConnectionManager.h>
/*
If you prefer using a MKR GSM 1400 comment the line above and uncommet the following.
*/
//#include <GSMConnectionManager.h>


char ssid[] = SECRET_SSID; // your network SSID (name)
char pass[] = SECRET_PASS; // your network password (use for WPA, or use as key for WEP)
// Your THING_ID
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Your THING_ID
// Enter your THING_ID here

#define THING_ID "ARDUINO_IOT_CLOUD_THING_ID"

Expand All @@ -18,4 +22,11 @@ void initProperties() {
ArduinoCloud.addProperty(potentiometer, READ, ON_CHANGE);
}

ConnectionManager *ArduinoIoTPreferredConnection = new WiFiConnectionManager(SECRET_SSID, SECRET_PASS);
/*
The following include line is used for WiFi enabled boards (MKR1000, MKR WiFi 1010)
*/
ConnectionManager *ArduinoIoTPreferredConnection = new WiFiConnectionManager(SECRET_SSID, SECRET_PASS);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A quick side note: Everything that follows is a suggestion for improvement and must not necessarily implemented within this PR. We can (and probably should) do cleanups in further PRs after this is merged.

A misconception, for which I've fallen myself until quite recently, is that if you instantiate derived classes with virtual functions in it (C++ runtime polymorphism) you always need to use the keyword new, effectively allocating the memory on the heap at runtime. Interestingly enough, the following statement also works:

Suggested change
ConnectionManager *ArduinoIoTPreferredConnection = new WiFiConnectionManager(SECRET_SSID, SECRET_PASS);
WiFiConnectionManager connection_mgr(SECRET_SSID, SECRET_PASS);

Doing it this way the memory for the object is allocated on the stack and not on the heap (static memory allocation vs dynamic memory allocation - prefer the former whenever possible/feasible). The reason above statement works is because of a C++ "feature" called object slicing.

Now object slicing has some pitfalls to it but it can be used effectively in this scenario where we have an abstract base class (= one or more pure virtual functions in it) ConnectionManager from which WiFiConnectionManager and GSMConnectionManager are derived. ConnectionManager serves to define the interface for the derived classes and only those functions defined within ConnectionManager are called by users of the derived classes.

When passing the connection around to various classes you then have to use pass-by-reference.

void myFunc(ConnectionManager & connection_mgr) { ...

It´s also possible to store a reference of it as a class member variable.

class MyClass {
public:
  void MyClass(ConnectionManager & connection_mgr) : _connection_mgr(connection_mgr) { ... }
  void myMemberFunc();
private:
  ConnectionManager & _connection_mgr;
};

The ConnectionManager can than be accessed within member functions via the . operator instead of -> as would be the case if the ConnectionManager would be stored as a pointer (ConnectionManager * _connection_mgr;)

void MyClass:myMemberFunc() {
  _connection_mgr.check();
}

Furthermore I'd also suggest to keep variable names in lowercase so to not confuse them with class/type names.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting...
I'm gonna level up on this stuff for the next version.
Thanks, @lxrobotics

I'll proceed to merge this PR in :)

/*
If you prefer using a MKR GSM 1400 comment the line above and uncommet the following.
*/
//ConnectionManager *ArduinoIoTPreferredConnection = new GSMConnectionManager(SECRET_PIN, SECRET_APN, SECRET_LOGIN, SECRET_PASS);
2 changes: 1 addition & 1 deletion library.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name=ArduinoIoTCloud
version=0.5.0
version=0.5.1
author=Arduino
maintainer=Arduino <[email protected]>
sentence=This library allows to connect to the Arduino IoT Cloud service.
Expand Down
28 changes: 6 additions & 22 deletions src/ArduinoIoTCloud.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@ static ConnectionManager *getTimeConnection = NULL;

static unsigned long getTime() {
if (!getTimeConnection) return 0;
return getTimeConnection->getTime();
unsigned long time = getTimeConnection->getTime();
if (!NTPUtils::isTimeValid(time)) {
debugMessage("Bogus NTP time from API, fallback to UDP method", 0);
time = NTPUtils(getTimeConnection->getUDP()).getTime();
}
return time;
}

ArduinoIoTCloudClass::ArduinoIoTCloudClass() :
Expand Down Expand Up @@ -118,7 +123,6 @@ int ArduinoIoTCloudClass::begin(Client& net, String brokerAddress, uint16_t brok


// TODO: Find a better way to allow callback into object method

// Begin function for the MQTTClient
mqttClientBegin();

Expand Down Expand Up @@ -212,13 +216,6 @@ void ArduinoIoTCloudClass::update(int const reconnectionMaxRetries, int const re
if(iotStatus != IOT_STATUS_CLOUD_CONNECTED){
return;
}
// Method's argument controls
int const maxRetries = (reconnectionMaxRetries > 0) ? reconnectionMaxRetries : MAX_RETRIES;
int const timeout = (reconnectionTimeoutMs > 0) ? reconnectionTimeoutMs : RECONNECTION_TIMEOUT;

// If the reconnect() culd not establish the connection, return the control to the user sketch
if (!mqttReconnect(maxRetries, timeout))
return;

// MTTQClient connected!, poll() used to retrieve data from MQTT broker
_mqttClient->poll();
Expand Down Expand Up @@ -321,21 +318,8 @@ void ArduinoIoTCloudClass::connectionCheck()

switch (iotStatus) {
case IOT_STATUS_IDLE:
{
int connectionAttempt;
if(connection == NULL){
connectionAttempt = begin(*_net, _brokerAddress, _brokerPort);
}else{
connectionAttempt = begin(connection, _brokerAddress, _brokerPort);
}
if(!connectionAttempt){
debugMessage("Error Starting Arduino Cloud\nTrying again in a few seconds", 0);
setIoTConnectionState(IOT_STATUS_CLOUD_ERROR);
return;
}
setIoTConnectionState(IOT_STATUS_CLOUD_CONNECTING);
break;
}
case IOT_STATUS_CLOUD_ERROR:
debugMessage("Cloud Error. Retrying...", 0);
setIoTConnectionState(IOT_STATUS_CLOUD_RECONNECTING);
Expand Down
6 changes: 0 additions & 6 deletions src/CloudSerial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,35 +38,30 @@ void CloudSerialClass::end()

int CloudSerialClass::available()
{
ArduinoCloud.update();

return _rxBuffer.available();
}

int CloudSerialClass::availableForWrite()
{
ArduinoCloud.update();

return _txBuffer.availableForStore();
}

int CloudSerialClass::peek()
{
ArduinoCloud.update();

return _rxBuffer.peek();
}

int CloudSerialClass::read()
{
ArduinoCloud.update();

return _rxBuffer.read_char();
}

void CloudSerialClass::flush()
{
ArduinoCloud.update();

byte out[CLOUD_SERIAL_TX_BUFFER_SIZE];
int length = 0;
Expand All @@ -91,7 +86,6 @@ size_t CloudSerialClass::write(const uint8_t data)

CloudSerialClass::operator bool()
{
ArduinoCloud.update();

return ArduinoCloud.connected();
}
Expand Down
7 changes: 5 additions & 2 deletions src/ConnectionManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
#define ARDUINO_CLOUD_DEBUG_LEVEL 2

#include <Client.h>
#include <Udp.h>
#include "utility/NTPUtils.h"

enum NetworkConnectionState {
CONNECTION_STATE_INIT,
Expand All @@ -38,6 +40,7 @@ class ConnectionManager {
virtual void check() = 0;
virtual unsigned long getTime() = 0;
virtual Client &getClient();
virtual UDP &getUDP();

virtual NetworkConnectionState getStatus() { return netConnectionState; }

Expand Down Expand Up @@ -82,14 +85,14 @@ inline void debugMessage(char *_msg, int _debugLevel, bool _timestamp = true, bo
if (_debugLevel <= debugMessageLevel) {
char prepend[20];
sprintf(prepend, "\n[ %d ] ", millis());
if(_timestamp)
if(_timestamp){
Serial.print(prepend);
}
if(_newline){
Serial.println(_msg);
}else{
Serial.print(_msg);
}

}
}

Expand Down
187 changes: 187 additions & 0 deletions src/EthernetConnectionManager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
#include "ConnectionManager.h"

#include <Ethernet.h>
#define BOARD_HAS_ETHERNET

class EthConnectionManager : public ConnectionManager {
public:
EthConnectionManager(uint8_t *mac, int ss_pin);

virtual unsigned long getTime();
virtual void init();
virtual void check();
virtual Client &getClient() { return ethClient; };
virtual UDP &getUDP() { return udp; };

private:

void changeConnectionState(NetworkConnectionState _newState);
const int CHECK_INTERVAL_IDLE = 100;
const int CHECK_INTERVAL_INIT = 100;
const int CHECK_INTERVAL_CONNECTING = 500;
const int CHECK_INTERVAL_GETTIME = 100;
const int CHECK_INTERVAL_CONNECTED = 10000;
const int CHECK_INTERVAL_RETRYING = 5000;
const int CHECK_INTERVAL_DISCONNECTED = 1000;
const int CHECK_INTERVAL_ERROR = 500;

unsigned long lastConnectionTickTime, lastNetworkStep;
uint8_t* mac;
int ss_pin;
EthernetClient ethClient;
EthernetUDP udp;
int connectionTickTimeInterval;
};

#if !defined(BOARD_HAS_WIFI) && !defined(BOARD_HAS_GSM)
static const unsigned long NETWORK_CONNECTION_INTERVAL = 30000;
#endif

EthConnectionManager::EthConnectionManager(uint8_t *mac, int ss_pin = -1) :
mac(mac),
ss_pin(ss_pin),
lastConnectionTickTime(millis()),
connectionTickTimeInterval(CHECK_INTERVAL_IDLE) {
}

unsigned long EthConnectionManager::getTime() {
//handled by fallback manager
return lastValidTimestamp + 1;
}

void EthConnectionManager::init() {
}

void EthConnectionManager::changeConnectionState(NetworkConnectionState _newState) {
netConnectionState = _newState;
int newInterval = CHECK_INTERVAL_IDLE;
switch (_newState) {
case CONNECTION_STATE_INIT:
newInterval = CHECK_INTERVAL_INIT;
break;
case CONNECTION_STATE_CONNECTING:
newInterval = CHECK_INTERVAL_CONNECTING;
break;
case CONNECTION_STATE_GETTIME:
newInterval = CHECK_INTERVAL_GETTIME;
break;
case CONNECTION_STATE_CONNECTED:
newInterval = CHECK_INTERVAL_CONNECTED;
break;
case CONNECTION_STATE_DISCONNECTED:
newInterval = CHECK_INTERVAL_DISCONNECTED;

break;
}
connectionTickTimeInterval = newInterval;
lastConnectionTickTime = millis();
}

void EthConnectionManager::check() {
char msgBuffer[120];
unsigned long const now = millis();
int networkStatus = 0;
if (now - lastConnectionTickTime > connectionTickTimeInterval) {
switch (netConnectionState) {
case CONNECTION_STATE_INIT:
if (ss_pin == -1) {
networkStatus = Ethernet.begin(mac);
} else {
networkStatus = Ethernet.begin(mac, ss_pin);
}
networkStatus = Ethernet.hardwareStatus();
*msgBuffer = 0;
sprintf(msgBuffer, "Eth hardware status(): %d", networkStatus);
debugMessage(msgBuffer, 2);
if (networkStatus == EthernetNoHardware) {
debugMessage("No Ethernet chip connected", 0);
// don't continue:
changeConnectionState(CONNECTION_STATE_ERROR);
lastConnectionTickTime = now;
return;
}
networkStatus = Ethernet.linkStatus();
*msgBuffer = 0;
sprintf(msgBuffer, "Eth link status(): %d", networkStatus);
debugMessage(msgBuffer, 2);
if (networkStatus == LinkOFF) {
debugMessage("Failed to configure Ethernet via dhcp", 0);
// don't continue:
changeConnectionState(CONNECTION_STATE_ERROR);
lastConnectionTickTime = now;
return;
}
*msgBuffer = 0;
sprintf(msgBuffer, "Ethernet shield recognized: ID", Ethernet.hardwareStatus());
debugMessage(msgBuffer, 0);
changeConnectionState(CONNECTION_STATE_CONNECTING);
break;
case CONNECTION_STATE_CONNECTING:
*msgBuffer = 0;
sprintf(msgBuffer, "Connecting via dhcp");
debugMessage(msgBuffer, 2);
if (ss_pin == -1) {
networkStatus = Ethernet.begin(mac);
} else {
networkStatus = Ethernet.begin(mac, ss_pin);
}
*msgBuffer = 0;
sprintf(msgBuffer, "Ethernet.status(): %d", networkStatus);
debugMessage(msgBuffer, 2);
if (networkStatus == 0) {
*msgBuffer = 0;
sprintf(msgBuffer, "Connection failed");
debugMessage(msgBuffer, 0);

*msgBuffer = 0;
sprintf(msgBuffer, "Retrying in \"%d\" milliseconds", connectionTickTimeInterval);
debugMessage(msgBuffer, 2);
//changeConnectionState(CONNECTION_STATE_CONNECTING);
return;
} else {
*msgBuffer = 0;
sprintf(msgBuffer, "Connected!");
debugMessage(msgBuffer, 2);
changeConnectionState(CONNECTION_STATE_GETTIME);
return;
}
break;
case CONNECTION_STATE_GETTIME:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add an exit condition from the CONNECTION_STATE_GETTIME state in a similar way of the WiFiConnectionManager.

debugMessage("Acquiring Time from Network", 3);
unsigned long networkTime;
networkTime = getTime();
*msgBuffer = 0;
sprintf(msgBuffer, "Network Time: %u", networkTime);
debugMessage(msgBuffer, 3);
if(networkTime > lastValidTimestamp){
lastValidTimestamp = networkTime;
changeConnectionState(CONNECTION_STATE_CONNECTED);
}
break;
case CONNECTION_STATE_CONNECTED:
// keep testing connection
Ethernet.maintain();
networkStatus = Ethernet.linkStatus();
*msgBuffer = 0;
sprintf(msgBuffer, "Eth link status(): %d", networkStatus);
debugMessage(msgBuffer, 4);
if (networkStatus != LinkON) {
changeConnectionState(CONNECTION_STATE_DISCONNECTED);
return;
}
*msgBuffer = 0;
sprintf(msgBuffer, "Connected");
debugMessage(msgBuffer, 2);
break;
case CONNECTION_STATE_DISCONNECTED:
*msgBuffer = 0;
sprintf(msgBuffer, "Connection lost.");
debugMessage(msgBuffer, 0);
debugMessage("Attempting reconnection", 1);
changeConnectionState(CONNECTION_STATE_CONNECTING);
//wifiClient.stop();
break;
}
lastConnectionTickTime = now;
}
}
Loading