initial version
commit
50085550dd
@ -0,0 +1,70 @@
|
|||||||
|
/**
|
||||||
|
* sets up a telnet server, which can then be used for debug logging purposes
|
||||||
|
* provides all Print methods.
|
||||||
|
* begin() must be called in the setup!
|
||||||
|
* pollClients() must be called in the loop!
|
||||||
|
* to disable the routines, just skip the begin() call in the setup
|
||||||
|
*/
|
||||||
|
|
||||||
|
# pragma once
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#define MAX_TELNET_CLIENTS 3
|
||||||
|
|
||||||
|
class TelnetPrint : public Print {
|
||||||
|
protected:
|
||||||
|
WiFiServer server;
|
||||||
|
WiFiClient clients[MAX_TELNET_CLIENTS];
|
||||||
|
|
||||||
|
public:
|
||||||
|
TelnetPrint(uint16_t port=23) : server(port) {}
|
||||||
|
|
||||||
|
void begin() {
|
||||||
|
server.begin();
|
||||||
|
server.setNoDelay(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pollClients() {
|
||||||
|
//check clients for data
|
||||||
|
for(uint16_t i = 0; i < MAX_TELNET_CLIENTS; i++){
|
||||||
|
if (clients[i] && clients[i].connected()){
|
||||||
|
while(clients[i].available()) clients[i].read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to establish pending connections
|
||||||
|
if (server.hasClient()) {
|
||||||
|
for(uint8_t i = 0; i < MAX_TELNET_CLIENTS; i++){
|
||||||
|
//find free/disconnected spot
|
||||||
|
if (!clients[i] || !clients[i].connected()){
|
||||||
|
if(clients[i]) clients[i].stop();
|
||||||
|
clients[i] = server.available();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//no free/disconnected spot so reject
|
||||||
|
WiFiClient serverClient = server.available();
|
||||||
|
serverClient.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: find more efficient method than sending byte by byte
|
||||||
|
virtual size_t write(uint8_t s) {
|
||||||
|
for(uint8_t i = 0; i < MAX_TELNET_CLIENTS; i++){
|
||||||
|
if (clients[i] && clients[i].connected()){
|
||||||
|
clients[i].write(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is only called for Strings, not char arrays
|
||||||
|
virtual size_t write(const char *buffer, size_t size) {
|
||||||
|
for(uint8_t i = 0; i < MAX_TELNET_CLIENTS; i++){
|
||||||
|
if (clients[i] && clients[i].connected()){
|
||||||
|
clients[i].write(buffer, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <WiFiClientSecure.h>
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
WiFiClientSecure api;
|
||||||
|
|
||||||
|
bool postMeasurement(String m, String sensorID) {
|
||||||
|
//telnet.print("Connecting to API.. ");
|
||||||
|
if (!api.connect(API_ENDPOINT, 443)) {
|
||||||
|
//telnet.println("connection failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (api.verify(API_FINGERPRINT, API_ENDPOINT)) {
|
||||||
|
//telnet.println("certificate matches");
|
||||||
|
} else {
|
||||||
|
//telnet.println("certificate doesn't match");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String url = "/boxes/" + String(ID_BOX) + "/" + sensorID;
|
||||||
|
api.print(String("POST ") + url + " HTTP/1.1\r\n" +
|
||||||
|
"Host: " + API_ENDPOINT + "\r\n" +
|
||||||
|
"User-Agent: mobile-sensebox-esp8266\r\n" +
|
||||||
|
"Connection: close\r\n\r\n");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
/* WiFi (ESP8266) */
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
static const char* WIFI_SSID = "Elmo";
|
||||||
|
static const char* WIFI_PASS = "FreiBier123";
|
||||||
|
|
||||||
|
/* GPS reciever (uBloc NEO-7M) */
|
||||||
|
static const int GPS_RX_PIN = 4;
|
||||||
|
static const int GPS_TX_PIN = 3;
|
||||||
|
static const int GPS_BAUD = 9600;
|
||||||
|
static const int GPS_INTERVAL = 1000; // update interval of the gps device
|
||||||
|
|
||||||
|
/* API (openSenseMap) */
|
||||||
|
static const char* API_ENDPOINT = "api.opensensemap.org";
|
||||||
|
// SHA1 of the API SSL cert
|
||||||
|
static const char* API_FINGERPRINT = "0F B0 0C E0 FD 18 C2 0B 07 1C 21 AB A0 FF EF CC 09 62 57 A9";
|
||||||
|
static const char* API_KEY = "...";
|
||||||
|
static const char* ID_BOX = "57308b2c566b8d3c11114a9f";
|
||||||
|
static const char* ID_SENSOR_WIFI = "...";
|
@ -0,0 +1,52 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <TinyGPS++.h>
|
||||||
|
#include <Time.h>
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
/* GPS */
|
||||||
|
TinyGPSPlus gps;
|
||||||
|
// allocate the maximum buffer size, so we can reduce the Serial polling interval to a minimum
|
||||||
|
//SoftwareSerial gpsSerial(GPS_RX_PIN, GPS_TX_PIN, false, 27*49);
|
||||||
|
|
||||||
|
void pollGPS() {
|
||||||
|
while (Serial.available() > 0)
|
||||||
|
gps.encode(Serial.read());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool updateLocation() {
|
||||||
|
// abort if the GPS device does not push valid data within one update cycle
|
||||||
|
static const unsigned int timeout = 2 * GPS_INTERVAL;
|
||||||
|
unsigned long start = millis();
|
||||||
|
|
||||||
|
do {
|
||||||
|
pollGPS();
|
||||||
|
if (millis() - start > timeout) return false;
|
||||||
|
} while (!gps.location.isUpdated());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool updateTime() {
|
||||||
|
// abort if the GPS device does not push valid data within one update cycle
|
||||||
|
static const unsigned int timeout = 2 * GPS_INTERVAL;
|
||||||
|
unsigned long start = millis();
|
||||||
|
|
||||||
|
do {
|
||||||
|
pollGPS();
|
||||||
|
if (millis() - start > timeout) return false;
|
||||||
|
} while ( !(gps.date.isUpdated() && gps.time.isUpdated()) );
|
||||||
|
|
||||||
|
// in case we didnt timeout, resync the local clock
|
||||||
|
setTime(gps.time.hour(), gps.time.minute(), gps.time.second(),
|
||||||
|
gps.date.day(), gps.date.month(), gps.date.year());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* UTILS */
|
||||||
|
String getISODate() {
|
||||||
|
char result[16] = { 0 };
|
||||||
|
sprintf(result, "%04d-%02d-%02d-T%02d:%02d:%02dZ",
|
||||||
|
year(), month(), day(), hour(), minute(), second());
|
||||||
|
return (String)result;
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
#include "config.h"
|
||||||
|
#include "gps.h"
|
||||||
|
#include "wifi.h"
|
||||||
|
#include "api.h"
|
||||||
|
#include "storage.h"
|
||||||
|
#include "TelnetPrint.h"
|
||||||
|
|
||||||
|
#define DEBUG_OUT Serial
|
||||||
|
|
||||||
|
//TelnetPrint telnet = TelnetPrint();
|
||||||
|
Storage storage = Storage();
|
||||||
|
|
||||||
|
/* UTILS */
|
||||||
|
void printState(WifiState wifi) {
|
||||||
|
DEBUG_OUT.print("homeAvailable: ");
|
||||||
|
DEBUG_OUT.println(wifi.homeAvailable);
|
||||||
|
DEBUG_OUT.print("numNetworks: ");
|
||||||
|
DEBUG_OUT.println(wifi.numNetworks);
|
||||||
|
DEBUG_OUT.print("numUnencrypted: ");
|
||||||
|
DEBUG_OUT.println(wifi.numUnencrypted);
|
||||||
|
|
||||||
|
DEBUG_OUT.print("lat: ");
|
||||||
|
//DEBUG_OUT.print(gps.location.lat(), 6);
|
||||||
|
DEBUG_OUT.print(" lng: ");
|
||||||
|
//DEBUG_OUT.println(gps.location.lng(), 6);
|
||||||
|
|
||||||
|
DEBUG_OUT.println(getISODate());
|
||||||
|
DEBUG_OUT.println("");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* MAIN ENTRY POINTS */
|
||||||
|
void setup() {
|
||||||
|
//Serial.begin(9600); // GPS reciever
|
||||||
|
DEBUG_OUT.begin(115200);
|
||||||
|
|
||||||
|
WiFi.mode(WIFI_STA);
|
||||||
|
connectWifi(WIFI_SSID, WIFI_PASS);
|
||||||
|
|
||||||
|
delay(5000);
|
||||||
|
|
||||||
|
// wait until we got a first fix from GPS, and thus an initial time
|
||||||
|
/*DEBUG_OUT.print("Getting GPS fix..");
|
||||||
|
while (!updateLocation()) { DEBUG_OUT.print("."); }
|
||||||
|
DEBUG_OUT.print(" done! ");*/
|
||||||
|
DEBUG_OUT.println(getISODate());
|
||||||
|
|
||||||
|
DEBUG_OUT.println("Setup done!\n");
|
||||||
|
DEBUG_OUT.println("WiFi MAC WiFi IP");
|
||||||
|
DEBUG_OUT.print(WiFi.macAddress());
|
||||||
|
DEBUG_OUT.print(" ");
|
||||||
|
DEBUG_OUT.println(WiFi.localIP());
|
||||||
|
|
||||||
|
DEBUG_OUT.print("SPIFF bytes free: ");
|
||||||
|
DEBUG_OUT.println(storage.begin());
|
||||||
|
|
||||||
|
digitalWrite(D9, HIGH);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool firstLoop = true;
|
||||||
|
void loop() {
|
||||||
|
//pollGPS();
|
||||||
|
//DEBUG_OUT.pollClients();
|
||||||
|
WifiState wifi = scanWifi(WIFI_SSID);
|
||||||
|
|
||||||
|
// TODO: take other measurements (average them?)
|
||||||
|
/*
|
||||||
|
if(!updateLocation()) DEBUG_OUT.println("GPS timed out (location)");
|
||||||
|
if(!updateTime()) DEBUG_OUT.println("GPS timed out (time)");
|
||||||
|
*/
|
||||||
|
// TODO: write measurements to file
|
||||||
|
|
||||||
|
// TODO: connect to wifi, if available & not connected yet
|
||||||
|
// then upload local data, clean up
|
||||||
|
|
||||||
|
printState(wifi);
|
||||||
|
|
||||||
|
//delay(20000);
|
||||||
|
if (firstLoop) {
|
||||||
|
Measurement testMeasure;
|
||||||
|
testMeasure.lat = 51.2;
|
||||||
|
testMeasure.lng = 7.89;
|
||||||
|
testMeasure.value = 66.6;
|
||||||
|
strcpy(testMeasure.timeStamp, "2016-08-21T09:07:22Z");
|
||||||
|
strcpy(testMeasure.sensorID, "123457812345678123456781234567");
|
||||||
|
|
||||||
|
if (storage.add(testMeasure)) {
|
||||||
|
DEBUG_OUT.println("measurement stored! storage size: ");
|
||||||
|
} else {
|
||||||
|
DEBUG_OUT.println("measurement store failed! storage size: ");
|
||||||
|
}
|
||||||
|
DEBUG_OUT.println(storage.size());
|
||||||
|
firstLoop = false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,104 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <FS.h>
|
||||||
|
#include <ESP8266TrueRandom.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include "api.h"
|
||||||
|
|
||||||
|
#define MEASUREMENT_JSON_SIZE (JSON_OBJECT_SIZE(5))
|
||||||
|
|
||||||
|
struct Measurement {
|
||||||
|
char timeStamp[21];
|
||||||
|
float lat;
|
||||||
|
float lng;
|
||||||
|
float value;
|
||||||
|
char sensorID[32];
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: TEST THIS THING!!
|
||||||
|
class Storage {
|
||||||
|
protected:
|
||||||
|
// not needed, as we post the data as json string?
|
||||||
|
/*Measurement deserializeMeasurement(char* s) {
|
||||||
|
Measurement m;
|
||||||
|
StaticJsonBuffer<MEASUREMENT_JSON_SIZE> jsonBuffer;
|
||||||
|
JsonObject& root = jsonBuffer.parseObject(s);
|
||||||
|
m.timeStamp = (const char[sizeof Measurement.timeStamp])jsonBuffer.strdup(root["date"]);
|
||||||
|
m.lat = root["lat"];
|
||||||
|
m.lng = root["lng"];
|
||||||
|
m.value = root["value"];
|
||||||
|
m.sensorID = root["sensorId"];
|
||||||
|
return m;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
bool serializeMeasurement(Measurement& m, Print& f) {
|
||||||
|
StaticJsonBuffer<MEASUREMENT_JSON_SIZE> jsonBuffer;
|
||||||
|
JsonObject& root = jsonBuffer.createObject();
|
||||||
|
// TODO: fix data model
|
||||||
|
root["date"] = m.timeStamp;
|
||||||
|
root["lat"] = m.lat;
|
||||||
|
root["lng"] = m.lng;
|
||||||
|
root["value"] = m.value;
|
||||||
|
root["sensorId"] = m.sensorID;
|
||||||
|
root.printTo(f);
|
||||||
|
f.print("\n");
|
||||||
|
return root.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
Storage() {}
|
||||||
|
|
||||||
|
size_t begin() {
|
||||||
|
SPIFFS.begin();
|
||||||
|
FSInfo fs;
|
||||||
|
SPIFFS.info(fs);
|
||||||
|
return fs.totalBytes - fs.usedBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool add(Measurement& m, const char* directory = "/measurements/") {
|
||||||
|
byte uuid[16];
|
||||||
|
ESP8266TrueRandom.uuid(uuid);
|
||||||
|
// we need to shorten the uuid, as long filenames are not supported it seems..?
|
||||||
|
String fileName = directory + ESP8266TrueRandom.uuidToString(uuid).substring(26);
|
||||||
|
|
||||||
|
if (File f = SPIFFS.open(fileName, "w") ) {
|
||||||
|
bool success = serializeMeasurement(m, f);
|
||||||
|
f.close();
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String pop(const char* directory = "/measurements/") {
|
||||||
|
Dir dir = SPIFFS.openDir(directory);
|
||||||
|
String measurement = "";
|
||||||
|
if (!dir.next()) return measurement; // abort if storage is empty
|
||||||
|
String fileName = dir.fileName();
|
||||||
|
File f = dir.openFile("r");
|
||||||
|
measurement = f.readStringUntil('\n'); // assumes that the data does not contain any \n!
|
||||||
|
f.close();
|
||||||
|
SPIFFS.remove(fileName);
|
||||||
|
return measurement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*const Measurement pop(const char* directory = "/measurements/") {
|
||||||
|
Dir dir = SPIFFS.openDir(directory);
|
||||||
|
Measurement m;
|
||||||
|
if (!dir.next()) return m; // abort if storage is empty
|
||||||
|
String fileName = dir.fileName();
|
||||||
|
File f = dir.openFile("r");
|
||||||
|
m = deserializeMeasurement(f);
|
||||||
|
f.close();
|
||||||
|
SPIFFS.remove(fileName);
|
||||||
|
return m;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
uint16_t size(const char* directory = "/measurements/") {
|
||||||
|
Dir dir = SPIFFS.openDir(directory);
|
||||||
|
uint16_t i = 0;
|
||||||
|
while(dir.next()) i++;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,47 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
/* WIFI */
|
||||||
|
struct WifiState {
|
||||||
|
bool homeAvailable;
|
||||||
|
unsigned int numNetworks;
|
||||||
|
unsigned int numUnencrypted;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: filter duplicate SSIDs!
|
||||||
|
WifiState scanWifi(String homeSSID) {
|
||||||
|
WifiState state;
|
||||||
|
state.homeAvailable = false;
|
||||||
|
state.numNetworks = WiFi.scanNetworks(false, false);
|
||||||
|
state.numUnencrypted = 0;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < state.numNetworks; i++) {
|
||||||
|
if (WiFi.encryptionType(i) == ENC_TYPE_NONE)
|
||||||
|
++state.numUnencrypted;
|
||||||
|
|
||||||
|
if (WiFi.SSID(i) == homeSSID)
|
||||||
|
state.homeAvailable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool connectWifi(const char* ssid, const char* pass) {
|
||||||
|
static const unsigned int timeout = 10000; // abort after 10 secs
|
||||||
|
unsigned long start = millis();
|
||||||
|
|
||||||
|
WiFi.disconnect();
|
||||||
|
WiFi.begin(ssid, pass);
|
||||||
|
//telnet.print("Connecting to WiFi.");
|
||||||
|
while (WiFi.status() != WL_CONNECTED && millis() - start < timeout) {
|
||||||
|
delay(200);
|
||||||
|
//telnet.print(".");
|
||||||
|
}
|
||||||
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
|
//telnet.println("connected!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//telnet.println(" timeout");
|
||||||
|
return false;
|
||||||
|
}
|
Loading…
Reference in New Issue