/*
* My FISHINO Uno Air Quality station
*
* Copyleft Lumir Vanek
*
* Updated: 7.6.2017
*/
#include <avr/pgmspace.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Fishino.h>
#include <SPI.h>
#define _USE_WIFI
#define _USE_RTC
#define _USE_CCS811
#define _USE_Si7021 // I2C Si7021 Humidity and Temperature sensor
#ifdef _USE_RTC
#include "RTClib.h"
RTC_DS1307 rtc;
#endif
#ifdef _USE_Si7021
#include "SparkFun_Si7021_Breakout_Library.h"
#endif
#ifdef _USE_CCS811
#include <SparkFunCCS811.h>
#endif
// I2C bus adresses
#define I2C_LCD1 0x3F // LCD 20x4 driven by HD44780, connected to I2C module PCF8574AT (7bit I2C address)
#define I2C_CCS811 0x5B //Default I2C Address
// WiFi AP definitions
const char AP_SSID[] = "SSID"; // Case sensitive !
const char AP_PASSWORD[] = "secret";
// IOT service site information
const char http_ip[] = "10.5.73.x"; // dlg360g5
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 = "Fishino AQ Station";
const char msg_fw_version[] PROGMEM = "SW 6.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 PCF8574AT
*/
#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
FishinoClient 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
//************* 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;
/*
* 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_Si7021
// The Si7021 has a default I2C address of 0x40 and cannot be changed!
Weather si7021; //Create Instance of HTU21D or SI7021 temp and humidity sensor and MPL3115A2 barrometric sensor
const char sensorCode_Si7021Hum[] = "LV_HUM_01";
const char sensorCode_Si7021Temp[] = "LV_TEMP_09";
#endif
#ifdef _USE_CCS811
CCS811 ccs811(I2C_CCS811);
const char sensorCode_CCS811VOC[] = "LV_VOC_01";
const char sensorCode_CCS811CO2[] = "LV_CO2_01";
#endif
void setup()
{
// Current LCD display page init
currentPage = 0;
Serial.begin(115200);
Wire.begin();
#ifdef _USE_CCS811
/*
* Init CCS811 I2C Air Quality sensor
*/
delay(200);
Serial << F("Init CCS811 I2C Air Quality sensor\n");
//This begins the CCS811 sensor and prints error status of .begin()
CCS811Core::status returnCode = ccs811.begin();
Serial << F("CCS811 begin exited with: ");
printCCS811DriverError(returnCode);
returnCode = ccs811.setDriveMode(2); // A measurement is performed every 10 seconds
Serial << F("CCS811 setDriveMode exited with: ");
printCCS811DriverError(returnCode);
#endif
#ifdef _USE_Si7021
/*
* Init Si7021 I2C temp and humidity sensor
*/
Serial << F("Init Si7021 I2C temp and humidity sensor\n");
si7021.begin();
#endif
/*
* 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__)));
}
#endif
/*
* 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__)));
/*
* Hello message
*/
sayHello();
/*
* Reset and test WiFi module
*/
while(!Fishino.reset())
{
Serial << F("Fishino RESET FAILED, RETRYING...\n");
}
Serial << F("Fishino WiFi RESET OK\n");
Fishino.setMode(STATION_MODE);
Fishino.setPhyMode(PHY_MODE_11G);
// Print WiFi MAC address:
printMacAddress();
Serial << F("FW version: ");
Serial.println(Fishino.firmwareVersionStr());
#ifdef _USE_WIFI
/*
* Connect to WiFi
*/
wifiConnect();
printWifiStatus();
#endif
}
void loop()
{
//digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
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;
#ifdef _USE_WIFI
/*
* Relay 1 - bit 6
*/
success &= getValue(getStringNonPadded(svc_ix_get_last_state_txt), relayCode_01, relay);
if (relay.charAt(0) == '1')
{
Serial << F("\Relay 1 ON\n");
}
else
{
Serial << F("\Relay 1 OFF\n");
}
/*
* Relay 2 - bit 7
*/
success &= getValue(getStringNonPadded(svc_ix_get_last_state_txt), relayCode_02, relay);
if (relay.charAt(0) == '1')
{
Serial << F("\nRelay 2 ON\n");
}
else
{
Serial << F("\nRelay 2 OFF\n");
}
#endif
#ifdef _USE_WIFI
if (!success)
{
clearLCD(lcd1);
lcd1.print("Error. Resetting Fishino");
while(!Fishino.reset())
{
Serial << F("Fishino RESET FAILED, RETRYING...\n");
}
Serial << F("Fishino WiFi RESET OK\n");
Fishino.setMode(STATION_MODE);
wifiConnect();
printWifiStatus();
return;
}
#endif
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);
// Default used if Si7021 not works
float humidity7021 = 40.0;
float temperature7021 = 25.0;
#ifdef _USE_Si7021
humidity7021 = si7021.getRH();
temperature7021 = si7021.getTemp();
Serial.print("Si7021 Humidity: ");
Serial.print(humidity7021);
Serial.println(" %");
Serial.print("Si7021 Temperature: ");
Serial.print(temperature7021);
Serial.println(" deg C");
clearLCD(lcd1);
line1 = "Si7021 " + String((char) char_temperature) + " " + String(temperature7021, 1) + " " + String((char)char_degree) + "C";
lcd1.print(line1);
lcd1.setCursor(0, 1);
line2 = "Si7021 " + String((char) char_humidity) + " " + String(humidity7021, 1) + " %";
lcd1.print(line2);
#ifdef _USE_WIFI
sendValue(sensorCode_Si7021Hum, humidity7021);
sendValue(sensorCode_Si7021Temp, temperature7021);
#endif
#endif
if (humidity7021 < 0) { humidity7021 = 40.0; }
if (humidity7021 > 100) { humidity7021 = 100.0; }
#ifdef _USE_CCS811
lcd1.setCursor(0, 2);
//Check to see if data is available
if (ccs811.dataAvailable())
{
//Calling this function updates the global tVOC and eCO2 variables
ccs811.readAlgorithmResults();
//This sends the temperature data to the CCS811
ccs811.setEnvironmentalData(humidity7021, temperature7021);
Serial.print("CO2 concentration : ");
float co2 = ccs811.getCO2();
Serial.print(co2);
Serial.println(" ppm");
line3 = "CCS811 CO2 " + String(co2, 0) + " ppm";
lcd1.print(line3);
#ifdef _USE_WIFI
if (co2 >= 400)
{
sendValue(sensorCode_CCS811CO2, co2);
}
#endif
Serial.print("VOC concentration : ");
float voc = ccs811.getTVOC();
Serial.print(voc);
Serial.println(" ppb");
lcd1.setCursor(0, 3);
line4 = "CCS811 VOC " + String(voc, 0) + " ppb";
lcd1.print(line4);
#ifdef _USE_WIFI
if(voc > 0)
{
sendValue(sensorCode_CCS811VOC, voc);
}
#endif
}
else if (ccs811.checkForStatusError())
{
//If the CCS811 found an internal error, print it.
printCCS811SensorError();
}
#endif
delay(20000);
}
/*
* 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();
}
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
/*
* 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;
}
/**
* Connect to WiFi AP
* @return true if success, otherwise false
*/
boolean wifiConnect()
{
lcd1.setCursor(0, 3);
Serial << F("Connecting to AP\n");
String msg = String(F("Connecting to AP"));
lcd1.print(msg);
// Initiate connection with SSID and PSK
while(!Fishino.begin(AP_SSID, AP_PASSWORD))
{
Serial << ".";
delay(2000);
}
Serial << "OK\n";
int numberOfTries = 8;
Serial << F("Waiting for IP...");
Fishino.staStartDHCP();
// Wait till connection is established
while (Fishino.status() != STATION_GOT_IP)
{
if(numberOfTries-- <= 0)
{
Serial << F("WiFi connect failed !");
lcd1.setCursor(0, 2);
msg = String(F("WiFi connect failed !"));
return false;
}
delay(1000);
Serial << ".";
lcd1.print(".");
}
Serial.println("");
Serial << F("WiFi connected\n");
lcd1.setCursor(0, 2);
msg = String(F("WiFi connected"));
lcd1.print(padRight(msg));
return true;
}
void printWifiStatus()
{
// print the SSID of the network you're attached to:
Serial << F("SSID: ");
Serial.println(Fishino.SSID());
// print your WiFi shield's IP address:
IPAddress ip = Fishino.localIP();
Serial << F("IP Address: ");
Serial.println(ip);
lcd1.setCursor(0, 3);
lcd1.print("IP: ");
String s0(ip[0]);
String s1(ip[1]);
String s2(ip[2]);
String s3(ip[3]);
lcd1.print(s0); lcd1.print(".");
lcd1.print(s1); lcd1.print(".");
lcd1.print(s2); lcd1.print(".");
lcd1.print(s3);
// print the received signal strength:
/*long rssi = Fishino.RSSI();
Serial << F("Signal strength (RSSI):");
Serial.print(rssi);
Serial << F(" dBm\n");
lcd1.setCursor(0, 3);
lcd1.print("RSSI: ");
String strRssi(rssi);
lcd1.print(strRssi); lcd1.print(" dBm");*/
// get phy mode and show it
uint8_t mode = Fishino.getPhyMode();
Serial.print("PHY MODE: (");
Serial.print(mode);
Serial.print(") ");
switch(mode)
{
case PHY_MODE_11B:
Serial.println("11b");
lcd1.print(", 11b");
break;
case PHY_MODE_11G:
Serial.println("11g");
lcd1.print(", 11g");
break;
case PHY_MODE_11N:
Serial.println("11n");
lcd1.print(", 11n");
break;
default:
Serial.println("UNKNOWN");
}
}
void printMacAddress()
{
// print your MAC address:
byte const *mac = Fishino.macAddress();
Serial << F("MAC: ");
Serial.print(mac[5], HEX);
Serial.print(":");
Serial.print(mac[4], HEX);
Serial.print(":");
Serial.print(mac[3], HEX);
Serial.print(":");
Serial.print(mac[2], HEX);
Serial.print(":");
Serial.print(mac[1], HEX);
Serial.print(":");
Serial.println(mac[0], HEX);
}
/**
* 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) { delay(1000); }
} 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)
{
bool success = false;
if (!client.connected() && !client.available())
{
Serial << F("\nStarting connection to server...");
if(client.connect(http_ip, http_port))
{
Serial << F("Connected to server\n");
}
}
String url = String(F("/IotService/rest/"));
url += service;
url += F("?code=");
url += sensorCode;
Serial << F("GET data to URL: ");
Serial.println(url);
// Make an HTTP GET request
client << F("GET ") << url << F(" HTTP/1.1\r\n");
client << F("Host: ") << http_ip << F("\r\n");
client << F("Connection: close\r\n");
client << F("User-Agent: FishinoWiFi/1.1\r\n");
client.println();
//String reply = String("");
delay(100);
if (!client.available())
{
delay(1000);
}
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);
}
return success;
}
/**
* Send value to IOT service
*/
void sendValue(const char * sensorCode, float value)
{
int numberOfTries = 5;
while(!client.connect(http_ip, http_port))
{
Serial.println("Connection to IOT service failed !");
if(numberOfTries-- <=0) return;
wifiConnect();
}
Serial.println("Connected");
String url = "/IotService/rest/insert-value/" + String(sensorCode) + "/" + String(value);
Serial.print("GET data to URL: ");
Serial.println(url);
client << F("GET ") << url << F(" HTTP/1.1\r\n");
client << F("Host: ") << http_ip << F("\r\n");
client << F("Connection: close\r\n");
client << F("User-Agent: FishinoWiFi/1.1\r\n");
client.println();
delay(100);
while(client.available())
{
String line = client.readStringUntil('\r');
Serial.print(line);
}
Serial.println();
Serial.println("Connection closed");
}
///////////////////////////////////////////////////////////////////////////
//
// CCS811 Air Quality sensor support
//
///////////////////////////////////////////////////////////////////////////
#ifdef _USE_CCS811
/*
* Decode the CCS811Core::status type and prints the type of error to the serial terminal.
*
* Save the return value of any function of type CCS811Core::status, then pass
* to this function to see what the output was.
*/
void printCCS811DriverError(CCS811Core::status errorCode)
{
switch (errorCode)
{
case CCS811Core::SENSOR_SUCCESS:
Serial.println(F("CCS811 SUCCESS"));
break;
case CCS811Core::SENSOR_ID_ERROR:
Serial.println(F("CCS811 ID ERROR"));
break;
case CCS811Core::SENSOR_I2C_ERROR:
Serial.println(F("CCS811 I2C ERROR"));
break;
case CCS811Core::SENSOR_INTERNAL_ERROR:
Serial.println(F("CCS811 INTERNAL ERROR"));
break;
case CCS811Core::SENSOR_GENERIC_ERROR:
Serial.println(F("CCS811 GENERIC ERROR"));
break;
default:
Serial.println(F("Unspecified error."));
}
}
/*
* Gets, clears, then prints the errors saved within the error register.
*/
void printCCS811SensorError()
{
uint8_t error = ccs811.getErrorRegister();
if (error == 0xFF) //comm error
{
Serial.println("Failed to get ERROR_ID register.");
}
else
{
String msg("CCS811 Error: ");
if (error & 1 << 5) msg += ("HeaterSupply");
if (error & 1 << 4) msg += ("HeaterFault");
if (error & 1 << 3) msg += ("MaxResistance");
if (error & 1 << 2) msg += ("MeasModeInvalid");
if (error & 1 << 1) msg += ("ReadRegInvalid");
if (error & 1 << 0) msg += ("MsgInvalid");
Serial.println(msg);
lcd1.print(msg);
}
}
#endif