/*
* WetterStation
* Version : 0.0
* Author : Hendrik Langer < hendrik + dev @ xd0 . de >
*/
# include <Arduino.h>
# include <ESP8266WiFi.h>
# include <ESP8266WiFiMulti.h>
extern " C " {
# include "user_interface.h"
}
# include <ESP8266HTTPClient.h>
# include <ArduinoJson.h>
# include <MQTTClient.h>
# include <Wire.h>
# include <SPI.h>
# include <Adafruit_Sensor.h>
# include <Adafruit_BMP085_U.h>
# include <DHT.h>
# include <RunningAverage.h>
# include <SdsDustSensor.h>
# include <XD0OTA.h>
# include "main.h"
# include "passwords.h"
const char * server = " ingress.opensensemap.org " ;
# define MQTT_MAX_PACKET_SIZE 512
const char * mqttserver = " home.xd0.de " ;
const char * mqttusername = " esp-weatherstation " ;
const char * mqttpassword = PWD_MQTT ;
constexpr unsigned int postingInterval = 60000 ; //Uploadintervall in Millisekunden
constexpr unsigned int dhcp_interval = 60 * 60 * 1000 ;
uint32_t loop_count = 0 ;
# define EXTERNAL_POWER 1
//senseBox ID
# define SENSEBOX_ID "5a9e9e38f55bff001a494877"
//Sensor IDs
// Temperature
# define SENSOR1_ID "5a9e9e38f55bff001a49487e"
// Humidity
# define SENSOR2_ID "5a9e9e38f55bff001a49487d"
// Pressure
# define SENSOR3_ID "5a9e9e38f55bff001a49487c"
// PM10
# define SENSOR4_ID "5a9e9e38f55bff001a49487b"
// PM2.5
# define SENSOR5_ID "5a9e9e38f55bff001a49487a"
// Radioactivity
# define SENSOR6_ID "5a9e9e38f55bff001a494879"
// Voltage
# define SENSOR7_ID "5a9e9e38f55bff001a494878"
// RSSI
# define SENSOR8_ID "5a9eddb1f55bff001a51de52"
static constexpr uint8_t BMP_SCL = D4 ;
static constexpr uint8_t BMP_SDA = D3 ;
static constexpr uint8_t DHT22_PIN = D7 ;
static constexpr uint8_t DHTTYPE = DHT22 ; // DHT 22 (AM2302)
static constexpr uint8_t SDS_TX = D1 ;
static constexpr uint8_t SDS_RX = D2 ;
static constexpr uint8_t GEIGER_PIN = D6 ;
static constexpr float CONV_FACTOR = 0.008120 ;
static constexpr float OWN_BACKGROUND_CPS = 0 ; // documentation says 0.2 (make sure value doesn't get negative if subtracting!)
# ifndef EXTERNAL_POWER
ADC_MODE ( ADC_VCC ) ;
# endif
ESP8266WiFiMulti wifiMulti ;
os_timer_t Timer1 ;
RunningAverage geigeraverage ( 10 ) ;
Adafruit_BMP085_Unified bmp = Adafruit_BMP085_Unified ( 10085 ) ;
DHT dht ( DHT22_PIN , DHTTYPE ) ;
SdsDustSensor sds ( SDS_TX , SDS_RX ) ;
volatile unsigned long geiger_counts = 0 ;
unsigned long geiger_previousMillis ;
unsigned long last_dhcp = 0 ;
unsigned long previousMillis = 0 ;
IPAddress ip , dns , gateway , subnet ;
char ssid [ 64 ] ;
char password [ 64 ] ;
struct __attribute__ ( ( packed ) ) SENSOR_DATA {
float temperature ;
float humidity ;
float pressure ;
float temp2 ;
float p10 ;
float p25 ;
float cpm ;
float radioactivity ;
float voltage ;
float rssi ;
bool sds_updated ;
} sd ;
static uint32_t cal = system_rtc_clock_cali_proc ( ) ; // WARNING: UPDATING THIS WILL MAKE THE rtcMillis() RETURN A LOWER VALUE THEN BEFORE, EVEN IF TIME PASSED, BREAKING INTERVAL CHECKS!
unsigned long rtcMillis ( ) {
uint64_t rtc_t = system_get_rtc_time ( ) ;
// uint32_t timemicrosec = rtc_t*((uint64) ((cal * 1000) >> 12));
// Serial.printf("cal: %d.%d \r\n", ((cal*1000)>>12)/1000, ((cal*1000)>>12)%1000 );
// Serial.printf("cal: %u\n", cal);
return ( ( uint64_t ) ( rtc_t * ( uint64_t ) cal ) > > 12 ) / 1000 ;
}
void fpm_wakup_cb_func1 ( void ) {
//gpio_pin_wakeup_disable();
//ESP.wdtFeed();
//wifi_fpm_do_wakeup();
wifi_fpm_close ( ) ;
geiger_counts + + ;
}
void lightsleep ( ) {
ESP . wdtFeed ( ) ;
wifi_station_disconnect ( ) ;
wifi_set_opmode ( NULL_MODE ) ;
wifi_fpm_set_sleep_type ( LIGHT_SLEEP_T ) ;
wifi_fpm_open ( ) ; // Enables force sleep
GPIO_DIS_OUTPUT ( GPIO_ID_PIN ( GEIGER_PIN ) ) ;
PIN_FUNC_SELECT ( PERIPHS_IO_MUX_MTCK_U , FUNC_GPIO13 ) ; // PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U,FUNC_GPIO13);
gpio_pin_wakeup_enable ( GPIO_ID_PIN ( GEIGER_PIN ) , GPIO_PIN_INTR_LOLEVEL ) ;
wifi_fpm_set_wakeup_cb ( fpm_wakup_cb_func1 ) ;
wifi_fpm_do_sleep ( 0xFFFFFFF ) ; // Sleep for longest possible time
//delay(1);
}
void ICACHE_FLASH_ATTR getValuesJSON ( char * buffer , const size_t buf_len , int format ) {
StaticJsonBuffer < MQTT_MAX_PACKET_SIZE > jsonBuffer ; // ToDo: buf_len
if ( format = = 1 ) {
JsonArray & array = jsonBuffer . createArray ( ) ;
JsonObject & temperatureObject = array . createNestedObject ( ) ;
temperatureObject [ " sensor " ] = SENSOR1_ID ;
temperatureObject [ " value " ] = sd . temperature ;
JsonObject & humidityObject = array . createNestedObject ( ) ;
humidityObject [ " sensor " ] = SENSOR2_ID ;
humidityObject [ " value " ] = sd . humidity ;
JsonObject & pressureObject = array . createNestedObject ( ) ;
pressureObject [ " sensor " ] = SENSOR3_ID ;
pressureObject [ " value " ] = sd . pressure ;
if ( sd . sds_updated ) {
JsonObject & pm10Object = array . createNestedObject ( ) ;
pm10Object [ " sensor " ] = SENSOR4_ID ;
pm10Object [ " value " ] = sd . p10 ;
JsonObject & pm25Object = array . createNestedObject ( ) ;
pm25Object [ " sensor " ] = SENSOR5_ID ;
pm25Object [ " value " ] = sd . p25 ;
}
if ( sd . cpm > 0 ) {
JsonObject & cpmObject = array . createNestedObject ( ) ;
cpmObject [ " sensor " ] = SENSOR6_ID ;
cpmObject [ " value " ] = sd . radioactivity ;
}
if ( sd . voltage > 2.5 ) {
JsonObject & voltageObject = array . createNestedObject ( ) ;
voltageObject [ " sensor " ] = SENSOR7_ID ;
voltageObject [ " value " ] = sd . voltage ;
}
if ( sd . rssi ! = 0 ) {
JsonObject & rssiObject = array . createNestedObject ( ) ;
rssiObject [ " sensor " ] = SENSOR8_ID ;
rssiObject [ " value " ] = sd . rssi ;
}
array . printTo ( buffer , buf_len ) ;
} else if ( format = = 2 ) {
JsonObject & root = jsonBuffer . createObject ( ) ;
root [ " temperature " ] = sd . temperature ;
root [ " humidity " ] = sd . humidity ;
root [ " pressure " ] = sd . pressure ;
if ( sd . sds_updated ) {
root [ " pm10 " ] = sd . p10 ;
root [ " pm2.5 " ] = sd . p25 ;
}
if ( sd . cpm > 0 ) {
root [ " cpm " ] = sd . cpm ;
root [ " radioactivity " ] = sd . radioactivity ;
}
if ( sd . voltage > 2.5 ) {
root [ " voltage " ] = sd . voltage ;
}
if ( sd . rssi ! = 0 ) {
root [ " rssi " ] = sd . rssi ;
}
root [ " millis " ] = millis ( ) ;
root [ " heap " ] = ESP . getFreeHeap ( ) ;
root . printTo ( buffer , sizeof ( buffer ) ) ;
}
}
void ICACHE_FLASH_ATTR sendValues ( ) {
sd . temperature = dht . readTemperature ( ) ;
sd . humidity = dht . readHumidity ( ) ;
bmp . getPressure ( & ( sd . pressure ) ) ;
sd . pressure / = 100 ;
bmp . getTemperature ( & ( sd . temp2 ) ) ;
# ifndef EXTERNAL_POWER
sd . voltage = ESP . getVcc ( ) / 1024.0 ;
# else
sd . voltage = analogRead ( A0 ) * 0.04285078 - 0.05942125 ; // by linear regression for my(!) voltage divider. else: sd.voltage = analogRead(A0)*3.3*(R1+R2)/(R2*1024)
# endif
// sds.wakeup(); delay(30000); // working 30 seconds
PmResult pm = sds . queryPm ( ) ;
sd . sds_updated = false ;
if ( pm . isOk ( ) & & ( pm . pm25 ! = sd . p25 | | pm . pm10 ! = sd . p10 ) ) {
sd . p25 = pm . pm25 ;
sd . p10 = pm . pm10 ;
sd . sds_updated = true ;
} else {
sd . sds_updated = false ;
}
// sds.sleep();
if ( rtcMillis ( ) - geiger_previousMillis > 10000 ) {
sd . cpm = geiger_counts * 60000 / ( rtcMillis ( ) - geiger_previousMillis ) ;
geiger_previousMillis = rtcMillis ( ) ;
geiger_counts = 0 ;
geigeraverage . addValue ( sd . cpm ) ;
}
//geiger_runningaverage = geigeraverage.getAverage()*10;
float constexpr own_cpm = OWN_BACKGROUND_CPS * 60 ;
sd . radioactivity = ( sd . cpm - own_cpm ) * CONV_FACTOR ;
DEBUG_MSG ( " Temperature : %6.2f°C (DHT22) \n " , sd . temperature ) ;
DEBUG_MSG ( " Humidity : %6.2f%% (DHT22) \n " , sd . humidity ) ;
DEBUG_MSG ( " Temperature : %6.2f°C (BMP180) \n " , sd . temp2 ) ;
DEBUG_MSG ( " Pressure : %6.2fhPa (BMP180) \n " , sd . pressure ) ;
if ( sd . sds_updated ) DEBUG_MSG ( " Particles 10 : %6.2fµg/m³ (SDS011) \n " , sd . p10 ) ;
if ( sd . sds_updated ) DEBUG_MSG ( " Particles 2.5: %6.2fµg/m³ (SDS011) \n " , sd . p25 ) ;
if ( sd . cpm > 0 ) DEBUG_MSG ( " Radiation : %6.2fµSv/h (J305) \n " , sd . radioactivity ) ;
DEBUG_MSG ( " Voltage : %6.2fV (ESP8266) \n " , sd . voltage ) ;
char buffer [ MQTT_MAX_PACKET_SIZE ] ;
getValuesJSON ( buffer , MQTT_MAX_PACKET_SIZE , 1 ) ;
WiFi . forceSleepWake ( ) ;
delay ( 1 ) ; // yield();
WiFi . persistent ( false ) ; // don't load and save credentials to flash
WiFi . mode ( WIFI_STA ) ;
wifiMulti . addAP ( " nether.net " , PWD_NETHERNET ) ;
wifiMulti . addAP ( " LNet " , PWD_LNET ) ;
wifiMulti . addAP ( " hw1_gast " , PWD_HW1 ) ;
wifiMulti . addAP ( " Freifunk " , " " ) ;
if ( ip ! = INADDR_NONE & & dns ! = INADDR_NONE & & gateway ! = INADDR_NONE & & subnet ! = INADDR_NONE
& & ( ( ip [ 0 ] = = 192 & & ip [ 1 ] = = 168 ) | | ( ip [ 0 ] = = 172 & & ip [ 1 ] = = 16 ) )
& & strlen ( ssid ) > 0 & & strlen ( password ) > 0
& & ( rtcMillis ( ) - last_dhcp < dhcp_interval )
) {
DEBUG_MSG ( " static ip \n " ) ;
WiFi . config ( ip , dns , gateway , subnet ) ;
WiFi . begin ( ssid , password ) ;
int tries = 0 ;
constexpr unsigned int retry_delay = 500 ;
constexpr unsigned int max_retry_delay = 10000 ;
while ( WiFi . status ( ) ! = WL_CONNECTED ) {
tries + + ;
DEBUG_MSG ( " . " ) ;
if ( tries * retry_delay > = max_retry_delay ) {
DEBUG_MSG ( " [ERROR] \n " ) ;
DEBUG_MSG ( " Rebooting.. \n " ) ;
ESP . restart ( ) ;
}
delay ( retry_delay ) ;
}
DEBUG_MSG ( " [CONNECTED, static] \n " ) ;
DEBUG_MSG ( " IP address: " ) ;
DEBUG_MSG ( " %s \n " , String ( WiFi . localIP ( ) ) . c_str ( ) ) ;
} else {
DEBUG_MSG ( " dhcp \n " ) ;
int tries = 0 ;
constexpr unsigned int retry_delay = 500 ;
constexpr unsigned int max_retry_delay = 12000 ;
while ( wifiMulti . run ( ) ! = WL_CONNECTED ) {
tries + + ;
DEBUG_MSG ( " . " ) ;
if ( tries * retry_delay > = max_retry_delay ) {
DEBUG_MSG ( " [ERROR] \n " ) ;
DEBUG_MSG ( " Rebooting.. \n " ) ;
ESP . restart ( ) ;
}
delay ( retry_delay ) ;
}
DEBUG_MSG ( " [CONNECTED, dhcp] \n " ) ;
DEBUG_MSG ( " IP address: " ) ;
DEBUG_MSG ( " %s \n " , String ( WiFi . localIP ( ) ) . c_str ( ) ) ;
ip = WiFi . localIP ( ) ;
dns = WiFi . dnsIP ( ) ;
gateway = WiFi . gatewayIP ( ) ;
subnet = WiFi . subnetMask ( ) ;
strncpy ( ssid , WiFi . SSID ( ) . c_str ( ) , 64 ) ;
strncpy ( password , WiFi . psk ( ) . c_str ( ) , 64 ) ;
last_dhcp = rtcMillis ( ) ;
}
sd . rssi = WiFi . RSSI ( ) ;
int httpCode = 0 ;
for ( int tries = 0 ; tries < 3 & & httpCode ! = HTTP_CODE_CREATED ; tries + + ) {
HTTPClient httpclient ;
char url [ 100 ] ;
sprintf ( url , " http://%s/boxes/%s/data " , server , SENSEBOX_ID ) ;
httpclient . begin ( url ) ;
httpclient . addHeader ( " Content-Type " , " application/json " ) ;
httpCode = httpclient . POST ( buffer ) ;
if ( httpCode > 0 ) {
if ( httpCode = = HTTP_CODE_CREATED ) {
# ifdef USERDEBUG
httpclient . writeToStream ( & Serial ) ;
DEBUG_MSG ( " \n " ) ;
# endif
} else {
DEBUG_MSG ( " [HTTP] POST... failed, error: %s \n " , httpclient . errorToString ( httpCode ) . c_str ( ) ) ;
}
}
httpclient . end ( ) ;
}
WiFiClientSecure net ;
MQTTClient mqttclient ( MQTT_MAX_PACKET_SIZE ) ;
mqttclient . begin ( mqttserver , 8883 , net ) ;
int tries = 0 ;
constexpr unsigned int retry_delay = 500 ;
constexpr unsigned int max_retry_delay = 5000 ;
while ( ! mqttclient . connect ( mqttusername , mqttusername , mqttpassword ) ) {
tries + + ;
DEBUG_MSG ( " . " ) ;
if ( tries * retry_delay > = max_retry_delay ) {
DEBUG_MSG ( " [ERROR] \n " ) ;
DEBUG_MSG ( " Rebooting.. \n " ) ;
ESP . restart ( ) ;
}
delay ( retry_delay ) ;
}
mqttclient . loop ( ) ;
getValuesJSON ( buffer , MQTT_MAX_PACKET_SIZE , 2 ) ;
if ( mqttclient . publish ( " sensor/esp-weatherstation/01/json " , buffer , strlen ( buffer ) ) ) {
DEBUG_MSG ( " mqtt done \n " ) ;
} else {
DEBUG_MSG ( " mqtt failed \n " ) ;
}
mqttclient . loop ( ) ;
mqttclient . disconnect ( ) ;
if ( sd . rssi = = 0 ) { // re-read rssi if zero
sd . rssi = WiFi . RSSI ( ) ;
}
if ( loop_count = = 1 ) {
XD0OTA ota ;
ota . update ( ) ;
} else if ( loop_count > 720 ) { // do an update every 12h
loop_count = 0 ;
}
loop_count + + ;
WiFi . disconnect ( ) ;
WiFi . mode ( WIFI_OFF ) ;
WiFi . forceSleepBegin ( ) ;
delay ( 1 ) ; // yield();
}
// geiger event when gpio pulled low (called vin on board)
/*void ICACHE_RAM_ATTR ISR_geiger_impulse() {
geiger_counts + + ;
DEBUG_MSG ( " X " ) ;
} */
void setup ( ) {
# ifdef USERDEBUG
Serial . begin ( 115200 ) ;
# endif
pinMode ( LED_BUILTIN , OUTPUT ) ;
digitalWrite ( LED_BUILTIN , HIGH ) ; // turn OFF board led
//wifi_status_led_uninstall();
sds . begin ( ) ;
sds . setCustomWorkingPeriod ( 15 ) ; // sensor sends data every 15 minutes
sds . setQueryReportingMode ( ) ; // ensures sensor is in 'query' reporting mode
Wire . begin ( BMP_SDA , BMP_SCL ) ;
if ( ! bmp . begin ( BMP085_MODE_STANDARD ) ) {
DEBUG_MSG ( " No valid BMP085 sensor! \n " ) ;
}
dht . begin ( ) ;
pinMode ( GEIGER_PIN , INPUT ) ;
//attachInterrupt(digitalPinToInterrupt(GEIGER_PIN), ISR_geiger_impulse, FALLING);
geigeraverage . clear ( ) ;
previousMillis = rtcMillis ( ) ;
DEBUG_MSG ( " ready. \n " ) ; //Serial.flush();
}
void loop ( ) {
unsigned long currentMillis = rtcMillis ( ) ;
if ( ( currentMillis - previousMillis ) > = postingInterval ) {
previousMillis = currentMillis ;
DEBUG_MSG ( " sending values... previousMillis:%u, millis():%u, difference:%d \n " , previousMillis , currentMillis , currentMillis - previousMillis ) ;
sendValues ( ) ;
DEBUG_MSG ( " resetting geiger counts. was: %d \n " , geiger_counts ) ;
geiger_previousMillis = rtcMillis ( ) ;
geiger_counts = 0 ;
}
DEBUG_MSG ( " sleeping now... (%u) \n " , currentMillis ) ;
delay ( 10 ) ;
lightsleep ( ) ;
delay ( 10 ) ; // debounce?
}