BLUF (bottom line up front): Still not working. I was able to get the MKR1000 board to connect a couple of times, by resetting the board with double-tap, or 30-second hold, of reset button, and cycling power. I was able to connect with both an empty example sketch, and with my Greenhouse control sketch. But… on the 3rd attempt after just cosmetic changes to the sketch, it won’t connect again (neither the basic Edgent example, or my developed sketch), after 15-20 more tries with different reset attempts.
Details below.
Thank you PeteKnight, I’ve seen you reply to so many of these questions, and I don’t feel too beat down by your reply (I’ll try to never say, “all libraries are current”) again.
I’ve been ‘tinkering’ with this for about 6-8 months, trying to automate our greenhouse (temp control and watering) for times we’re away from home. I’m a retired Naval Flight Officer, not a developer, with just enough computer science education/experience to be dangerous.
I didn’t even notice that the other solution I referenced was for blynk-cloud.com instead of blynk.cloud.
I’ve only been using the new Blynk IoT since I started tinkering.
The code I posted was just a brand new example sketch I tried last night to see if the problem existed in a ‘clean’ sketch, vs one where I’ve added code for sensors, display, timers, functions to do things for the greenhouse. It’s just the Blynk example in the Arduino IDE, all I changed was my template and device info, and set the board to Arduino MKR1000.
And I had the same problem of connection timeout.
What bothers me is that it’s not a consistent problem - the same board/sketch connected just fine many ties earlier, then just stopped working after a meaningless code change (like adding a serial print line, etc.).
I have a different sketch, on a MKR1010 board that connects just fine, all the time. I mention that only to (potentially) rule out that the problem could be related to my wifi setup at home.
So, after many more attempts, and much more reading/searching (including reading Pete’s excellent Troubleshooting post (Troubleshooting Edgent Dynamic Provisioning Problems), I thought I came across a fix, or explanation - resetting the board with 30-second reset button press, and/or double-tap. But, it’s not working again now, and I’m just at a loss.
Still trying different reset options, to figure out what worked an hour ago, but no luck… still get no connection, timeout, last error code 702.
Could it be a hardware problem with the MKR1000 board? It did work fine just a while ago, so I’m inclined to say it’s not the board, and not the sketch, but I don’t know.
Any other ideas?
This is the full Greenhouse sketch I’ve been trying, in addition to the basic Blynk example.
/* Arduino MKR1000
*/
#define BLYNK_TEMPLATE_ID "TMPLGvxtfc-X"
#define BLYNK_DEVICE_NAME "Greenhouse"
#define BLYNK_FIRMWARE_VERSION "1.0.1"
#define BLYNK_PRINT Serial
//#define BLYNK_DEBUG // comment out to not see the debug info
#define APP_DEBUG // same...
#include "BlynkEdgent.h" // for the other files (folders) in this sketch
//#include <SPI.h> // for OLED display
#include <Adafruit_GFX.h> // OLED
#include <Adafruit_SSD1306.h> // OLED
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// The pins for I2C are defined by the Wire-library. So, son't need to define them here
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C //< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // declaration for OLED display
#include "Adafruit_BME680.h"
/*#define BME_SCK 13
#define BME_MISO 12
#define BME_MOSI 11
#define BME_CS 10*/
// #define SEALEVELPRESSURE_HPA (1021) // adjust to change altitude
Adafruit_BME680 bme; // I2C
//Adafruit_BME680 bme(BME_CS); // hardware SPI
//Adafruit_BME680 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK);
#include "Adafruit_LTR390.h"
Adafruit_LTR390 ltr = Adafruit_LTR390();
#include "Adafruit_VEML7700.h"
Adafruit_VEML7700 veml = Adafruit_VEML7700();
#include <WidgetRTC.h>
WidgetRTC rtc;
//#include <RTCZero.h>; // for Real Time Clock https://www.arduino.cc/en/Reference/RTC
//RTCZero rtc; // declaration for RTC instance (of RTCZero), called 'rtc'
//#include <avr/dtostrf.h> // for the string convertion to get set # of digits to display, used in pressure string
#include <DHT.h>; // for DHT22 temp/hum sensor
#define DHTPIN1 4 // signal from DHT1
#define DHTPIN2 5 // signal from DHT2
#define DHTTYPE1 DHT22 // DHT 22, power supply 3.3 to 5.5v
#define DHTTYPE2 DHT11 // DHT 11, power supply 3.3 to 5.5v
DHT dht1(DHTPIN1, DHTTYPE1); // declaration for DHT instance, initialize DHT sensor
DHT dht2(DHTPIN2, DHTTYPE2); // declaration for DHT instance, initialize DHT sensor
#define soilSensor1 A4 // signal from soil sensor 1 goes into hardwired pin A3 - Grapefruit1
#define valve1 1 // V1, relay_1 hardwired to pin A1, Grapefruit valve
#define valve2 2 // V2, relay_2 hardwired to pin A2, Shelf valve
BlynkTimer timer; // to call functions at set intervals
int waterRunning1 = 0; // V1, for watering, or relayStatus1
int waterRunning2 = 0; // V2, for watering, or relayStatus2
int waterEnabled1 = 0; // V3, watering enabled or not for Grapefruit, initialy OFF, button in app to turn ON
int waterEnabled2 = 0; // V4, same for Shelf...
int tempDHT; // V5, send to app
int humidDHT; // V6, send to app, monitor environment
int tempDHT2; // V7
int humidDHT2; // V8
int tempDiff; // V28
int percentMoisture1; // V9, Grapefruit sensor converted to %
int waterPoint1 = 25; // V11, soil % where start Grapefruit watering - default, change with slider in app
int waterTime1 = 120; // V12, how long to open VALVE1, in seconds, change with slider in app
int dayTime = 1; // V29, variable set to 0 or 1 in flow, to determine if should water (no watering at night), current Lux vs threshold
int luxVEML = 100; // V26, light reading
int dayLightThreshold = 300; // V15, threshold to determine dayTime or not, comparing to current Lux level
String dayLightStatus; // V16, set to "yes" or "no" after dayTime is changed, so can easily see on the app
int dry1 = 0; //800 // V17, value of completely dry sensor, we'll trigger watering based on a % of moisture, adjust these in the app
int wet1 = 800; //250 // V18, value of fully wet for the sensor,
int lowTempWarn = 53; // V21, when to send notifications on temp, can adjust these in app
int lowTempAlarm = 50; // V22, same...
String nowTime; // V25, push current time to display in app (quick way to see status, or when went offline)
const int GMT = -5; // set GMT time zone adjust (-5 here on US east coast, or -4 in summer/DST)
int IAQ = 1; // V27, to display IAQ in app
BLYNK_WRITE(V1) { // Blynk button tied to V9, to show status of Relay_1, and control the relay on Pin 1, called valve1
int pinValue1 = param.asInt(); // assign incoming value from virtual pin V9 to a variable (pinValue1)
digitalWrite(valve1,pinValue1); // A1 is pin for Relay_1, for Grapefruit, so pushing "ON" button in app, sets HIGH to A1
}
BLYNK_WRITE(V2) { // Blynk button tied to V10, show status of Relay_2, and control the relay on Pin 2 called valve2
int pinValue2 = param.asInt(); // assign incoming value from virtual pin V10 to a variable (pinValue2)
digitalWrite(valve2,pinValue2); // pinValue2 is written to valve2 (which is pin A2, going to relay2
}
BLYNK_WRITE(V3) { //
waterEnabled1 = param.asInt();
}
BLYNK_WRITE(V4) { //
waterEnabled2 = param.asInt();
}
BLYNK_WRITE(V11) { // Blynk app slider sets value on V11 for watering threshold, in soil moisture %
waterPoint1 = param.asInt(); //reading the value of slider (sec) passed through V11
}
BLYNK_WRITE(V12) { // V12 Blynk app slider sets value for watering time, in seconds
waterTime1 = param.asInt(); //reading the value of slider (sec) passed through V12, change to mSec below
}
BLYNK_WRITE(V15) { // V15 Blynk app slider sets value for 'dayLightThreshold' value, to determine dayTime from Lux
dayLightThreshold = param.asInt();
}
BLYNK_WRITE(V16) { // V16 for 'dayLightStatus' value, also set in function below, yes or no it's light out
dayLightStatus = param.asInt();
}
BLYNK_WRITE(V17) { //
dry1 = param.asInt();
}
BLYNK_WRITE(V18) { //
wet1 = param.asInt();
}
BLYNK_WRITE(V21) { // V21 for 'lowTempWarn' value, set by slider in app, to trigger the notofication
lowTempWarn = param.asInt();
}
BLYNK_WRITE(V22) { // V22 for 'lowTempAlarm' value, set by slider in app, to trigger the notofication
lowTempAlarm = param.asInt();
}
BLYNK_WRITE(V25) { //
nowTime = param.asInt();
}
BLYNK_CONNECTED() { // runs if/when Blynk reconnects, runs included commands...
Blynk.sendInternal("rtc", "sync"); // request current local time from Blynk CLoud
Blynk.syncAll(); // Command restores all Widget values based on last saved values on the server.
// All analog and digital pin states will be restored.
// Every Virtual Pin will perform BLYNK_WRITE event.
// Blynk.syncVirtual(V0, V3); // Or could update single/multiple pins with this
}
void setup()
{
Serial.begin(115200);
delay(2000);
BlynkEdgent.begin(); // All the magic
rtc.begin(); // start the RTC library code, included above
//-----------------+++++++++++++++++++--------------------
Serial.println("Adafruit LTR-390 test"); // light and UV sensor
if ( ! ltr.begin() ) {
Serial.println("Couldn't find LTR sensor!");
while (1) delay(10);
}
Serial.println("Found LTR sensor!");
ltr.setMode(LTR390_MODE_UVS); // sensor is EITHER UV or LUX - NOT both
if (ltr.getMode() == LTR390_MODE_ALS) {
Serial.println("In ALS mode");
} else {
Serial.println("In UVS mode");
}
ltr.setGain(LTR390_GAIN_3);
Serial.print("Gain : ");
switch (ltr.getGain()) {
case LTR390_GAIN_1: Serial.println(1); break;
case LTR390_GAIN_3: Serial.println(3); break;
case LTR390_GAIN_6: Serial.println(6); break;
case LTR390_GAIN_9: Serial.println(9); break;
case LTR390_GAIN_18: Serial.println(18); break;
}
ltr.setResolution(LTR390_RESOLUTION_16BIT);
Serial.print("Resolution : ");
switch (ltr.getResolution()) {
case LTR390_RESOLUTION_13BIT: Serial.println(13); break;
case LTR390_RESOLUTION_16BIT: Serial.println(16); break;
case LTR390_RESOLUTION_17BIT: Serial.println(17); break;
case LTR390_RESOLUTION_18BIT: Serial.println(18); break;
case LTR390_RESOLUTION_19BIT: Serial.println(19); break;
case LTR390_RESOLUTION_20BIT: Serial.println(20); break;
}
ltr.setThresholds(100, 1000);
ltr.configInterrupt(true, LTR390_MODE_UVS);
delay(2000);
//-----------------+++++++++++++++++++--------------------
Serial.println(F("BME680 async test")); // Temp/Hum/Press/Gas sensor
if (!bme.begin()) {
Serial.println(F("Could not find a valid BME680 sensor, check wiring!"));
while (1);
} else {
Serial.println("BME680 is good to go");
}
// Set up oversampling and filter initialization
bme.setTemperatureOversampling(BME680_OS_8X);
bme.setHumidityOversampling(BME680_OS_2X);
bme.setPressureOversampling(BME680_OS_4X);
bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
bme.setGasHeater(320, 150); // 320*C for 150 ms
//-----------------+++++++++++++++++++--------------------
Serial.println("Adafruit VEML7700 Test");
if (!veml.begin()) {
Serial.println("Sensor not found");
while (1);
}
Serial.println("VEML LUX Sensor found");
veml.setGain(VEML7700_GAIN_1);
veml.setIntegrationTime(VEML7700_IT_800MS);
Serial.print(F("Gain: "));
switch (veml.getGain()) {
case VEML7700_GAIN_1: Serial.println("1"); break;
case VEML7700_GAIN_2: Serial.println("2"); break;
case VEML7700_GAIN_1_4: Serial.println("1/4"); break;
case VEML7700_GAIN_1_8: Serial.println("1/8"); break;
}
Serial.print(F("Integration Time (ms): "));
switch (veml.getIntegrationTime()) {
case VEML7700_IT_25MS: Serial.println("25"); break;
case VEML7700_IT_50MS: Serial.println("50"); break;
case VEML7700_IT_100MS: Serial.println("100"); break;
case VEML7700_IT_200MS: Serial.println("200"); break;
case VEML7700_IT_400MS: Serial.println("400"); break;
case VEML7700_IT_800MS: Serial.println("800"); break;
}
//veml.powerSaveEnable(true);
//veml.setPowerSaveMode(VEML7700_POWERSAVE_MODE4);
veml.setLowThreshold(10000);
veml.setHighThreshold(20000);
veml.interruptEnable(true);
delay(2000);
//-----------------+++++++++++++++++++--------------------
pinMode(valve1, OUTPUT); // A1 (valve1) is hardwired board pin for VALVE1, define it here as output
pinMode(valve2, OUTPUT); // A2 (valve2) is hardwired board pin for VALVE2, define it as output
pinMode(soilSensor1, INPUT); // A3 is soil sensor1, an input
dht1.begin(); // Inside data - begin reading temp/humid from DHT22
dht2.begin(); // Outside data - begin reading temp/humid from second DHT22
SPI.begin(); // for OLED
delay(100);
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { // test for it working
Serial.println(F("SSD1306 allocation failed"));
for(;;); // Don't proceed, loop forever
}
// Show initial display buffer contents on the screen -- the library initializes this with an Adafruit splash screen.
display.display(); // so we should see the Adafruit logo if it's working
delay(1000); // Pause for a second
display.clearDisplay(); // end for OLED
delay(2000);
//-----------------+++++++++++++++++++--------------------
// TIMERS DEFINED HERE, for functions below, with lentgh of time between calls, ie "1000L" 1 sec
timer.setInterval(2000L, sendToCloud); // read/send status data every 2 sec
timer.setInterval(4L*60L*1000L, waterOnOff); // run waterOnOff() function every 4 min
// Keep interval short since this will also turn off manually-activated watering...
// to prevent accidental over-watering if you forget to turn off manual watering,
// or if something prevents ability to send a manual watering off command.
// like, if the app goes offline when sending the water ON command
//-----------------+++++++++++++++++++--------------------
nowTime = String(hour()) + ":" + minute() + ":" + second();
// String currentDate = String(day()) + " " + month() + " " + year();
Serial.print("In Setup... Current time: ");
Serial.print(nowTime);
// Serial.print(" ");
// Serial.print(currentDate);
Serial.println();
Serial.println("setup complete, start Program Loop");
delay(2000);
}
void loop() {
BlynkEdgent.run();
timer.run(); // calls timer function, that calls the functions for data and watering on defined timing
}
// +++++ END loop() ++++++++++ START function definitions
/*
* These functions are defined below:
* void sendToCloud()
* void waterOnOff() -- Includes GRAPEFRUIT and SHELF
*/
void sendToCloud() { // define fuction to read and send environment data, called by timer
nowTime = String(hour()) + ":" + minute() + ":" + second();
Blynk.virtualWrite(V25, nowTime); // should push "mo/dy hr:mn" to app
//-----------------------------
// UV reading from LTR390 sensor...
if (ltr.newDataAvailable()) {
Serial.print("LTR390 sensor UV data: ");
Serial.println(ltr.readUVS());
}
//-----------------------------
// Environmental Date from BME680 sensor...
// Tell BME680 to begin measurement.
unsigned long endTime = bme.beginReading();
if (endTime == 0) {
Serial.println(F("Failed to begin reading :("));
return;
}
// Serial.print(F("BME680 reading started at ")); // put back in to check timning of readings
// Serial.print(millis());
// Serial.print(F(" and will finish at "));
// Serial.print(endTime);
// Serial.print(F( "You can do other work during BME680 measurement. "));
delay(50); // This represents parallel work.
// There's no need to delay() until millis() >= endTime: bme.endReading()
// takes care of that. It's okay for parallel work to take longer than
// BME680's measurement time.
// Obtain measurement results from BME680. Note that this operation isn't
// instantaneous even if milli() >= endTime due to I2C/SPI latency.
if (!bme.endReading()) {
Serial.println(F("Failed to complete reading :("));
return;
}
// Serial.print(F("Reading completed at "));
// Serial.println(millis());
Serial.print(F("BME680 Temperature = "));
// Serial.print(bme.temperature); // is Celcius
// Serial.println(F(" *C"));
double fBME = ((bme.temperature * 9/5) + 32);
Serial.print(fBME); // is F
Serial.println(F(" ºF"));
Serial.print(F("and Pressure = "));
// Serial.print(bme.pressure / 100.0);
// Serial.println(F(" hPa"));
double pressureHg = (bme.pressure / 1000) /3386.39; // check this math, output was "0.30 inHg"
Serial.print(pressureHg);
Serial.println(F(" inHg"));
Serial.print(F("Humidity = "));
Serial.print(bme.humidity);
Serial.println(F(" %"));
Serial.print(F("Gas = "));
Serial.print(bme.gas_resistance / 1000.0);
Serial.println(F(" KOhms (higher is better!)"));
// Serial.print(F("Approx. Altitude = "));
// Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA));
// Serial.println(F(" m"));
IAQ = (log(bme.gas_resistance / 1000.0) + 0.04 * bme.humidity); // get Indoor Air Quality (IAQ)
Serial.print("IAQ (lower is better, 0-50 is Good) is: ");
Serial.println(IAQ);
Blynk.virtualWrite(V27, IAQ); // pushes lux data to cloud app
//-----------------------------
luxVEML = veml.readLux();
Serial.print("Lux: "); Serial.print(veml.readLux());
Serial.print(" and White is: "); Serial.println(veml.readWhite());
// Serial.print("Raw ALS: "); Serial.println(veml.readALS());
Blynk.virtualWrite(V26, luxVEML); // pushes lux data to cloud app
/*
uint16_t irq = veml.interruptStatus(); // not sure why I would care about this...
if (irq & VEML7700_INTERRUPT_LOW) {
Serial.println("** Low threshold");
}
if (irq & VEML7700_INTERRUPT_HIGH) {
Serial.println("** High threshold");
}
*/
delay(500);
//-----------------------------
if (luxVEML > dayLightThreshold) { // set "dayTime" variable to 0 or 1 (dark or light)
dayLightStatus = "Yes!";
dayTime = 1;
Blynk.virtualWrite(V16, dayLightStatus); // pushes it's "day" to cloud app
} else {
dayLightStatus = "No";
dayTime = 0;
Blynk.virtualWrite(V16, dayLightStatus); // pushes it's "dark" to cloud app
}
tempDHT = dht1.readTemperature(true); // Read DHT22 sensor ("true" returns ºF)
// delay(100);
humidDHT = dht1.readHumidity();
// delay(100);
if (isnan(humidDHT) || isnan(tempDHT)) { // check that sensor read is working
Serial.println("Error reading DHT22, inside temp / humidity");
}
tempDHT2 = dht2.readTemperature(true); // Read DHT11 sensor ("true" returns ºF)
// delay(100);
humidDHT2 = dht2.readHumidity();
// delay(100);
if (isnan(humidDHT2) || isnan(tempDHT2)) { // check that sensor read is working
Serial.println("Error reading DHT11, outside temp / humidity");
}
tempDiff = tempDHT - tempDHT2;
int moisture1 = analogRead(soilSensor1); // read analog input value of soil sensor #1 and store it
percentMoisture1 = map(moisture1, wet1, dry1, 100, 0); // we'll convert to a 0-100 percentage, use wet and dry variables
Blynk.virtualWrite(V9, percentMoisture1); // pushes Grapefruit soil moisture value to cloud app
Blynk.virtualWrite(V5, tempDHT); // send all environment data to cloud
Blynk.virtualWrite(V6, humidDHT);
Blynk.virtualWrite(V7, tempDHT2); // send DHT2 environment data to cloud
Blynk.virtualWrite(V8, humidDHT2);
Blynk.virtualWrite(V23, tempDiff);
// Send info to OLED Display...
display.clearDisplay(); // Clear the buffer
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1); // Draw 2X-scale text
display.setCursor(0, 1); // 5 over and 1 down
display.print(" Grapefruit ");
display.display(); // Show that text
display.setCursor(0, 24); // cursor to 0 over and 25 down
display.setTextSize(3);
display.print(" ");
display.setTextSize(5);
display.print(percentMoisture1);
display.setTextSize(2);
display.println(" %");
display.display();
Serial.print("in sendToCloud... "); // Send a little info to Serial monitor just to check it's running...
Serial.print("Inside temp F: ");
Serial.print(tempDHT);
Serial.print(" Outside temp F: ");
Serial.println(tempDHT2);
Serial.print("Difference between INSIDE and OUTSIDE temp is : ");
Serial.println(tempDiff);
if (tempDHT <= lowTempWarn){
Blynk.logEvent("low_temp_warning", lowTempWarn) ;
}
if (tempDHT <= lowTempAlarm){
Blynk.logEvent("low_temp_alarm", lowTempAlarm) ;
}
} // end of sendToCloud function
void waterOnOff() { // define the fuction to check soil and turn water on and off, called by timer
Serial.print("Grapefruit...");
int waterThisMills1 = waterTime1*1000; // change slider set duration to millisec to use in delay for watering
Serial.print(" grapefruit moisture % is ");
Serial.println(percentMoisture1);
if (waterEnabled1 == 1) {
Serial.println("at GF, waterEnabled1 is 1... next check if already watering...");
if (waterRunning1 == 0) {
Serial.println("at GF, waterRunning1 is 0, not running... next check moisture...");
if (percentMoisture1 <= waterPoint1) { // or could use soil%-3 // check moisture %, if below set %, and is day, then start watering
Serial.println("at GF, percentMoisture1 is below waterPoint1 (dry)... next check light level...");
if (dayTime == 1) { // if daytime, then water, and start timeout to turn off after duration...
Serial.println("at GF, dayTime is 1 (it's light)... all conditions met, so WATER ON for the GF.");
digitalWrite(valve1, HIGH); // send 3.3v signal to the relay, sends 12v to valve, OPENS
waterRunning1 = 1;
Blynk.virtualWrite(V1, HIGH); // turn ON app button/state for watering ON
Blynk.virtualWrite(V23, nowTime); // update "whenWatered" time in the app
Blynk.logEvent("grapefruit_water_on", "Watering Grapefruit Now") ;
timer.setTimeout(waterThisMills1 , []() { // wait here for 'thisMillls, then run the following
digitalWrite(valve1, LOW); // turn OFF the water after set time
waterRunning1 = 0;
Blynk.virtualWrite(V1, LOW); // app button to OFF for watering
Blynk.logEvent("grapefruit_water_off", "Water OFF") ;
});
} else { // else it's dark, no water, but ensure water turned off, if manually on
digitalWrite(valve1, LOW); // turn OFF the water after set time
Blynk.virtualWrite(V1, LOW); // app button to OFF for watering
}
} else { // else from moisture check, if above threshold, don't need to water, ensure water is OFF
digitalWrite(valve1, LOW); // if it's not dry, don't send signal to relay
Blynk.virtualWrite(V1, LOW); // app button to OFF for watering
}
}
}
Serial.println();
}