You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

244 lines
8.3 KiB
C++

#include <TinyGPS++.h>
#include "config.h"
#include "gps.h"
#include "wifi.h"
#include "api.h"
#include "storage.h"
#include "TelnetPrint.h"
#include "streampipe.h"
#include <Wire.h>
#include <HDC100X.h>
#include <SDS011-select-serial.h>
#include <SoftwareSerial.h>
#define DEBUG_OUT telnet // debug output is available via telnet!
Storage storage = Storage();
Wifi wifi = Wifi();
TelnetPrint telnet = TelnetPrint();
OsemApi api = OsemApi();
Gps gps = Gps();
SoftwareSerial sdsSerial(D1, D2); //D1, D2 works but kills I2C.. D4 is LED!
SDS011 sds(sdsSerial);
HDC100X hdc(0x43);
unsigned long cycleStart;
WifiState wifiState; // global, as both measure and upload need the state
TinyGPSLocation location;
bool storeMeasurement(float lat, float lng, float value, const char* timeStamp, const char* sensorID) {
Measurement m;
m.lat = lat;
m.lng = lng;
m.value = value;
strcpy(m.timeStamp, timeStamp);
strcpy(m.sensorID, sensorID);
return storage.add(m);
}
void measure(WifiState& wifiState, TinyGPSLocation& loc) {
char dateString[22];
float temperature, humidity, pm25, pm10;
int sds_error;
// measure WiFi
wifiState = wifi.scan(WIFI_SSID);
// update gps position. if we can't get a fix, skip the measurements
if(!gps.updateLocation() || !gps.updateTime()) {
DEBUG_OUT << "GPS timed out, skipping measurements" << EOL;
//digitalWrite(BUILTIN_LED, LOW); // turn status LED on
return;
}
//digitalWrite(BUILTIN_LED, HIGH);
loc = gps.getLocation();
gps.getISODate(dateString);
temperature = hdc.getTemp();
humidity = hdc.getHumi();
sds_error = sds.read(&pm25, &pm10);
if (sds_error) {
DEBUG_OUT << "could not read SDS011" << EOL;
//return;
}
// print state
DEBUG_OUT << "homeAvailable: " << wifiState.homeAvailable << EOL;
DEBUG_OUT << "numAPs: " << wifiState.numAccessPoints << EOL;
DEBUG_OUT << "numNetworks: " << wifiState.numNetworks << EOL;
DEBUG_OUT << "numUnencrypted: " << wifiState.numUnencrypted << EOL;
DEBUG_OUT << "temperature: " << temperature << EOL;
DEBUG_OUT << "humidity: " << humidity << EOL;
DEBUG_OUT << "PM2.5: " << pm25 << EOL;
DEBUG_OUT << "PM10: " << pm10 << EOL;
DEBUG_OUT.print("lat: ");
DEBUG_OUT.print(loc.lat(), 8);
DEBUG_OUT.print(" lng: ");
DEBUG_OUT.println(loc.lng(), 8);
DEBUG_OUT << dateString << EOL;
// write measurements to file
if (!storeMeasurement(loc.lat(), loc.lng(), wifiState.numAccessPoints, dateString, ID_SENSOR_WIFI_APS))
DEBUG_OUT << "measurement (wifi aps) store failed!" << EOL;
if (!storeMeasurement(loc.lat(), loc.lng(), wifiState.numNetworks, dateString, ID_SENSOR_WIFI_NET))
DEBUG_OUT << "measurement (wifi nets) store failed!" << EOL;
if (!storeMeasurement(loc.lat(), loc.lng(), wifiState.numUnencrypted, dateString, ID_SENSOR_WIFI_OPEN))
DEBUG_OUT << "measurement (wifi open) store failed!" << EOL;
if (temperature != -40) {
if (!storeMeasurement(loc.lat(), loc.lng(), temperature, dateString, ID_SENSOR_TEMP))
DEBUG_OUT << "measurement (temperature) store failed!" << EOL;
if (!storeMeasurement(loc.lat(), loc.lng(), humidity, dateString, ID_SENSOR_HUMI))
DEBUG_OUT << "measurement (humidity) store failed!" << EOL;
}
if (!sds_error) {
if (!storeMeasurement(loc.lat(), loc.lng(), pm10, dateString, ID_SENSOR_PM10))
DEBUG_OUT << "measurement (pm10) store failed!" << EOL;
if (!storeMeasurement(loc.lat(), loc.lng(), pm25, dateString, ID_SENSOR_PM25))
DEBUG_OUT << "measurement (pm25) store failed!" << EOL;
}
DEBUG_OUT << EOL;
}
void upload(WifiState& wifiState) {
bool connected = wifi.isConnected();
size_t numMeasures = storage.size();
DEBUG_OUT << numMeasures << " measurements stored." << EOL;
if (!numMeasures) return;
// in case we are not measuring, scan manually to detect the home network
// somehow the automatic reconnect does not work.. TODO
if (!connected && digitalRead(PIN_MEASURE_MODE) == LOW)
wifiState = wifi.scan(WIFI_SSID);
// connect to wifi, if available & not connected yet
// once we are connected, upload stored measurements & free the storage
// only upload limited measures per cycle, to avoid long gaps in measurements
if (connected || (wifiState.homeAvailable && wifi.reconnect()) ) {
uint16_t uploadCount = 0;
String measure;
int separatorPos;
String fileName;
while (storage.size() && uploadCount++ < MAX_UPLOADS_PER_CYCLE) {
measure = storage.get(fileName);
separatorPos = measure.indexOf('\n');
DEBUG_OUT << "Uploading measurement for " << measure;
if (api.postMeasurement(measure.substring(separatorPos + 1), measure.substring(0, separatorPos))) {
// remove the measurement from the local storage on success
DEBUG_OUT << "success!" << EOL;
storage.remove(fileName);
} else {
DEBUG_OUT << "upload failed!" << EOL;
}
}
} else {
DEBUG_OUT << "wifi connection to " << WIFI_SSID << " failed" << EOL;
}
DEBUG_OUT << EOL;
}
/**
* delay for a given duration (ms), rollover-safe implementation
* offset may be a duration which has been "used up" before, so the delay is adaptive,
* meaning that the interval of a adaptiveDelay() call is constant
* returns earlier, if we moved more than MEASUREMENT_DISTANCE meters from our last fix
*
* also polls the GPS serial & telnet connections!
*/
void adaptiveDelay(unsigned long ms, TinyGPSLocation& lastLoc, unsigned long offset = 0, bool checkDistance = false) {
unsigned long start = millis();
for (;;) {
// for some reason, operations have to be performed in this loop for
// proper operation, so we just do the polling to be done anyway
gps.pollGPS();
telnet.pollClients();
// update our location. if we moved MEASUREMENT_DISTANCE meters or more, return
TinyGPSLocation newLoc = gps.getLocation();
double distanceToPrevLoc = TinyGPSPlus::distanceBetween(lastLoc.lat(), lastLoc.lng(), newLoc.lat(), newLoc.lng());
if (checkDistance && distanceToPrevLoc >= MEASUREMENT_DISTANCE) {
DEBUG_OUT << "moved " << distanceToPrevLoc << "m, delay stopped." << EOL;
return;
}
unsigned long now = millis();
unsigned long elapsed = now - start + offset;
if (elapsed >= ms) return;
}
}
/* MAIN ENTRY POINTS */
void setup() {
//pinMode(BUILTIN_LED, OUTPUT); // status indicator LED: on = no GPS fix
//digitalWrite(BUILTIN_LED, LOW);
pinMode(PIN_MEASURE_MODE, INPUT_PULLUP); // switch for measurements (pull it down to disable)
pinMode(PIN_UPLOAD_MODE, INPUT_PULLUP); // switch for API uploads (pull it down to disable)
size_t bytesFree = storage.begin();
gps.begin();
wifi.begin(WIFI_SSID, WIFI_PASS);
DEBUG_OUT.begin(9600);
sdsSerial.begin(9600);
Wire.begin();
hdc.begin(HDC100X_TEMP_HUMI, HDC100X_14BIT, HDC100X_14BIT, DISABLE);
hdc.getTemp();
// wait until we got a first fix from GPS, and thus an initial time
// exception: measure mode is disabled (for quick upload only)
if (digitalRead(PIN_MEASURE_MODE) == HIGH) {
DEBUG_OUT << "Getting GPS fix..";
while (!gps.updateLocation()) {
adaptiveDelay(0, location); // poll for telnet connections
DEBUG_OUT << ".";
}
location = gps.getLocation();
DEBUG_OUT << " done!" << EOL;
//digitalWrite(BUILTIN_LED, HIGH);
}
// DEBUG
//while (!wifi.isConnected()) adaptiveDelay(500, location);
//String temp; while (storage.size()) storage.get(temp, true);
DEBUG_OUT << "Setup done!" << EOL;
DEBUG_OUT << "WiFi MAC WiFi IP" << EOL;
DEBUG_OUT << WiFi.macAddress() << " " << WiFi.localIP() << EOL;
DEBUG_OUT << "SPIFF bytes free: " << bytesFree << EOL << EOL;
}
void loop() {
cycleStart = millis();
adaptiveDelay(0, location); // do some polling inbetween
if (digitalRead(PIN_MEASURE_MODE) == HIGH)
measure(wifiState, location);
adaptiveDelay(0, location); // do some polling inbetween
if (digitalRead(PIN_UPLOAD_MODE) == HIGH)
upload(wifiState);
// run the measurements in a fixed interval, using an adaptive delay
// the interval is defined by a duration and/or distance from our last fix
if (digitalRead(PIN_MEASURE_MODE) == HIGH)
return adaptiveDelay(MEASUREMENT_INTERVAL, location, millis() - cycleStart, MEASUREMENT_DISTANCE_ENABLED);
// run as fast as possible when not measuring.
// smartDelay has to be called anyway, as some polling functions are run within
adaptiveDelay(0, location);
}