/*
* Leonardo ETH IOT Base station
*
* Copyleft Lumir Vanek
*
* Updated: 5.6.2017
*/
#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>
#include <Ethernet2.h> // The Leonardo ETH uses a w5500 ethernet controller. The standard ethernet library will fail !
#include <EthernetClient.h>
#include <avr/pgmspace.h>
#define _USE_RTC
#ifdef _USE_RTC
#include "RTClib.h"
RTC_DS1307 rtc;
#endif
// I2C bus adresses
#define I2C_LCD1 0x27 // LCD 20x4 driven by HD44780, connected to I2C module PCF8574T (7bit I2C address)
#define I2C_PCF8574_PORT1 0x24 // I2C expander PCF8574P (7bit I2C address)
// My Leonardo ETH have a MAC address printed on a sticker on the PCB
byte mac[] = {
0x90, 0xA2, 0xDA, 0x10, 0xFF, 0x04
};
// IOT service site information
const char http_ip[] = "10.5.73.x";
const int http_port = 8080;
/*
* Display messages and JAX-RS service names are stored to flash (program) memory to save SRAM.
*/
const char msg_welcome[] PROGMEM = "Leonardo Home Brain";
const char msg_fw_version[] PROGMEM = "SW 7.6.2017";
const char msg_test_displ1[] PROGMEM = "123456789ABCEFGH";
const char msg_test_displ2[] PROGMEM = "!@#$%^&*()_-<>{}[]";
const char txt_dm_hms[] PROGMEM = "%02d.%02d. %02d:%02d:%02d";
// JAX-RS Services paths, as defined using @Path anotation in JAVA code
const char get_last_value_txt1[] PROGMEM = "get-last-value-txt1"; // JAX-RS Service that returns Sensor shorted name + value
const char get_last_value_txt2[] PROGMEM = "get-last-value-txt2"; // JAX-RS Service that returns Sensor value + unit
const char get_sensor_name[] PROGMEM = "get-sensor-name"; // JAX-RS Service that returns Sensor name
const char get_last_state_txt[] PROGMEM = "get-last-state-txt"; // JAX-RS Service that returns Relay state
PROGMEM const char* const string_table[] =
{
msg_welcome, msg_fw_version, msg_test_displ1, msg_test_displ2, txt_dm_hms, get_last_value_txt1, get_last_value_txt2, get_sensor_name, get_last_state_txt
};
// Index constants for obtaining Strings from PROGMEM defined above
#define msg_ix_welcome 1
#define msg_ix_fw_version 2
#define msg_ix_test_displ1 3
#define msg_ix_test_displ2 4
#define txt_ix_dm_hms 5
#define svc_ix_get_last_value_txt1 6
#define svc_ix_get_last_value_txt2 7
#define svc_ix_get_sensor_name 8
#define svc_ix_get_last_state_txt 9
/*
* LCD connected via PCF8574T
*/
#define En_pin 2
#define Rw_pin 1
#define Rs_pin 0
#define D4_pin 4
#define D5_pin 5
#define D6_pin 6
#define D7_pin 7
#define BACKLIGHT_PIN 3
// DEM 20486 FGH-PW LCD MODULE
LiquidCrystal_I2C lcd1(I2C_LCD1, En_pin, Rw_pin, Rs_pin, D4_pin, D5_pin, D6_pin, D7_pin, BACKLIGHT_PIN, POSITIVE);
/*
* HD44780 LCD User-Defined Graphics
* http://www.frank4dd.com/howto/rabbit/hd44780lcd-to-rabbit-rcm4010.htm
*/
byte custom_smiley[8] = {
0x00, //.....
0x0A, //.#.#.
0x00, //.....
0x04, //..#..
0x00, //.....
0x11, //#...#
0x0E, //.###.
0x00 //.....
};
byte custom_frowny[8] = {
0x00, //.....
0x0A, //.#.#.
0x00, //.....
0x04, //..#..
0x00, //.....
0x0E, //.###.
0x11, //#...#
0x00 //.....
};
byte custom_temperature[8] = //icon for termometer
{
B00100,
B01010,
B01010,
B01110,
B01110,
B11111,
B11111,
B01110
};
byte custom_humidity[8] = //icon for water droplet
{
B00100,
B00100,
B01010,
B01010,
B10001,
B10001,
B10001,
B01110,
};
byte custom_whitebox[8] = //icon for void box
{
B11111,
B10001,
B10001,
B10001,
B10001,
B10001,
B10001,
B11111,
};
byte custom_menuicon[8] = //icon for menu symbol
{
B11111,
B00000,
B11111,
B00000,
B11111,
B00000,
B11111,
B00000,
};
byte custom_degree[8] = //icon for degree symbol
{
B00110,
B01001,
B01001,
B00110,
B00000,
B00000,
B00000,
B00000,
};
#define char_smiley 0
#define char_frowny 1
#define char_temperature 2
#define char_humidity 3
#define char_whitebox 4
#define char_menuicon 5
#define char_degree 6
EthernetClient client;
//************* Gargenhouse **************************
const char sensorCode_DallasTemp_01[] = "LV_TEMP_01";
//************* Outdoor ******************************
const char sensorCode_DallasTemp_02[] = "LV_TEMP_02";
//************* Greenhouse ***************************
const char sensorCode_DallasTemp_03[] = "LV_TEMP_03";
const char sensorCode_BMP180Temp_07[] = "LV_TEMP_07";
const char sensorCode_BMP180Pres_02[] = "LV_PRES_02";
const char sensorCode_BMP180Atm_02[] = "LV_ATM_02";
const char sensorCode_BH1750Light_01[] = "LV_LIGH_01";
const char sensorCode_Si7021Hum_02[] = "LV_HUM_02";
const char sensorCode_Si7021Temp_10[] = "LV_TEMP_10";
//************* Basen ********************************
const char sensorCode_DallasTemp_04[] = "LV_TEMP_04"; // Air
const char sensorCode_DallasTemp_08[] = "LV_TEMP_08"; // Water
const char sensorCode_DallasTemp_11[] = "LV_TEMP_11"; // Solar panel
//************ AQ Station ***************************
const char sensorCode_Si7021Hum_03[] = "LV_HUM_03";
//************* Testboard ****************************
const char sensorCode_DallasTemp_06[] = "LV_TEMP_06";
const char sensorCode_BMP180Temp_05[] = "LV_TEMP_05";
const char sensorCode_BMP180Pres_01[] = "LV_PRES_01";
const char sensorCode_BMP180Atm_01[] = "LV_ATM_01";
const char sensorCode_Si7021Hum_01[] = "LV_HUM_01";
const char sensorCode_Si7021Temp_09[] = "LV_TEMP_09";
//************* Relays **************************
const char relayCode_01[] = "LV_RELAY_01";
const char relayCode_02[] = "LV_RELAY_02";
// Current LCD display page
int currentPage = 0;
// Buttons connected to PORT C
#define A1 4
#define A2 5
#define A3 6
#define A4 7
#define A5 8
#define NXT 9
/*
* Content of I2C expander PCF8574P on expand shield
*
* bit 0 - inside small blue LED
* bit 1 - outside FLUX LED
* bit 2 - outside FLUX LED
* bit 3 - outside FLUX LED
* bit 4 - outside FLUX LED
* bit 5 - outside FLUX LED
* bit 6 - Relay LV_RELAY_01 Workroom
* bit 7 - Relay LV_RELAY_02 Outdoor
*/
byte port1data = 0x00;
#ifdef _USE_CCS811
#define I2C_CCS811 0x5B //Default I2C Address
CCS811 ccs811(I2C_CCS811);
const char sensorCode_CCS811VOC[] = "LV_VOC_01";
const char sensorCode_CCS811CO2[] = "LV_CO2_01";
#endif
void setup()
{
// Input pins for front panel buttons
pinMode(A1, INPUT_PULLUP);
pinMode(A2, INPUT_PULLUP);
pinMode(A3, INPUT_PULLUP);
pinMode(A4, INPUT_PULLUP);
pinMode(A5, INPUT_PULLUP);
pinMode(NXT, INPUT_PULLUP);
// Current LCD display page init
currentPage = 0;
// Open serial communications and wait for port to open:
Serial.begin(115200);
/*
* Initialize PCF8574 - the I²C 8-bit expander
*/
setPort1(B00000001, false); // Blue LED ON
/*
* Initialize the LCD
*/
lcd1.begin(20, 4); // 20 chars, 4 lines
lcd1.createChar(char_smiley, custom_smiley);
lcd1.createChar(char_frowny, custom_frowny);
lcd1.createChar(char_temperature, custom_temperature);
lcd1.createChar(char_humidity, custom_humidity);
lcd1.createChar(char_menuicon, custom_menuicon);
lcd1.createChar(char_degree, custom_degree);
lcd1.createChar(char_whitebox, custom_whitebox);
/*
* Setup RTC
*/
#ifdef _USE_RTC
rtc.begin();
if (! rtc.isrunning())
{
Serial.println("RTC is NOT running!");
// following line sets the RTC to the date & time this sketch was compiled
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
/*
* Uncomment to set RTC to compile datetime. Then comment it back and load sketch again
* to prevent set wrong datetime after reset !
*/
//rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
#endif
/*
* Hello message
*/
sayHello();
/// test1_Port1(1);
/*
* Start the Ethernet connection:
*/
if (Ethernet.begin(mac) == 0)
{
Serial.println("Failed to configure Ethernet using DHCP");
// no point in carrying on, so do nothing forevermore:
for(;;)
;
}
// give the Ethernet shield a second to initialize:
delay(1000);
// print your local IP address:
printIPAddress();
Serial.println("\nStarting connection to server...");
if(client.connect(http_ip, http_port))
{
Serial.println("Connected to server\n");
}
else
{
Serial.println("connection failed");
return false;
}
Serial.println();
}
void loop()
{
checkConnection();
String line1 = String((char) char_frowny);
String line2 = String((char) char_frowny);
String line3 = String((char) char_frowny);
String line4 = String((char) char_frowny);
String relay;
boolean success = true;
/*
* Relay 1 - bit 6
*/
success &= getValue(getStringNonPadded(svc_ix_get_last_state_txt), relayCode_01, relay);
Serial.println("\nRelay 1: ");
Serial.println(relay);
if ((relay.charAt(0) == '1') && ((port1data & B01000000) == 0x00))
{
port1data |= B01000000; // Switch Relay 1 ON
Serial.println("\nSwitching Relay 1 ON\n");
}
else if ((relay.charAt(0) == '0') && ((port1data & B01000000) != 0x00))
{
port1data &= B10111111; // Switch Relay 1 OFF
Serial.println("\nSwitching Relay 1 OFF\n");
}
/*
* Relay 2 - bit 7
*/
success &= getValue(getStringNonPadded(svc_ix_get_last_state_txt), relayCode_02, relay);
Serial.println("\nRelay 2: ");
Serial.println(relay);
if ((relay.charAt(0) == '1') && ((port1data & B10000000) == 0x00))
{
port1data |= B10000000; // Switch Relay 2 ON
Serial.println("\nSwitching Relay 2 ON\n");
}
else if ((relay.charAt(0) == '0') && ((port1data & B10000000) != 0x00))
{
port1data &= B01111111; // Switch Relay 2 OFF
Serial.println("\nSwitching Relay 2 OFF\n");
}
port1data &= B11000000; // Switch Flux LED 1 OFF
if (digitalRead(A1) == LOW) { port1data |= B00000010; }
if (digitalRead(A2) == LOW) { port1data |= B00000100; }
if (digitalRead(A3) == LOW) { port1data |= B00001000; }
if (digitalRead(A4) == LOW) { port1data |= B00010000; }
if (digitalRead(A5) == LOW) { port1data |= B00100000; }
setPort1(port1data, false);
/*
* Read current page values and display it on LCD display
*/
switch (currentPage)
{
case 0: // ======================= BMP180 Temp. ===============================================================
currentPage = 1;
line1 = String("Teploty BMP180:");
success &= getValue(getStringNonPadded(svc_ix_get_last_value_txt1), sensorCode_DallasTemp_02, line2); // Outdoor
success &= getValue(getStringNonPadded(svc_ix_get_last_value_txt1), sensorCode_DallasTemp_04, line3); // Basen air
success &= getValue(getStringNonPadded(svc_ix_get_last_value_txt1), sensorCode_BMP180Temp_07, line4); // Greenhouse
break;
case 1: // ======================= Greenhouse =================================================================
currentPage = 2;
if (digitalRead(A1) == LOW)
{
Serial.print(F("\nA1 pressed\n"));
line1 = String(F("Teploty Sklenik: A1"));
success &= getValue(getStringNonPadded(svc_ix_get_last_value_txt1), sensorCode_BMP180Temp_07, line2); // Greenhouse BMP180
success &= getValue(getStringNonPadded(svc_ix_get_last_value_txt1), sensorCode_Si7021Temp_10, line3); // Greenhouse Si7021
success &= getValue(getStringNonPadded(svc_ix_get_last_value_txt1), sensorCode_DallasTemp_03, line4); // Greenhouse Dallas
}
else
{
Serial.print(F("\nA1 NOT pressed\n"));
return;
}
break;
case 2: // ======================= Basen ======================================================================
currentPage = 3;
if (digitalRead(A2) == LOW)
{
Serial.print(F("\nA2 pressed\n"));
line1 = String(F("Teploty Bazen: A2"));
success &= getValue(getStringNonPadded(svc_ix_get_last_value_txt1), sensorCode_DallasTemp_04, line2); // Basen air
success &= getValue(getStringNonPadded(svc_ix_get_last_value_txt1), sensorCode_DallasTemp_08, line3); // Basen water
success &= getValue(getStringNonPadded(svc_ix_get_last_value_txt1), sensorCode_DallasTemp_11, line4); // Solar Panel
}
else
{
Serial.print(F("\nA2 NOT pressed\n"));
return;
}
break;
case 3: // ======================= Humidity ===================================================================
currentPage = 4;
if (digitalRead(A3) == LOW)
{
Serial.print(F("A3 pressed\n"));
success &= getValue(getStringNonPadded(svc_ix_get_sensor_name), sensorCode_Si7021Hum_02, line1); // Greenhouse
success &= getValue(getStringNonPadded(svc_ix_get_last_value_txt2), sensorCode_Si7021Hum_02, line2);
success &= getValue(getStringNonPadded(svc_ix_get_sensor_name), sensorCode_Si7021Hum_03, line3); // AQ Station
success &= getValue(getStringNonPadded(svc_ix_get_last_value_txt2), sensorCode_Si7021Hum_03, line4);
}
else
{
Serial.print(F("\nA3 NOT pressed\n"));
return;
}
break;
case 4: // ======================= Pressure ===================================================================
currentPage = 5;
if (digitalRead(A4) == LOW)
{
Serial.print(F("\nA4 pressed\n"));
success &= getValue(getStringNonPadded(svc_ix_get_sensor_name), sensorCode_BMP180Pres_01, line1);
success &= getValue(getStringNonPadded(svc_ix_get_last_value_txt2), sensorCode_BMP180Pres_01, line2);
success &= getValue(getStringNonPadded(svc_ix_get_sensor_name), sensorCode_BMP180Pres_02, line3);
success &= getValue(getStringNonPadded(svc_ix_get_last_value_txt2), sensorCode_BMP180Pres_02, line4);
}
else
{
Serial.print(("\nA4 NOT pressed\n"));
return;
}
break;
case 5: // ======================= Testboard ==================================================================
currentPage = 0;
if (digitalRead(A5) == LOW)
{
Serial.print(F("\nA5 pressed\n"));
//success &= getValue(getStringNonPadded(svc_ix_get_last_value_txt1), sensorCode_Si7021Temp_09, line1); // Testboard
success &= getValue(getStringNonPadded(svc_ix_get_last_value_txt1), sensorCode_BMP180Temp_05, line2); // Testboard
success &= getValue(getStringNonPadded(svc_ix_get_last_value_txt1), sensorCode_DallasTemp_06, line3); // Testboard
//success &= getValue(getStringNonPadded(svc_ix_get_last_value_txt1), sensorCode_Si7021Hum_01, line4); // Testboard
}
else
{
Serial.print(F("\nA5 NOT pressed\n"));
return;
}
break;
}
if (!success)
{
clearLCD(lcd1);
lcd1.print("Error !");
}
clearLCD(lcd1);
lcd1.print(getStringPadded(msg_ix_welcome));
#ifdef _USE_RTC
printRTC(1);
#endif
printPort1(3);
delay(5000); // wait for a 5 seconds
clearLCD(lcd1);
/*if(currentPage == 3)
{
lcd1.setCursor(18, 0);
lcd1.print("A2");
}
else*/
if(currentPage == 4)
{
lcd1.setCursor(18, 0);
lcd1.print("A3");
}
else if(currentPage == 5)
{
lcd1.setCursor(18, 0);
lcd1.print("A4");
}
lcd1.setCursor(0, 0);
lcd1.print(line1);
lcd1.setCursor(0, 1);
lcd1.print(line2);
lcd1.setCursor(0, 2);
lcd1.print(line3);
lcd1.setCursor(0, 3);
lcd1.print(line4);
/**
* Wait 8 sec
*/
for (int i = 0; i < 32; i++)
{
delay(250); // wait for a 8 seconds
if (digitalRead(NXT) == LOW)
{
Serial.print(F("NXT pressed\n"));
lcd1.setCursor(16, 0);
lcd1.print("Next");
break;
}
}
}
/*
* Clear given LCD:
*
*/
void clearLCD(LiquidCrystal_I2C & lcd)
{
lcd.clear();
lcd.home();
}
/*
* Hello message and 3 beeps
*/
void sayHello()
{
clearLCD(lcd1);
lcd1.print(getStringPadded(msg_ix_welcome));
lcd1.setCursor(0, 1);
lcd1.print(getStringPadded(msg_ix_fw_version));
#ifdef _USE_RTC
printRTC(2);
#endif
for(int i = 0; i < 5; i++)
{
lcd1.backlight();
delay(250);
lcd1.noBacklight();
setPort1(B00000010, false);
delay(200);
setPort1(B00000100, false);
delay(200);
setPort1(B00001000, false);
delay(200);
setPort1(B00010000, false);
delay(200);
setPort1(B00100000, false);
delay(200);
setPort1(B00000000, false);
if (digitalRead(NXT) == LOW)
{
break;
}
}
lcd1.backlight();
}
#ifdef _USE_RTC
void printRTC(int row)
{
char buf[25];
DateTime now = rtc.now();
sprintf(buf, "%02d.%02d. %02d:%02d:%02d", now.day(), now.month(), now.hour(), now.minute(), now.second());
String dateTime = String(buf);
lcd1.setCursor(0, row);
lcd1.print(dateTime);
}
#endif
void printIPAddress()
{
Serial.print("My IP address: ");
lcd1.setCursor(0, 2);
lcd1.print("IP: ");
for (byte thisByte = 0; thisByte < 4; thisByte++)
{
// print the value of each byte of the IP address:
Serial.print(Ethernet.localIP()[thisByte], DEC);
String s(Ethernet.localIP()[thisByte]);
lcd1.print(s);
if (thisByte < 3)
{
Serial.print(".");
lcd1.print(".");
}
}
Serial.println();
}
void checkConnection()
{
// if there are incoming bytes available
// from the server, read them and print them:
if (client.available()) {
char c = client.read();
Serial.print(c);
}
if (!client.connected())
{
client.stop();
delay(1000);
Serial.println("\nReconnecting to server...");
if(client.connect(http_ip, http_port))
{
Serial.println("Connected to server\n");
}
else
{
Serial.println("connection failed");
return false;
}
}
delay(1000);
}
/*
* Get message stored to flash (program) memory, padded to LCD lime size
*/
const char* getStringPadded(int index)
{
static char buffer[21]; // make sure this is large enough for the largest string it must hold
strcpy_P(buffer, (char*) pgm_read_word(&(string_table[index-1]))); // Necessary casts and dereferencing, just copy.
while (strlen(buffer) < 20)
{
int len = strlen(buffer);
buffer[len] = ' ';
buffer[len+1] = 0x00;
}
return buffer;
}
/*
* Get message stored to flash (program) memory
*/
const char* getStringNonPadded(int index)
{
static char buffer[25]; // make sure this is large enough for the largest string it must hold
strcpy_P(buffer, (char*) pgm_read_word(&(string_table[index-1]))); // Necessary casts and dereferencing, just copy.
return buffer;
}
/*
* Append spaces to rest of line to fit LCD 20 chars
*/
String padRight(String &line)
{
while(line.length() < 20) { line = line + " "; }
return line;
}
/*
* Return byte as String representation
*/
void getByteAsString(String &_string, byte _byte)
{
String binary = String(_byte, BIN);
while(binary.length() < 8) { binary = "0" + binary; }
binary.replace('0', (char) char_whitebox);
binary.replace('1', (char) 0xff);
String hexa = String(_byte, HEX);
while(hexa.length() < 2) { hexa = "0" + hexa; }
_string = _string + binary + " 0x" + hexa;
}
/**
* Get last value of given sensor from IOT service
*/
boolean getValue(const char* service, const char * sensorCode, String &dest)
{
int nTry = 5;
bool success;
do
{
success = tryGetValue(service, sensorCode, dest);
if (!success) { checkConnection(); }
} while (success == false && nTry-- > 0);
return success;
}
/**
* Get last value of given sensor from IOT service
*/
bool tryGetValue(const char* service, const char * sensorCode, String &dest)
{
setPort1(port1data | B00000001, false); // Blue LED ON
bool success = false;
String url = String(F("/IotService/rest/"));
url += service;
url += "?code=";
url += sensorCode;
Serial.println("GET data to URL: ");
Serial.println(url);
// Make an HTTP GET request
/*Serial.println("GET ") << url << F(" HTTP/1.1\r\n");
Serial.println("Host: ") << http_ip << F("\r\n");
Serial.println("Connection: close\r\n");
Serial.println("User-Agent: FishinoWiFi/1.1\r\n");
Serial.println();*/
client.print(F("GET "));
client.print(url);
client.println(F(" HTTP/1.1"));
client.print("Host: ");
client.print(http_ip);
client.print(F("\r\n"));
//client.print("Connection: close\r\n");
client.println("User-Agent: LeonardoETH/1.1");
client.println();
//String reply = String("");
/*delay(1000);
if (!client.available())
{
delay(2000);
}*/
while(client.available())
{
String line = client.readStringUntil('\r');
if ((line.charAt(1) == '#') && (line.length() <= 25))
{
//Serial.print(line);
dest = line.substring(3, line.length());
success = true;
}
//Serial.println(line);
}
setPort1(port1data & B11111110, false); // Blue LED OFF
return success;
}
/**
* Send value to IOT service
*/
void sendValue(const char * sensorCode, float value)
{
String url = "/IotService/rest/insert-value/" + String(sensorCode) + "/" + String(value);
Serial.print("GET data to URL: ");
Serial.println(url);
// Make an HTTP GET request
client.print( "GET " + url + " HTTP/1.1\r\n");
client.print("Host: " + String(http_ip) + "\r\n");
//client.print("Connection: close\r\n");
client.println();
//delay(100);
while(client.available())
{
String line = client.readStringUntil('\r');
//Serial.print(line);
}
Serial.println();
//Serial.println("Connection closed");
}
/*
* Set global variable 'port1data' to external I2C PCF8574 Port 2
*/
void setPort1(boolean printIt)
{
Wire.beginTransmission(I2C_PCF8574_PORT1);
Wire.write(port1data);
Wire.endTransmission();
if(printIt) printPort1(2);
}
/*
* Set global variable 'port2data' to external I2C PCF8574 Port 2
*/
void setPort1(byte _port1data, boolean printIt)
{
port1data = _port1data;
Wire.beginTransmission(I2C_PCF8574_PORT1);
Wire.write(port1data);
Wire.endTransmission();
if(printIt) printPort1(3);
}
/*
* Print external I2C PCF8574 Port 2 (global variable 'port1data')
*/
void printPort1(int row)
{
String _string = String("P1:");
getByteAsString(_string, port1data);
lcd1.setCursor(0, row);
lcd1.print(padRight(_string));
}
/*
* Test external I2C PCF8574 Port - play with FLUX LED's
*/
void test1_Port1(unsigned int nCount)
{
while(/*(getKey() == 0) &&*/ (nCount-- > 0))
{
port1data = 0x00;
do
{
port1data = port1data << 1;
port1data |= B00000001;
setPort1(true);
delay(500);
/**if(getKey() != 0) return;*/
} while(port1data != 0xFF);
do
{
port1data = port1data << 1;
port1data &= B11111110;
setPort1(true);
delay(500);
/*if(getKey() != 0) return;*/
} while(port1data != 0x00);
}
}