"public/git@transfer.hft-stuttgart.de:zedflow/fgdi.git" did not exist on "2f9a4c16bc51c8903267ded6ac8d549842bb1209"
Commit daf08852 authored by Eric Duminil's avatar Eric Duminil
Browse files

Simple example with LED ring and CO2 sensor

parent da1f41c0
#######################################
# Datatypes (KEYWORD1)
#######################################
NTPClient KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
begin KEYWORD2
end KEYWORD2
update KEYWORD2
forceUpdate KEYWORD2
getDay KEYWORD2
getHours KEYWORD2
getMinutes KEYWORD2
getSeconds KEYWORD2
getFormattedTime KEYWORD2
getEpochTime KEYWORD2
{
"name": "NTPClient",
"keywords": "ntp, client, time",
"description": "A NTPClient to connect to a time server",
"authors":
[
{
"name": "Fabrice Weinberg",
"email": "fabrice@weinberg.me"
},
{
"name": "Sandeep Mistry",
"email": "s.mistry@arduino.cc"
}
],
"repository":
{
"type": "git",
"url": "https://github.com/arduino-libraries/NTPClient.git"
},
"version": "3.1.0",
"frameworks": "arduino",
"platforms": "espressif"
}
name=NTPClient
version=3.1.0
author=Fabrice Weinberg
maintainer=Fabrice Weinberg <fabrice@weinberg.me>
sentence=An NTPClient to connect to a time server
paragraph=Get time from a NTP server and keep it in sync.
category=Timing
url=https://github.com/arduino-libraries/NTPClient
architectures=*
2.8
* Add setBufferSize() to override MQTT_MAX_PACKET_SIZE
* Add setKeepAlive() to override MQTT_KEEPALIVE
* Add setSocketTimeout() to overide MQTT_SOCKET_TIMEOUT
* Added check to prevent subscribe/unsubscribe to empty topics
* Declare wifi mode prior to connect in ESP example
* Use `strnlen` to avoid overruns
* Support pre-connected Client objects
2.7
* Fix remaining-length handling to prevent buffer overrun
* Add large-payload API - beginPublish/write/publish/endPublish
* Add yield call to improve reliability on ESP
* Add Clean Session flag to connect options
* Add ESP32 support for functional callback signature
* Various other fixes
2.4
* Add MQTT_SOCKET_TIMEOUT to prevent it blocking indefinitely
whilst waiting for inbound data
* Fixed return code when publishing >256 bytes
2.3
* Add publish(topic,payload,retained) function
2.2
* Change code layout to match Arduino Library reqs
2.1
* Add MAX_TRANSFER_SIZE def to chunk messages if needed
* Reject topic/payloads that exceed MQTT_MAX_PACKET_SIZE
2.0
* Add (and default to) MQTT 3.1.1 support
* Fix PROGMEM handling for Intel Galileo/ESP8266
* Add overloaded constructors for convenience
* Add chainable setters for server/callback/client/stream
* Add state function to return connack return code
1.9
* Do not split MQTT packets over multiple calls to _client->write()
* API change: All constructors now require an instance of Client
to be passed in.
* Fixed example to match 1.8 api changes - dpslwk
* Added username/password support - WilHall
* Added publish_P - publishes messages from PROGMEM - jobytaffey
1.8
* KeepAlive interval is configurable in PubSubClient.h
* Maximum packet size is configurable in PubSubClient.h
* API change: Return boolean rather than int from various functions
* API change: Length parameter in message callback changed
from int to unsigned int
* Various internal tidy-ups around types
1.7
* Improved keepalive handling
* Updated to the Arduino-1.0 API
1.6
* Added the ability to publish a retained message
1.5
* Added default constructor
* Fixed compile error when used with arduino-0021 or later
1.4
* Fixed connection lost handling
1.3
* Fixed packet reading bug in PubSubClient.readPacket
1.2
* Fixed compile error when used with arduino-0016 or later
1.1
* Reduced size of library
* Added support for Will messages
* Clarified licensing - see LICENSE.txt
1.0
* Only Quality of Service (QOS) 0 messaging is supported
* The maximum message size, including header, is 128 bytes
* The keepalive interval is set to 30 seconds
* No support for Will messages
Copyright (c) 2008-2020 Nicholas O'Leary
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# Arduino Client for MQTT
This library provides a client for doing simple publish/subscribe messaging with
a server that supports MQTT.
## Examples
The library comes with a number of example sketches. See File > Examples > PubSubClient
within the Arduino application.
Full API documentation is available here: https://pubsubclient.knolleary.net
## Limitations
- It can only publish QoS 0 messages. It can subscribe at QoS 0 or QoS 1.
- The maximum message size, including header, is **256 bytes** by default. This
is configurable via `MQTT_MAX_PACKET_SIZE` in `PubSubClient.h` or can be changed
by calling `PubSubClient::setBufferSize(size)`.
- The keepalive interval is set to 15 seconds by default. This is configurable
via `MQTT_KEEPALIVE` in `PubSubClient.h` or can be changed by calling
`PubSubClient::setKeepAlive(keepAlive)`.
- The client uses MQTT 3.1.1 by default. It can be changed to use MQTT 3.1 by
changing value of `MQTT_VERSION` in `PubSubClient.h`.
## Compatible Hardware
The library uses the Arduino Ethernet Client api for interacting with the
underlying network hardware. This means it Just Works with a growing number of
boards and shields, including:
- Arduino Ethernet
- Arduino Ethernet Shield
- Arduino YUN – use the included `YunClient` in place of `EthernetClient`, and
be sure to do a `Bridge.begin()` first
- Arduino WiFi Shield - if you want to send packets > 90 bytes with this shield,
enable the `MQTT_MAX_TRANSFER_SIZE` define in `PubSubClient.h`.
- Sparkfun WiFly Shield – [library](https://github.com/dpslwk/WiFly)
- TI CC3000 WiFi - [library](https://github.com/sparkfun/SFE_CC3000_Library)
- Intel Galileo/Edison
- ESP8266
- ESP32
The library cannot currently be used with hardware based on the ENC28J60 chip –
such as the Nanode or the Nuelectronics Ethernet Shield. For those, there is an
[alternative library](https://github.com/njh/NanodeMQTT) available.
## License
This code is released under the MIT License.
#######################################
# Syntax Coloring Map For PubSubClient
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
PubSubClient KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
connect KEYWORD2
disconnect KEYWORD2
publish KEYWORD2
publish_P KEYWORD2
beginPublish KEYWORD2
endPublish KEYWORD2
write KEYWORD2
subscribe KEYWORD2
unsubscribe KEYWORD2
loop KEYWORD2
connected KEYWORD2
setServer KEYWORD2
setCallback KEYWORD2
setClient KEYWORD2
setStream KEYWORD2
setKeepAlive KEYWORD2
setBufferSize KEYWORD2
setSocketTimeout KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
{
"name": "PubSubClient",
"keywords": "ethernet, mqtt, m2m, iot",
"description": "A client library for MQTT messaging. MQTT is a lightweight messaging protocol ideal for small devices. This library allows you to send and receive MQTT messages. It supports the latest MQTT 3.1.1 protocol and can be configured to use the older MQTT 3.1 if needed. It supports all Arduino Ethernet Client compatible hardware, including the Intel Galileo/Edison, ESP8266 and TI CC3000.",
"repository": {
"type": "git",
"url": "https://github.com/knolleary/pubsubclient.git"
},
"version": "2.8",
"exclude": "tests",
"examples": "examples/*/*.ino",
"frameworks": "arduino",
"platforms": [
"atmelavr",
"espressif8266",
"espressif32"
]
}
name=PubSubClient
version=2.8
author=Nick O'Leary <nick.oleary@gmail.com>
maintainer=Nick O'Leary <nick.oleary@gmail.com>
sentence=A client library for MQTT messaging.
paragraph=MQTT is a lightweight messaging protocol ideal for small devices. This library allows you to send and receive MQTT messages. It supports the latest MQTT 3.1.1 protocol and can be configured to use the older MQTT 3.1 if needed. It supports all Arduino Ethernet Client compatible hardware, including the Intel Galileo/Edison, ESP8266 and TI CC3000.
category=Communication
url=http://pubsubclient.knolleary.net
architectures=*
/*
PubSubClient.h - A simple client for MQTT.
Nick O'Leary
http://knolleary.net
*/
#ifndef PubSubClient_h
#define PubSubClient_h
#include <Arduino.h>
#include "IPAddress.h"
#include "Client.h"
#include "Stream.h"
#define MQTT_VERSION_3_1 3
#define MQTT_VERSION_3_1_1 4
// MQTT_VERSION : Pick the version
//#define MQTT_VERSION MQTT_VERSION_3_1
#ifndef MQTT_VERSION
#define MQTT_VERSION MQTT_VERSION_3_1_1
#endif
// MQTT_MAX_PACKET_SIZE : Maximum packet size. Override with setBufferSize().
#ifndef MQTT_MAX_PACKET_SIZE
#define MQTT_MAX_PACKET_SIZE 256
#endif
// MQTT_KEEPALIVE : keepAlive interval in Seconds. Override with setKeepAlive()
#ifndef MQTT_KEEPALIVE
#define MQTT_KEEPALIVE 15
#endif
// MQTT_SOCKET_TIMEOUT: socket timeout interval in Seconds. Override with setSocketTimeout()
#ifndef MQTT_SOCKET_TIMEOUT
#define MQTT_SOCKET_TIMEOUT 15
#endif
// MQTT_MAX_TRANSFER_SIZE : limit how much data is passed to the network client
// in each write call. Needed for the Arduino Wifi Shield. Leave undefined to
// pass the entire MQTT packet in each write call.
//#define MQTT_MAX_TRANSFER_SIZE 80
// Possible values for client.state()
#define MQTT_CONNECTION_TIMEOUT -4
#define MQTT_CONNECTION_LOST -3
#define MQTT_CONNECT_FAILED -2
#define MQTT_DISCONNECTED -1
#define MQTT_CONNECTED 0
#define MQTT_CONNECT_BAD_PROTOCOL 1
#define MQTT_CONNECT_BAD_CLIENT_ID 2
#define MQTT_CONNECT_UNAVAILABLE 3
#define MQTT_CONNECT_BAD_CREDENTIALS 4
#define MQTT_CONNECT_UNAUTHORIZED 5
#define MQTTCONNECT 1 << 4 // Client request to connect to Server
#define MQTTCONNACK 2 << 4 // Connect Acknowledgment
#define MQTTPUBLISH 3 << 4 // Publish message
#define MQTTPUBACK 4 << 4 // Publish Acknowledgment
#define MQTTPUBREC 5 << 4 // Publish Received (assured delivery part 1)
#define MQTTPUBREL 6 << 4 // Publish Release (assured delivery part 2)
#define MQTTPUBCOMP 7 << 4 // Publish Complete (assured delivery part 3)
#define MQTTSUBSCRIBE 8 << 4 // Client Subscribe request
#define MQTTSUBACK 9 << 4 // Subscribe Acknowledgment
#define MQTTUNSUBSCRIBE 10 << 4 // Client Unsubscribe request
#define MQTTUNSUBACK 11 << 4 // Unsubscribe Acknowledgment
#define MQTTPINGREQ 12 << 4 // PING Request
#define MQTTPINGRESP 13 << 4 // PING Response
#define MQTTDISCONNECT 14 << 4 // Client is Disconnecting
#define MQTTReserved 15 << 4 // Reserved
#define MQTTQOS0 (0 << 1)
#define MQTTQOS1 (1 << 1)
#define MQTTQOS2 (2 << 1)
// Maximum size of fixed header and variable length size header
#define MQTT_MAX_HEADER_SIZE 5
#if defined(ESP8266) || defined(ESP32)
#include <functional>
#define MQTT_CALLBACK_SIGNATURE std::function<void(char*, uint8_t*, unsigned int)> callback
#else
#define MQTT_CALLBACK_SIGNATURE void (*callback)(char*, uint8_t*, unsigned int)
#endif
#define CHECK_STRING_LENGTH(l,s) if (l+2+strnlen(s, this->bufferSize) > this->bufferSize) {_client->stop();return false;}
class PubSubClient : public Print {
private:
Client* _client;
uint8_t* buffer;
uint16_t bufferSize;
uint16_t keepAlive;
uint16_t socketTimeout;
uint16_t nextMsgId;
unsigned long lastOutActivity;
unsigned long lastInActivity;
bool pingOutstanding;
MQTT_CALLBACK_SIGNATURE;
uint32_t readPacket(uint8_t*);
boolean readByte(uint8_t * result);
boolean readByte(uint8_t * result, uint16_t * index);
boolean write(uint8_t header, uint8_t* buf, uint16_t length);
uint16_t writeString(const char* string, uint8_t* buf, uint16_t pos);
// Build up the header ready to send
// Returns the size of the header
// Note: the header is built at the end of the first MQTT_MAX_HEADER_SIZE bytes, so will start
// (MQTT_MAX_HEADER_SIZE - <returned size>) bytes into the buffer
size_t buildHeader(uint8_t header, uint8_t* buf, uint16_t length);
IPAddress ip;
const char* domain;
uint16_t port;
Stream* stream;
int _state;
public:
PubSubClient();
PubSubClient(Client& client);
PubSubClient(IPAddress, uint16_t, Client& client);
PubSubClient(IPAddress, uint16_t, Client& client, Stream&);
PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client);
PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&);
PubSubClient(uint8_t *, uint16_t, Client& client);
PubSubClient(uint8_t *, uint16_t, Client& client, Stream&);
PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client);
PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&);
PubSubClient(const char*, uint16_t, Client& client);
PubSubClient(const char*, uint16_t, Client& client, Stream&);
PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client);
PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&);
~PubSubClient();
PubSubClient& setServer(IPAddress ip, uint16_t port);
PubSubClient& setServer(uint8_t * ip, uint16_t port);
PubSubClient& setServer(const char * domain, uint16_t port);
PubSubClient& setCallback(MQTT_CALLBACK_SIGNATURE);
PubSubClient& setClient(Client& client);
PubSubClient& setStream(Stream& stream);
PubSubClient& setKeepAlive(uint16_t keepAlive);
PubSubClient& setSocketTimeout(uint16_t timeout);
boolean setBufferSize(uint16_t size);
uint16_t getBufferSize();
boolean connect(const char* id);
boolean connect(const char* id, const char* user, const char* pass);
boolean connect(const char* id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage);
boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage);
boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession);
void disconnect();
boolean publish(const char* topic, const char* payload);
boolean publish(const char* topic, const char* payload, boolean retained);
boolean publish(const char* topic, const uint8_t * payload, unsigned int plength);
boolean publish(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained);
boolean publish_P(const char* topic, const char* payload, boolean retained);
boolean publish_P(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained);
// Start to publish a message.
// This API:
// beginPublish(...)
// one or more calls to write(...)
// endPublish()
// Allows for arbitrarily large payloads to be sent without them having to be copied into
// a new buffer and held in memory at one time
// Returns 1 if the message was started successfully, 0 if there was an error
boolean beginPublish(const char* topic, unsigned int plength, boolean retained);
// Finish off this publish message (started with beginPublish)
// Returns 1 if the packet was sent successfully, 0 if there was an error
int endPublish();
// Write a single byte of payload (only to be used with beginPublish/endPublish)
virtual size_t write(uint8_t);
// Write size bytes from buffer into the payload (only to be used with beginPublish/endPublish)
// Returns the number of bytes written
virtual size_t write(const uint8_t *buffer, size_t size);
boolean subscribe(const char* topic);
boolean subscribe(const char* topic, uint8_t qos);
boolean unsubscribe(const char* topic);
boolean loop();
boolean connected();
int state();
};
#endif
#include "util.h"
namespace config {
const char *ntp_server = NTP_SERVER;
const long utc_offset_in_seconds = UTC_OFFSET_IN_SECONDS; // UTC+1
}
#if defined(ESP8266)
const char *current_board = "ESP8266";
# if !defined(AMPEL_WIFI)
void preinit() {
// WiFi would be initialized otherwise (on ESP8266), even if unused.
// see https://github.com/esp8266/Arduino/issues/2111#issuecomment-224251391
ESP8266WiFiClass::preinitWiFiOff();
}
# endif
#elif defined(ESP32)
const char *current_board = "ESP32";
#else
const char *current_board = "UNKNOWN";
#endif
//NOTE: ESP32 sometimes couldn't access the NTP server, and every loop would take +1000ms
// ifdefs could be used to define functions specific to ESP32, e.g. with configTime
namespace ntp {
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, config::ntp_server, config::utc_offset_in_seconds, 60000UL);
bool connected_at_least_once = false;
void initialize() {
timeClient.begin();
}
void update() {
connected_at_least_once |= timeClient.update();
}
void getLocalTime(char *timestamp) {
timeClient.getFormattedDate(timestamp);
}
void setLocalTime(int32_t unix_seconds) {
char time[23];
timeClient.getFormattedDate(time);
Serial.print(F("Current time : "));
Serial.println(time);
if (connected_at_least_once) {
Serial.println(F("NTP update already happened. Not changing anything."));
return;
}
Serial.print(F("Setting UNIX time to : "));
Serial.println(unix_seconds);
timeClient.setEpochTime(unix_seconds - seconds());
timeClient.getFormattedDate(time);
Serial.print(F("Current time : "));
Serial.println(time);
}
}
void Ampel::showFreeSpace() {
Serial.print(F("Free heap space : "));
Serial.print(ESP.getFreeHeap());
Serial.println(F(" bytes."));
Serial.print(F("Max free block size : "));
Serial.print(esp_get_max_free_block_size());
Serial.println(F(" bytes."));
Serial.print(F("Heap fragmentation : "));
Serial.print(esp_get_heap_fragmentation());
Serial.println(F(" %"));
}
char sensorId[10]; // e.g "ESPxxxxxx\0"
char macAddress[18]; // e.g "XX:XX:XX:XX:XX:XX\0"
uint8_t mac[6];
char* getMacString() {
WiFi.macAddress(mac);
// Get all 6 bytes of ESP MAC
snprintf(macAddress, sizeof(macAddress), "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4],
mac[5]);
return macAddress;
}
char* getSensorId() {
WiFi.macAddress(mac);
// Get last 3 bytes of ESP MAC (worldwide unique)
snprintf(sensorId, sizeof(sensorId), "ESP%02x%02x%02x", mac[3], mac[4], mac[5]);
return sensorId;
}
Ampel::Ampel() :
board(current_board), sensorId(getSensorId()), macAddress(getMacString()), max_loop_duration(0) {
sensor_console::defineIntCommand("set_time", ntp::setLocalTime, F("1618829570 (Sets time to the given UNIX time)"));
sensor_console::defineCommand("free", Ampel::showFreeSpace, F("(Displays available heap space)"));
sensor_console::defineCommand("reset", []() {
ESP.restart();
}, F("(Restarts the ESP)"));
}
Ampel ampel;
#ifndef AMPEL_UTIL_H_INCLUDED
#define AMPEL_UTIL_H_INCLUDED
#include <Arduino.h>
#include "config.h"
#include "sensor_console.h"
#include <WiFiUdp.h> // required for NTP
#include "src/lib/NTPClient-master/NTPClient.h" // NTP
#if defined(ESP8266)
# include <ESP8266WiFi.h> // required to get MAC address
# define esp_get_max_free_block_size() ESP.getMaxFreeBlockSize()
# define esp_get_heap_fragmentation() ESP.getHeapFragmentation()
#elif defined(ESP32)
# include <WiFi.h> // required to get MAC address
# define esp_get_max_free_block_size() ESP.getMaxAllocHeap() //largest block of heap that can be allocated.
# define esp_get_heap_fragmentation() "?" // apparently not available for ESP32
#endif
namespace ntp {
void initialize();
void update();
void getLocalTime(char *timestamp);
}
namespace util {
template<typename Tpa, typename Tpb>
inline auto min(const Tpa &a, const Tpb &b) -> decltype(a < b ? a : b) {
return b < a ? b : a;
}
template<typename Tpa, typename Tpb>
inline auto max(const Tpa &a, const Tpb &b) -> decltype(b > a ? b : a) {
return b > a ? b : a;
}
}
class Ampel {
private:
static void showFreeSpace();
public:
const char *version = "v0.2.3-DEV"; // Update manually after significant changes.
const char *board;
const char *sensorId;
const char *macAddress;
uint32_t max_loop_duration;
Ampel();
};
extern Ampel ampel;
//NOTE: Only use seconds() for duration comparison, not timestamps comparison. Otherwise, problems happen when millis roll over.
#define seconds() (millis() / 1000UL)
#endif
#include "web_server.h"
namespace config {
// Values should be defined in config.h
#ifdef HTTP_USER
const char *http_user = HTTP_USER;
#else
const char *http_user = "";
#endif
#ifdef HTTP_PASSWORD
const char *http_password = HTTP_PASSWORD;
#else
const char *http_password = "";
#endif
}
namespace web_server {
const char *header_template;
const char *body_template;
const char *script_template;
void handleWebServerRoot();
void handlePageNotFound();
void handleWebServerCommand();
#ifdef AMPEL_CSV
void handleDeleteCSV();
void handleWebServerCSV();
#endif
#if defined(ESP8266)
ESP8266WebServer http(80); // Create a webserver object that listens for HTTP request on port 80
#elif defined(ESP32)
WebServer http(80);
#endif
void update() {
http.handleClient(); // Listen for HTTP requests from clients
}
void initialize() {
header_template =
PSTR("<!doctype html><html lang=en>"
"<head>\n"
"<title>%d ppm - CO2 SENSOR - %s - %s</title>\n"
"<meta charset='UTF-8'>\n"
// HfT Favicon
"<link rel='icon' type='image/png' sizes='16x16' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAHtJREFUOE9jvMnA+5+BAsBIFQMkl85h+P3kGcOb8jqwW+TPH2H4de0GA29UGNxtfx49YWCRk0HwHz5iuKegwwB2AS4DkA2F6VR6cAWsEQbgBqDY9vARw/ejJ+Au+LxsFcPz6BSwHpwGYPMCSS6gyAAKYhESiKMGjPgwAADopHVhn5ynEwAAAABJRU5ErkJggg=='/>\n"
// Responsive grid:
"<link rel='stylesheet' href='https://unpkg.com/purecss@2.0.3/build/pure-min.css'>\n"
"<link rel='stylesheet' href='https://unpkg.com/purecss@2.0.3/build/grids-responsive-min.css'>\n"
// JS Graphs:
"<script src='https://cdn.plot.ly/plotly-basic-1.58.2.min.js'></script>\n"
// Fullscreen
"<meta name='viewport' content='width=device-width, initial-scale=1'>\n"
// Refresh after every measurement.
// "<meta http-equiv='refresh' content='%d'>\n"
"</head>\n"
"<body>\n"
"<div class='pure-g'><div class='pure-u-1'><div class='pure-menu'><p class='pure-menu-heading'>HfT-Stuttgart CO<sub>2</sub> Ampel</p></div></div>\n"
"<div class='pure-u-1'><ul class='pure-menu pure-menu-horizontal pure-menu-list'>\n"
"<li class='pure-menu-item'><a href='#table' class='pure-menu-link'>Info</a></li>\n"
#ifdef AMPEL_CSV
"<li class='pure-menu-item'><a href='#graph' class='pure-menu-link'>Graph</a></li>\n"
"<li class='pure-menu-item'><a href='#log' class='pure-menu-link'>Log</a></li>\n"
"<li class='pure-menu-item'><a href='%s' class='pure-menu-link'>Download CSV</a></li>\n"
#endif
"<li class='pure-menu-item' id='led'>&#11044;</li>\n" // LED
"</ul></div></div>\n"
"<script>\n"
// Show a colored dot on the webpage, with a similar color than on LED Ring.
"hue=(1-(Math.min(Math.max(parseInt(document.title),500),1600)-500)/1100)*120;\n"
"document.getElementById('led').style.color=['hsl(',hue,',100%%,50%%)'].join('');\n"
"</script>\n"
"<div class='pure-g'>\n"
"<div class='pure-u-1' id='graph'></div>\n"// Graph placeholder
"</div>\n"
"<div class='pure-g'>\n"
"<table id='table' class='pure-table-striped pure-u-1 pure-u-md-1-2'>\n");
body_template =
PSTR("<tr><th colspan='2'>%s</th></tr>\n"
"<tr><td>CO<sub>2</sub> concentration</td><td>%5d ppm</td></tr>\n"
"<tr><td>Temperature</td><td>%.1f&#8451;</td></tr>\n"
"<tr><td>Humidity</td><td>%.1f%%</td></tr>\n"
"<tr><td>Last measurement</td><td>%s</td></tr>\n"
"<tr><td>Measurement timestep</td><td>%5d s</td></tr>\n"
#ifdef AMPEL_CSV
"<tr><th colspan='2'>CSV</th></tr>\n"
"<tr><td>Last write</td><td>%s</td></tr>\n"
"<tr><td>Timestep</td><td>%5d s</td></tr>\n"
"<tr><td>Available drive space</td><td>%d kB</td></tr>\n"
#endif
#ifdef AMPEL_MQTT
"<tr><th colspan='2'>MQTT</th></tr>\n"
"<tr><td>Connected?</td><td>%s</td></tr>\n"
"<tr><td>Last publish</td><td>%s</td></tr>\n"
"<tr><td>Timestep</td><td>%5d s</td></tr>\n"
#endif
#if defined(AMPEL_LORAWAN) && defined(ESP32)
"<tr><th colspan='2'>LoRaWAN</th></tr>\n"
"<tr><td>Connected?</td><td>%s</td></tr>\n"
"<tr><td>Frequency</td><td>%s MHz</td></tr>\n"
"<tr><td>Last transmission</td><td>%s</td></tr>\n"
"<tr><td>Timestep</td><td>%5d s</td></tr>\n"
#endif
"<tr><th colspan='2'>Sensor</th></tr>\n"
"<tr><td>Temperature offset</td><td>%.1fK</td></tr>\n" //TODO: Read it from sensor?
"<tr><td>Auto-calibration?</td><td>%s</td></tr>\n"
"<tr><td>Local address</td><td><a href='http://%s.local/'>%s.local</a></td></tr>\n"
"<tr><td>Local IP</td><td><a href='http://%s'>%s</a></td></tr>\n"
"<tr><td>MAC</td><td>%s</td></tr>\n"
"<tr><td>Free heap space</td><td>%6d bytes</td></tr>\n"
"<tr><td>Largest heap block</td><td>%6d bytes</td></tr>\n"
"<tr><td>Max loop duration</td><td>%5d ms</td></tr>\n"
"<tr><td>Board</td><td>%s</td></tr>\n"
"<tr><td>Ampel firmware</td><td>%s</td></tr>\n"
"<tr><td>Uptime</td><td>%2d d %4d h %02d min %02d s</td></tr>\n"
"</table>\n"
"<div id='log' class='pure-u-1 pure-u-md-1-2'></div>\n"
"<form action='/command'><input type='text' id='send' name='send'><input type='submit' value='Send'></form>\n"
#ifdef AMPEL_CSV
"<form action='/delete_csv' method='POST' onsubmit=\"return confirm('Are you really sure you want to delete all data?') && (document.body.style.cursor = 'wait');\">"
"<input type='submit' value='Delete CSV'/>"
"</form>\n"
#endif
"</div>\n");
script_template =
PSTR(
"<a href='https://transfer.hft-stuttgart.de/gitlab/co2ampel/ampel-firmware' target='_blank'>Source code</a>\n"
"<a href='https://transfer.hft-stuttgart.de/gitlab/co2ampel/ampel-documentation' target='_blank'>Documentation</a>\n"
#ifdef AMPEL_CSV
"<script>\n"
"document.body.style.cursor = 'default';\n"
"fetch('%s',{credentials:'include'})\n"
".then(response=>response.text())\n"
".then(csvText=>csvToTable(csvText))\n"
".then(htmlTable=>addLogTableToPage(htmlTable))\n"
".then(_=>Plotly.newPlot('graph',data,layout,{displaylogo:false}))\n"
".catch(e=>console.error(e));\n"
"xs=[];\n"
"data=[{x:xs,y:[],type:'scatter',name:'CO<sub>2</sub>',line:{color:'#2ca02c'}},\n"
"{x:xs,y:[],type:'scatter',name:'Temperature',yaxis:'y2',line:{color:'#ff7f0e',dash:'dot'}},\n"
"{x:xs,y:[],type:'scatter',name:'Humidity',yaxis:'y3',line:{color:'#1f77b4',dash:'dot'}}];\n"
"layout={height:600,title:'%s',legend:{xanchor:'right',x:0.2,y:1.0},\n"
"xaxis:{domain:[0.0,0.85]},yaxis:{ticksuffix:'ppm',range:[0,2000],dtick:200},\n"
"yaxis2:{overlaying:'y',side:'right',ticksuffix:'°C',position:0.9,anchor:'free',range:[0,30],dtick:3},\n"
"yaxis3:{overlaying:'y',side:'right',ticksuffix:'%%',position:0.95,anchor:'free',range:[0,100],dtick:10}\n"
"};\n"
"function csvToTable(csvText) {\n"
"csvText=csvText.trim();\n"
"lines=csvText.split('\\n');\n"
"table=document.createElement('table');\n"
"table.className='pure-table-striped';\n"
"n=lines.length;\n"
"lines.forEach((line,i)=>{\n"
"fields=line.split(';');\n"
"xs.push(fields[0]);\n"
"data[0]['y'].push(fields[1]);\n"
"data[1]['y'].push(fields[2]);\n"
"data[2]['y'].push(fields[3]);\n"
"if(i>4 && i<n-12){if(i==5){fields=['...','...','...','...']}else{return;}}\n"
"row=document.createElement('tr');\n"
"fields.forEach((field,index)=>{\n"
"cell=document.createElement(i<2?'th':'td');\n"
"cell.appendChild(document.createTextNode(field));\n"
"row.appendChild(cell);});\n"
"table.appendChild(row);});\n"
"return table;}\n"
"function addLogTableToPage(table){document.getElementById('log').appendChild(table);}\n"
"</script>\n"
#endif
"</body>\n"
"</html>");
// Web-server
http.on("/", handleWebServerRoot);
http.on("/command", handleWebServerCommand);
#ifdef AMPEL_CSV
http.on(csv_writer::filename, handleWebServerCSV); //NOTE: csv_writer should have been initialized first.
http.on("/delete_csv", HTTP_POST, handleDeleteCSV);
#endif
http.onNotFound(handlePageNotFound);
http.begin();
Serial.print(F("You can access this sensor via http://"));
Serial.print(ampel.sensorId);
Serial.print(F(".local (might be unstable) or http://"));
Serial.println(WiFi.localIP());
}
// Allow access if http_user or http_password are empty, or if provided credentials match
bool shouldBeAllowed() {
return strcmp(config::http_user, "") == 0 || strcmp(config::http_password, "") == 0
|| http.authenticate(config::http_user, config::http_password);
}
void handleWebServerRoot() {
if (!shouldBeAllowed()) {
return http.requestAuthentication(DIGEST_AUTH);
}
unsigned long ss = seconds();
uint8_t dd = ss / 86400;
ss -= dd * 86400;
unsigned int hh = ss / 3600;
ss -= hh * 3600;
uint8_t mm = ss / 60;
ss -= mm * 60;
//NOTE: Splitting in multiple parts in order to use less RAM
char content[2000]; // Update if needed
// INFO - Header size : 1767 - Body size : 1991 - Script size : 1909
snprintf_P(content, sizeof(content), header_template, sensor::co2, ampel.sensorId, wifi::local_ip
#ifdef AMPEL_CSV
, csv_writer::filename
#endif
);
// Serial.print(F("INFO - Header size : "));
// Serial.print(strlen(content));
http.setContentLength(CONTENT_LENGTH_UNKNOWN);
http.send_P(200, PSTR("text/html"), content);
// Body
snprintf_P(content, sizeof(content), body_template, ampel.sensorId, sensor::co2, sensor::temperature,
sensor::humidity, sensor::timestamp, config::measurement_timestep,
#ifdef AMPEL_CSV
csv_writer::last_successful_write, config::csv_interval, csv_writer::getAvailableSpace() / 1024,
#endif
#ifdef AMPEL_MQTT
mqtt::connected ? "Yes" : "No", mqtt::last_successful_publish, config::mqtt_sending_interval,
#endif
#if defined(AMPEL_LORAWAN) && defined(ESP32)
lorawan::connected ? "Yes" : "No", LMIC_FREQUENCY_PLAN, lorawan::last_transmission,
config::lorawan_sending_interval,
#endif
config::temperature_offset, config::auto_calibrate_sensor ? "Yes" : "No", ampel.sensorId, ampel.sensorId,
wifi::local_ip, wifi::local_ip, ampel.macAddress, ESP.getFreeHeap(), esp_get_max_free_block_size(),
ampel.max_loop_duration, ampel.board, ampel.version, dd, hh, mm, ss);
// Serial.print(F(" - Body size : "));
// Serial.print(strlen(content));
http.sendContent(content);
// Script
snprintf_P(content, sizeof(content), script_template
#ifdef AMPEL_CSV
, csv_writer::filename, ampel.sensorId
#endif
);
// Serial.print(F(" - Script size : "));
// Serial.println(strlen(content));
http.sendContent(content);
}
#ifdef AMPEL_CSV
void handleWebServerCSV() {
if (!shouldBeAllowed()) {
return http.requestAuthentication(DIGEST_AUTH);
}
if (FS_LIB.exists(csv_writer::filename)) {
fs::File csv_file = FS_LIB.open(csv_writer::filename, "r");
char csv_size[10];
snprintf(csv_size, sizeof(csv_size), "%d", csv_file.size());
http.sendHeader("Content-Length", csv_size);
http.streamFile(csv_file, F("text/csv"));
csv_file.close();
} else {
http.send(204, F("text/html"), F("No data available."));
}
}
void handleDeleteCSV() {
if (!shouldBeAllowed()) {
return http.requestAuthentication(DIGEST_AUTH);
}
Serial.print(F("Removing CSV file..."));
FS_LIB.remove(csv_writer::filename);
Serial.println(F(" Done!"));
http.sendHeader("Location", "/");
http.send(303);
}
#endif
void handleWebServerCommand() {
if (!shouldBeAllowed()) {
return http.requestAuthentication(DIGEST_AUTH);
}
http.sendHeader("Location", "/");
http.send(303);
sensor_console::execute(http.arg("send").c_str());
}
void handlePageNotFound() {
http.send(404, F("text/plain"), F("404: Not found"));
}
}
#ifndef WEB_SERVER_H_
#define WEB_SERVER_H_
#if defined(ESP8266)
# include <ESP8266WebServer.h>
#elif defined(ESP32)
# include <WebServer.h>
#endif
#include "config.h"
#include "util.h"
#include "wifi_util.h"
#include "co2_sensor.h"
#include "sensor_console.h"
#ifdef AMPEL_CSV
# include "csv_writer.h"
#endif
#ifdef AMPEL_MQTT
# include "mqtt.h"
#endif
#ifdef AMPEL_LORAWAN
# include "lorawan.h"
#endif
namespace web_server {
void initialize();
void update();
}
#endif
#include "wifi_util.h"
namespace config {
// WiFi config. See 'config.h' if you want to modify those values.
const char *wifi_ssid = WIFI_SSID;
const char *wifi_password = WIFI_PASSWORD;
#ifdef WIFI_TIMEOUT
const uint8_t wifi_timeout = WIFI_TIMEOUT; // [s] Will try to connect during wifi_timeout seconds before failing.
#else
const uint8_t wifi_timeout = 60; // [s] Will try to connect during wifi_timeout seconds before failing.
#endif
}
namespace wifi {
char local_ip[16]; // "255.255.255.255\0"
void scanNetworks() {
Serial.println();
Serial.println(F("WiFi - Scanning..."));
bool async = false;
bool showHidden = true;
int n = WiFi.scanNetworks(async, showHidden);
for (int i = 0; i < n; ++i) {
Serial.print(F(" * '"));
Serial.print(WiFi.SSID(i));
Serial.print(F("' ("));
int16_t quality = 2 * (100 + WiFi.RSSI(i));
Serial.print(util::min(util::max(quality, 0), 100));
Serial.println(F(" %)"));
}
Serial.println(F("Done!"));
Serial.println();
}
void showLocalIp() {
Serial.print(F("WiFi - Local IP : "));
Serial.println(wifi::local_ip);
Serial.print(F("WiFi - SSID : "));
Serial.println(WIFI_SSID);
}
// Initialize Wi-Fi
void connect(const char *hostname) {
sensor_console::defineCommand("wifi_scan", scanNetworks, F("(Scans available WiFi networks)"));
sensor_console::defineCommand("local_ip", showLocalIp, F("(Displays local IP and current SSID)"));
//NOTE: WiFi Multi could allow multiple SSID and passwords.
WiFi.persistent(false); // Don't write user & password to Flash.
WiFi.mode(WIFI_STA); // Set ESP to be a WiFi-client only
#if defined(ESP8266)
WiFi.hostname(hostname);
#elif defined(ESP32)
WiFi.setHostname(hostname);
#endif
Serial.print(F("WiFi - Connecting to "));
Serial.println(config::wifi_ssid);
WiFi.begin(config::wifi_ssid, config::wifi_password);
// Wait for connection, at most wifi_timeout seconds
for (int i = 0; i <= config::wifi_timeout && (WiFi.status() != WL_CONNECTED); i++) {
led_effects::showRainbowWheel();
Serial.print(".");
}
if (WiFi.status() == WL_CONNECTED) {
led_effects::showKITTWheel(color::green);
Serial.println();
Serial.print(F("WiFi - Connected! IP address: "));
IPAddress address = WiFi.localIP();
snprintf(local_ip, sizeof(local_ip), "%d.%d.%d.%d", address[0], address[1], address[2], address[3]);
Serial.println(local_ip);
} else {
//TODO: Allow sensor to work as an Access Point, in order to define SSID & password?
led_effects::showKITTWheel(color::red);
Serial.println(F("Connection to WiFi failed"));
}
}
}
#ifndef WIFI_UTIL_H_INCLUDED
#define WIFI_UTIL_H_INCLUDED
#include "config.h"
#include "util.h"
#include "led_effects.h"
namespace wifi {
extern char local_ip[];
void connect(const char *hostname);
}
#endif
...@@ -21,11 +21,3 @@ platform = espressif32 ...@@ -21,11 +21,3 @@ platform = espressif32
board = ttgo-lora32-v1 board = ttgo-lora32-v1
framework = arduino framework = arduino
monitor_speed = 115200 monitor_speed = 115200
lib_deps =
MCCI LoRaWAN LMIC library
build_flags =
-D ARDUINO_LMIC_PROJECT_CONFIG_H_SUPPRESS
-D CFG_eu868=1
-D CFG_sx1276_radio=1
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment