Blynk is an IoT Platform that features a no-code mobile app for building a custom Android or iOS mobile app (Blynk.App) connected to IoT devices. It also features a no-code web console (Blynk.Console) with widgets that can be configured to create a custom dashboard. Blynk no-code automations can be configured to perform one one or more actions based on the value of a datastream. Data is passed between an IoT device running the Blynk firmware, the Blynk Cloud, the web dashboard, and the smartphone app via datastreams.
Blynk also features an API that can be accessed externally to push data to the Blynk Cloud, causing the new data to be routed to the web dashboard and smartphone app, and triggering any configured Blynk automations. Multiple datastreams may be updated with a single HTTP GET request. One or more new values for a single datastream may be created with a single HTTP POST request.
blues.io Notehub Routes (webhooks) only support pushing data to an external cloud service via a HTTP POST. However, you can configure a Notehub Proxy for Device Web Requests and then send a web.get Notecard web request that includes the URL parameters (contains the payload). The Notecard must be in continuous mode and connected to Notehub in order to execute the Notecard API request.
The table below shows an example of Blynk datastream settings that include each of the possible Blynk datastream data types.
Virtual Pin | Data Type |
---|---|
V0 | Double |
V1 | Integer |
V2 | String |
V7 | GPS |
A HTTP GET that would push data to these Blynk datastreams would look like the following:
https://[server address]/external/api/batch/update?token=AbcdefGHiJKlMNOpqrstUvWxyZABcdeFG&V0=3.14&V1=101&V2="HEllo"&V7=-73.987&V7=40.725
Where '{server_address}" can be found here: HTTPS API server addresses. The token is unique to each Blynk device.
Create a new Notebub Route (webhook) by going to Notehub, selecting or creating a project, click on the 'Routes' menu option, and then click on the '+ New Route' button on the upper right of the page. Select the route type of 'Proxy for Device Web Requests'. For the route 'URL' to hold the token so it doesn't need to be sent from the Notecard to Notehub each time. The 'Alias' you provide will be the referenced later when you call that Route from your Notecard.
https://[server address]/external/api/batch/update?token=AbcdefGHiJKlMNOpqrstUvWxyZABcdeFG
Configure your Notecard to be in continuous mode with the following JSON request:
{"req":"hub.set", "product":"com.your-company.your-name:your_product", "mode": "continuous"}
And then follow that with a call to the Proxy route via a JSON request:
{"req": "web.get","route": "BlynkGet", "name":"&V0=3.14&V1=102&V2=heLLo&V7=-73.988&V7=40.724"}
The 'BlynkGet' above is the 'name' parameter for a web.get web transaction (Route 'Alias' = hub.get 'name'). The Blynk datastreams are updated with the data from the URL parameters '&V0=3.14&V1=102&V2=heLLo&V7=-73.988&V7=40.724'. Those URL parameters are appended to the URL from the Proxy Route you created, by the Proxy Route. Note that the value for the virtual pin V2 of data type string is NOT bounded by any single/double quotes. The Blynk.Cloud will automatically assign the same timestamp to the received data for all datastreams updated.
A successful web.get will receive the response:
{"result":200}
If necessary, you can put the Notecard back into periodic mode with the following command:
{"req": "hub.set", "product":"com.your-company.your-name:your_product", "mode": "periodic"}
Below is a complete sketch to robustly connect a cellular or WiFi Notecard to a network and then post data of each data type to Blynk.
/*
STM32L4 Feather
Sold by blues.io (not an Adafruit product)
https://dev.blues.io/quickstart/swan-quickstart/#installing-stm32duino-in-the-arduino-ide
NOTE: v2.3.0 of STM32 does not compile.
*/
// In Arduino IDE, select 'Tools','Board','SM32 boards groups..','Blues Wireless boards'
// And then under 'Board part number' choose 'Swan R5'.
// Tools -> USB support (if available), select
// Find USB port in Windows by looking at ..."CDC (generic 'Serial' supersede U(S)ART)."
/////////////////////////////////////////////////////////////////////////
// note-arduino library (search for library "Blues Wireless Notecard" by Blues Wireless)
#include <Notecard.h>
Notecard notecard;
#define PRODUCT_UID "com.your-company.your-name:your_product"
#define NOTECARD_SESSION_TOKEN "abcdefghijklmnopQRSTuvWXYZ012345"
#define DEVICE_UID "dev:94abc012345d"
#define WIFI_SSID "YourWiFiAP"
#define WIFI_PSWD "wifiAPpassword"
/////////////////////////////////////////////////////////////////////////
// Built in LED(s) & LiPoly Battery
// Red LED: LED_BUILTIN is pin #56
// USER_BTN on pin 357 is a programmable button on the Swan
// that is pulled HIGH by default (LOW when pressed).
/////////////////////////////////////////////////////////////////////////
// blinkLEDnoDelay()
unsigned long LEDblinkPeriod = 8;
unsigned long LEDblinkLast = 0;
uint8_t LEDblinkPWM = 0;
bool LEDblinkState = false;
uint8_t LEDlastMode = 0;
void blinkLEDnoDelay(byte pin, byte mode) {
// Blink the LED on 'pin' without using delay() according to
// the 'mode' argument defined below.
// pin must support PWM.
//
// mode:
// 0 = breathing
// 1 = blink slow constantly
// 2 = blink fast constantly
// 3 = slow burst every 1 second
// 4 = fast burst every 1 second
//
// Required global variables: LEDblinkPeriod, LEDblinkLast, LEDblinkPWM, LEDblinkState, LEDlastMode
if (mode == 0) {
// breathing
LEDblinkPeriod = 8;
if (LEDlastMode != mode) {
LEDblinkPWM = 0;
LEDblinkState = true;
digitalWrite(pin, LOW);
}
if (millis() - LEDblinkLast >= LEDblinkPeriod) {
if (LEDblinkPWM > 254) LEDblinkState = false;
if (LEDblinkPWM < 1) LEDblinkState = true;
if (LEDblinkState) {
LEDblinkPWM++;
} else {
LEDblinkPWM--;
}
analogWrite(pin, LEDblinkPWM);
LEDlastMode = mode;
LEDblinkLast = millis();
}
} else if (mode == 1) {
// blink slow constantly
LEDblinkPeriod = 1000;
if (millis() - LEDblinkLast >= LEDblinkPeriod) {
digitalWrite(pin, LEDblinkState);
LEDblinkState = !LEDblinkState;
LEDlastMode = mode;
LEDblinkLast = millis();
}
} else if (mode == 2) {
// blink fast constantly
LEDblinkPeriod = 100;
if (millis() - LEDblinkLast >= LEDblinkPeriod) {
digitalWrite(pin, LEDblinkState);
LEDblinkState = !LEDblinkState;
LEDlastMode = mode;
LEDblinkLast = millis();
}
} else if (mode == 3) {
// slow burst every 1 second
// Slow 4 blinks (lazy burst) followed by 1 sec pause
if (LEDlastMode != mode) {
LEDblinkPWM = 0;
LEDblinkState = true;
LEDblinkPeriod = 100;
}
if (millis() - LEDblinkLast >= LEDblinkPeriod) {
if (LEDblinkPWM < 7) {
if (LEDblinkPWM == 0) LEDblinkState = true;
digitalWrite(pin, LEDblinkState);
LEDblinkPeriod = 100;
LEDblinkState = !LEDblinkState;
LEDblinkPWM++;
} else {
digitalWrite(pin, LOW);
LEDblinkPWM = 0;
LEDblinkPeriod = 1000;
}
LEDlastMode = mode;
LEDblinkLast = millis();
}
} else if (mode == 4) {
// fast burst every 1 second
// Fast 4 blinks (burst) followed by 1 sec pause
if (LEDlastMode != mode) {
LEDblinkPWM = 0;
LEDblinkState = true;
LEDblinkPeriod = 25;
}
if (millis() - LEDblinkLast >= LEDblinkPeriod) {
if (LEDblinkPWM < 7) {
if (LEDblinkPWM == 0) LEDblinkState = true;
digitalWrite(pin, LEDblinkState);
LEDblinkPeriod = 25;
LEDblinkState = !LEDblinkState;
LEDblinkPWM++;
} else {
digitalWrite(pin, LOW);
LEDblinkPWM = 0;
LEDblinkPeriod = 1000;
}
LEDlastMode = mode;
LEDblinkLast = millis();
}
} // mode
} // blinkLEDnoDelay()
void blinkERR(byte ledPIN){
// S-O-S
const uint8_t S = 150, O = 300;
for(uint8_t i = 3; i>0; i--){
digitalWrite(ledPIN, HIGH);
delay(S);
digitalWrite(ledPIN, LOW);
delay(S);
}
delay(200);
for(uint8_t i = 3; i>0; i--){
digitalWrite(ledPIN, HIGH);
delay(O);
digitalWrite(ledPIN, LOW);
delay(O);
}
delay(200);
for(uint8_t i = 3; i>0; i--){
digitalWrite(ledPIN, HIGH);
delay(S);
digitalWrite(ledPIN, LOW);
delay(S);
}
delay(200);
} // blinkERR()
//////////////////////////////////////////////////////////////////////////////
//
uint8_t button_state = LOW; // button_state & last_button_state are initialized for a pulldown resistor
uint8_t last_button_state = LOW;
uint32_t last_debounce_time_ms = 0; // the last time the output pin was toggled
const uint8_t DEBOUNCE_DELAY_MS = 50; // The switch debounce time. 50 to 100 ms
void userButton() {
// Detect a change in the USER_BTN pushbutton state and turn on the
// USER_BTN in response.
// This logic is for a button using a pulldown resistor.
button_state = digitalRead(USER_BTN);
// if millis() or timer wraps around, reset it
if (last_debounce_time_ms > millis()) last_debounce_time_ms = millis();
if (button_state != last_button_state) {
// Multiple changes in the button_state can occur when a pushbutton is
// pressed, or a switch is toggled. Use a debounce timer to only react
// to a change in button state after an interval of DEBOUNCE_DELAY_MS.
last_button_state = button_state;
// Check if enough time has passed to evaluate another pushbutton press.
if ((millis() - last_debounce_time_ms) > DEBOUNCE_DELAY_MS) {
last_debounce_time_ms = millis();
if (button_state == LOW) {
digitalWrite(LED_BUILTIN, HIGH);
} else {
digitalWrite(LED_BUILTIN, LOW);
} // button_state
} // millis()
} // button_state != last_button_state
} // userButton()
//////////////////////////////////////////////////////////////////////////////
float fGetRandLatitude() {
// Latitude ranges from -90 to 90 degrees
uint8_t i = random(0, 90);
float f = (float)i;
int sign = random(10);
if (sign <= 5) {
f = f * (-1.0);
}
return f;
} // fGetLgSignedRandFloat()
float fGetRandLongitude() {
// Latitude ranges from -180 to +180 degrees
uint8_t i = random(0, 180);
float f = (float)i;
int sign = random(10);
if (sign <= 5) {
f = f * (-1.0);
}
return f;
} // fGetLgSignedRandFloat()
float fGetSignedRandFloat() {
long myLong = random(1000000, 3276700);
long divisor = 100;
int expnt = random(2,6);
switch (expnt) {
case 2:
divisor = 100;
break;
case 3:
divisor = 1000;
break;
case 4:
divisor = 10000;
break;
case 5:
divisor = 100000;
default:
divisor = 100;
}
float f = (float)myLong / divisor;
int sign = random(10);
if (sign <= 5) {
f = f * (-1.0);
}
return f;
} // fGetSignedRandFloat()
//////////////////////////////////////////////////////////////////////////////
// WiFi / Cellular Notecard functions
bool NotecardIsWiFi() {
// Returns TRUE if the Notecard is a WiFi Notecard.
// Returns FALSE if the Notecard is a cellular Notecard.
bool notecard_type_identified = false;
J *rsp;
// Wait until the card.status returns a response...
while (notecard_type_identified == false) {
rsp = notecard.requestAndResponse(notecard.newRequest("card.status"));
if (rsp == NULL){
for(uint8_t i = 0; i<10; i++){
digitalWrite(LED_BUILTIN, HIGH);
delay(100);
digitalWrite(LED_BUILTIN, LOW);
delay(100);
}
} else {
bool card_type = JGetBool(rsp, "wifi");
if (card_type == true) return true;
card_type = JGetBool(rsp, "cell");
if (card_type == true) return false;
}
}
} // NotecardIsConnected()
bool NotecardIsConnected() {
// Returns TRUE if the Notecard is connected to the WiFi or cellular network.
// {"req": "card.wireless"}
char network_status[20];
J *rsp = notecard.requestAndResponse(notecard.newRequest("card.wireless"));
if (rsp == NULL){
Serial.println("ERROR: card.wireless request returned NULL");
return false;
} else {
char *tempStatus = JGetString(rsp, "status");
strlcpy(network_status, tempStatus, sizeof(network_status));
//strncpy(network_status, JGetString(rsp, "status"), sizeof(JGetString(rsp, "status")));
notecard.deleteResponse(rsp);
if (strcmp(network_status,"{network-up}") == 0) {
return true;
} else {
Serial.print("network_status: '"); Serial.print(network_status); Serial.println("'");
return false;
}
}
} // NotecardIsConnected()
bool NotecardNetworkConnect() {
// Configures the Notecard to be in continuous mode.
// Performs a hub.sync
// If the Notecard is a WiFi Notecard, connects to the access point specified by WIFI_SSID
// specified by WIFI_SSID using the password from WIFI_PSWD.
// Returns TRUE if successful.
//Serial.println("\nNotecardNetworkConnect()..");
J *req;
//Serial.println("card.restart ..");
//req = notecard.newRequest("card.restart");
//delay(5000);
if (NotecardIsWiFi() == true) {
// Switch out of continuous mode to either periodic or minimum before resetting
// the WiFi credentials. The Notecard won't break the WiFi connection while in
// continuous mode.
req = notecard.newRequest("hub.set");
if (req) {
JAddStringToObject(req, "product", PRODUCT_UID);
JAddStringToObject(req, "mode", "periodic"); // Can also use "minimum"
if (!notecard.sendRequest(req)) {
notecard.logDebug("ERROR: hub.set in NotecardNetworkConnect()\n");
Serial.println("ERROR: hub.set in NotecardNetworkConnect()!\n");
return false;
}
}
// Connect to the WiFi access point using the global variables WIFI_SSID & WIFI_PSWD
//Serial.println("card.wifi ..");
req = notecard.newRequest("card.wifi");
if (req) {
JAddStringToObject(req, "ssid", WIFI_SSID);
JAddStringToObject(req, "password", WIFI_PSWD);
if (!notecard.sendRequest(req)) {
notecard.logDebug("ERROR: card.wifi in NotecardNetworkConnect()\n");
Serial.println("ERROR: card.wifi in NotecardNetworkConnect()!\n");
return false;
}
}
} // NotecardIsWiFi()
// Make a 'hub.set request and set the 'mode' to 'continuous'..
// {"req": "hub.set", "product":"com.your-company.your-name:your_product", "mode": "continuous"}
//Serial.println("\thub.set ..");
req = notecard.newRequest("hub.set");
if (req) {
JAddStringToObject(req, "product", PRODUCT_UID);
JAddStringToObject(req, "mode", "continuous");
if (!notecard.sendRequest(req)) {
notecard.logDebug("ERROR: hub.set in NotecardNetworkConnect()\n");
Serial.println("ERROR: hub.set in NotecardNetworkConnect()!\n");
return false;
}
}
// Issue a 'hub.sync' request: {"req":"hub.sync"}
//Serial.println("\thub.sync ..");
req = notecard.newRequest("hub.sync");
if (req) {
if (!notecard.sendRequest(req)) {
notecard.logDebug("ERROR: hub.set in NotecardNetworkConnect()\n");
Serial.println("ERROR: hub.set in NotecardNetworkConnect()!\n");
return false;
}
}
char network_status[20];
if (req = notecard.newRequest("card.wireless")) {
J *rsp = notecard.requestAndResponse(req);
if (rsp == NULL){
notecard.logDebug("card.wireless request in CellularNotecardConnect() failed!\n");
Serial.println("card.wireless request in CellularNotecardConnect() failed!");
return false;
} else {
char *tempStatus = JGetString(rsp, "status");
strlcpy(network_status, tempStatus, sizeof(network_status));
notecard.deleteResponse(rsp);
if (strcmp(network_status,"{network-up}") == 0) {
return true;
} else {
Serial.print("The Notecard network connection status is '"); Serial.print(network_status); Serial.println("'");
return false;
}
}
}
} // NotecardNetworkConnect
bool NotecardModeIsContinuous() {
// Returns TRUE if a hub.get reports that the Notecard connection mode is 'continuous'.
//Serial.println("NotecardModeIsContinuous()..");
// Execute a {"req":"hub.get"} to get the current settings..
char mode[20];
J *rsp = notecard.requestAndResponse(notecard.newRequest("hub.get"));
if (rsp == NULL){
Serial.println("hub.get by NotecardModeIsContinuous() failed!");
blinkERR(LED_BUILTIN);
} else {
char *tempMode = JGetString(rsp, "mode");
strlcpy(mode, tempMode, sizeof(mode));
notecard.deleteResponse(rsp);
if (strcmp(mode,"continuous") == 0) {
return true;
} else {
Serial.print("The Notecard mode is '"); Serial.print(mode); Serial.println("'.");
return false;
}
}
} // NotecardModeIsContinuous()
//////////////////////////////////////////////////////////////////////////////
// TimerA
const uint32_t TIMER_A_INTERVAL_MS = 3600000; // 1 hour = 3600000 ms
uint32_t timer_a_lap_ms = millis(); // timer
uint8_t v1 = 100;
float v0 = 0.0;
float lat = 0.0;
float lon = 0.0;
void timerA() {
// Timer A
if (timer_a_lap_ms > millis()) timer_a_lap_ms = millis();
if (millis() - timer_a_lap_ms > TIMER_A_INTERVAL_MS) {
timer_a_lap_ms = millis(); // reset the timer
// PRODUCT_UID NOTECARD_SESSION_TOKEN DEVICE_UID
// https://dev.blues.io/tools-and-sdks/libraries/arduino-library/#sending-notecard-requests
Serial.println("timerA()");
if (NotecardModeIsContinuous() == false) {
Serial.println("ERROR: The Notecard mode is not 'continuous'. Re-connecting to the cellular/WiFi network..");
if (NotecardNetworkConnect() == false) {
Serial.println("NotecardNetworkConnect() called from NotecardModeIsContinuous() failed!");
return;
}
}
digitalWrite(LED_BUILTIN, HIGH);
// Push data to the proxy route and then to Blynk
Serial.println("\nMaking a Notecard request web.get..");
J *req = notecard.newRequest("web.get");
if (req) {
Serial.println("web.get init okay");
JAddStringToObject(req, "route", "BlynkGet");
JAddStringToObject(req, "name", "&V0=1.23456&V1=112&V2=blues.io&V7=-73.988&V7=40.724");
char c_v1[3];
char str[25];
char payload[99];
sprintf(c_v1, "%u", v1);
v0 = fGetSignedRandFloat();
dtostrf(v0, 7, 6, str);
strcpy(payload, "&V0=");
strcat(payload, str);
strcat(payload, "&V1=");
strcat(payload, c_v1);
strcat(payload, "&V2=blues.io&V7=");
lon = fGetRandLongitude();
dtostrf(lon, 7, 6, str);
strcat(payload, str);
strcat(payload, "&V7=");
lat = fGetRandLatitude();
dtostrf(lat, 7, 6, str);
strcat(payload, str);
Serial.print("payload: '"); Serial.print(payload); Serial.println("'\t");
JAddStringToObject(req, "name", payload);
v1++;
if (v1 > 199) {
v1 = 100;
}
// requestAndResponse() sends a JSON request to the Notecard, and returns
// the Notecard's JSON response.
// For this request, a favorable response will be: {"result":200}
J *rsp = notecard.requestAndResponse(req);
int result = JGetInt(rsp, "result");
if (result == 200) {
Serial.println("Response: 200 okay");
} else {
Serial.println("Response: "); Serial.print(result); Serial.println(" ERROR!");
}
notecard.deleteResponse(rsp);
}
digitalWrite(LED_BUILTIN, LOW);
timer_a_lap_ms = millis(); // reset the timer
}
} // timerA()
/////////////////////////////////////////////////////////////////////////
void setup() {
delay(5000); // too fast!
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
pinMode(USER_BTN, INPUT);
timer_a_lap_ms = 0;
Serial.begin(115200);
while (!Serial && timer_a_lap_ms < 50) {
digitalWrite(LED_BUILTIN, HIGH);
delay(100);
digitalWrite(LED_BUILTIN, LOW);
timer_a_lap_ms++;
}
Serial.print("\nSerial ready in ");
Serial.print(timer_a_lap_ms);
Serial.println(" ms");
// Initialize Notecard I2C interface (with defaults)
digitalWrite(LED_BUILTIN, HIGH);
Serial.println("Initializing Notecard I2C interface..");
timer_a_lap_ms = millis();
notecard.begin();
Serial.print("Notecard I2C interface initialized sucessfully in ");
Serial.print(millis()-timer_a_lap_ms); Serial.println(" ms");
Serial.println("Determining the Notecard type..");
char notecard_type[9] = "";
if (NotecardIsWiFi() == true) {
Serial.println("The Notecard type is 'Wifi'");
strncpy(notecard_type, "WiFi", sizeof(notecard_type));
} else {
Serial.println("The Notecard type is 'Cellular'");
strncpy(notecard_type, "Cellular", sizeof(notecard_type));
}
// Uncomment below to connect to a connection to a new WiFi access point..
/*
if (NotecardIsWiFi() == true) {
while (NotecardNetworkConnect() == false) {
Serial.println("NotecardNetworkConnect() called from Setup() failed!");
for(uint8_t i = 0; i<5; i++){
blinkERR(LED_BUILTIN);
}
} // while
}
*/
// Make sure the Notecard is connected to a cellular/WiFi network.
// If not, then wait for a connection.
// From a cold start it takes 19 s to connect to the WiFi, 36 sec for cellular.
timer_a_lap_ms = millis();
if (NotecardIsConnected() == true) {
Serial.print("The "); Serial.print(notecard_type); Serial.println(" notecard is connected to a network.");
} else {
Serial.print("Connecting the "); Serial.print(notecard_type); Serial.println(" Notecard to a network..");
// do something about it...
while (NotecardNetworkConnect() == false) {
for(uint8_t i = 0; i<10; i++){
digitalWrite(LED_BUILTIN, HIGH);
delay(100);
digitalWrite(LED_BUILTIN, LOW);
delay(100);
}
delay(2000);
} // while
}
Serial.print(notecard_type); Serial.print(" Notecard connected in "); Serial.print((millis()-timer_a_lap_ms)/1000); Serial.println(" s");
digitalWrite(LED_BUILTIN, LOW);
timer_a_lap_ms = millis();
Serial.println("\nSetup finished\n");
} // setup()
void loop() {
blinkLEDnoDelay(LED_BUILTIN, 1); // Mode 0 breathing not supported
userButton();
timerA();
} // loop()
Do you need help developing or customizing a IoT product for your needs? Send me an email requesting a free one hour phone / web share consultation.
The information presented on this website is for the author's use only. Use of this information by anyone other than the author is offered as guidelines and non-professional advice only. No liability is assumed by the author or this web site.