Commit 1f7a20c1 authored by Eric Duminil's avatar Eric Duminil
Browse files

Merge branch 'develop'

parents da1f41c0 34496cfe
...@@ -22,6 +22,12 @@ ...@@ -22,6 +22,12 @@
#include <Arduino.h> #include <Arduino.h>
#include "driver/rmt.h" #include "driver/rmt.h"
#if defined(ESP_IDF_VERSION)
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0)
#define HAS_ESP_IDF_4
#endif
#endif
// This code is adapted from the ESP-IDF v3.4 RMT "led_strip" example, altered // This code is adapted from the ESP-IDF v3.4 RMT "led_strip" example, altered
// to work with the Arduino version of the ESP-IDF (3.2) // to work with the Arduino version of the ESP-IDF (3.2)
...@@ -89,6 +95,7 @@ void espShow(uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) ...@@ -89,6 +95,7 @@ void espShow(uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz)
if (!rmt_reserved_channels[i]) { if (!rmt_reserved_channels[i]) {
rmt_reserved_channels[i] = true; rmt_reserved_channels[i] = true;
channel = i; channel = i;
break;
} }
} }
if (channel == ADAFRUIT_RMT_CHANNEL_MAX) { if (channel == ADAFRUIT_RMT_CHANNEL_MAX) {
...@@ -96,6 +103,10 @@ void espShow(uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) ...@@ -96,6 +103,10 @@ void espShow(uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz)
return; return;
} }
#if defined(HAS_ESP_IDF_4)
rmt_config_t config = RMT_DEFAULT_CONFIG_TX(pin, channel);
config.clk_div = 2;
#else
// Match default TX config from ESP-IDF version 3.4 // Match default TX config from ESP-IDF version 3.4
rmt_config_t config = { rmt_config_t config = {
.rmt_mode = RMT_MODE_TX, .rmt_mode = RMT_MODE_TX,
...@@ -113,12 +124,16 @@ void espShow(uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) ...@@ -113,12 +124,16 @@ void espShow(uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz)
.idle_output_en = true, .idle_output_en = true,
} }
}; };
#endif
rmt_config(&config); rmt_config(&config);
rmt_driver_install(config.channel, 0, 0); rmt_driver_install(config.channel, 0, 0);
// Convert NS timings to ticks // Convert NS timings to ticks
uint32_t counter_clk_hz = 0; uint32_t counter_clk_hz = 0;
#if defined(HAS_ESP_IDF_4)
rmt_get_counter_clock(channel, &counter_clk_hz);
#else
// this emulates the rmt_get_counter_clock() function from ESP-IDF 3.4 // this emulates the rmt_get_counter_clock() function from ESP-IDF 3.4
if (RMT_LL_HW_BASE->conf_ch[config.channel].conf1.ref_always_on == RMT_BASECLK_REF) { if (RMT_LL_HW_BASE->conf_ch[config.channel].conf1.ref_always_on == RMT_BASECLK_REF) {
uint32_t div_cnt = RMT_LL_HW_BASE->conf_ch[config.channel].conf0.div_cnt; uint32_t div_cnt = RMT_LL_HW_BASE->conf_ch[config.channel].conf0.div_cnt;
...@@ -129,6 +144,7 @@ void espShow(uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) ...@@ -129,6 +144,7 @@ void espShow(uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz)
uint32_t div = div_cnt == 0 ? 256 : div_cnt; uint32_t div = div_cnt == 0 ? 256 : div_cnt;
counter_clk_hz = APB_CLK_FREQ / (div); counter_clk_hz = APB_CLK_FREQ / (div);
} }
#endif
// NS to tick converter // NS to tick converter
float ratio = (float)counter_clk_hz / 1e9; float ratio = (float)counter_clk_hz / 1e9;
......
...@@ -17,16 +17,16 @@ static inline uint32_t _getCycleCount(void) { ...@@ -17,16 +17,16 @@ static inline uint32_t _getCycleCount(void) {
} }
#ifdef ESP8266 #ifdef ESP8266
void ICACHE_RAM_ATTR espShow( IRAM_ATTR void espShow(
uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) { uint8_t pin, uint8_t *pixels, uint32_t numBytes, __attribute__((unused)) boolean is800KHz) {
#else #else
void espShow( void espShow(
uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) { uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) {
#endif #endif
#define CYCLES_800_T0H (F_CPU / 2500000) // 0.4us #define CYCLES_800_T0H (F_CPU / 2500001) // 0.4us
#define CYCLES_800_T1H (F_CPU / 1250000) // 0.8us #define CYCLES_800_T1H (F_CPU / 1250001) // 0.8us
#define CYCLES_800 (F_CPU / 800000) // 1.25us per bit #define CYCLES_800 (F_CPU / 800001) // 1.25us per bit
#define CYCLES_400_T0H (F_CPU / 2000000) // 0.5uS #define CYCLES_400_T0H (F_CPU / 2000000) // 0.5uS
#define CYCLES_400_T1H (F_CPU / 833333) // 1.2us #define CYCLES_400_T1H (F_CPU / 833333) // 1.2us
#define CYCLES_400 (F_CPU / 400000) // 2.5us per bit #define CYCLES_400 (F_CPU / 400000) // 2.5us per bit
......
name=Adafruit NeoPixel name=Adafruit NeoPixel
version=1.7.0 version=1.10.4
author=Adafruit author=Adafruit
maintainer=Adafruit <info@adafruit.com> maintainer=Adafruit <info@adafruit.com>
sentence=Arduino library for controlling single-wire-based LED pixels and strip. sentence=Arduino library for controlling single-wire-based LED pixels and strip.
......
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
// Unless you know what you are doing...
// Lines 47 and 52 have been edited to set transmit bit count
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// ------ //
// ws2812 //
// ------ //
#define ws2812_wrap_target 0
#define ws2812_wrap 3
#define ws2812_T1 2
#define ws2812_T2 5
#define ws2812_T3 3
static const uint16_t ws2812_program_instructions[] = {
// .wrap_target
0x6221, // 0: out x, 1 side 0 [2]
0x1123, // 1: jmp !x, 3 side 1 [1]
0x1400, // 2: jmp 0 side 1 [4]
0xa442, // 3: nop side 0 [4]
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program ws2812_program = {
.instructions = ws2812_program_instructions,
.length = 4,
.origin = -1,
};
static inline pio_sm_config ws2812_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + ws2812_wrap_target, offset + ws2812_wrap);
sm_config_set_sideset(&c, 1, false, false);
return c;
}
#include "hardware/clocks.h"
static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin,
float freq, uint bits) {
pio_gpio_init(pio, pin);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
pio_sm_config c = ws2812_program_get_default_config(offset);
sm_config_set_sideset_pins(&c, pin);
sm_config_set_out_shift(&c, false, true,
bits); // <----<<< Length changed to "bits"
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
int cycles_per_bit = ws2812_T1 + ws2812_T2 + ws2812_T3;
float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
#endif
language: c
sudo: false
before_install:
- source <(curl -SLs https://raw.githubusercontent.com/adafruit/travis-ci-arduino/master/install.sh)
script:
- build_platform esp8266
notifications:
email:
on_success: change
on_failure: change
...@@ -25,7 +25,7 @@ NTPClient::NTPClient(UDP& udp) { ...@@ -25,7 +25,7 @@ NTPClient::NTPClient(UDP& udp) {
this->_udp = &udp; this->_udp = &udp;
} }
NTPClient::NTPClient(UDP& udp, int timeOffset) { NTPClient::NTPClient(UDP& udp, long timeOffset) {
this->_udp = &udp; this->_udp = &udp;
this->_timeOffset = timeOffset; this->_timeOffset = timeOffset;
} }
...@@ -35,24 +35,45 @@ NTPClient::NTPClient(UDP& udp, const char* poolServerName) { ...@@ -35,24 +35,45 @@ NTPClient::NTPClient(UDP& udp, const char* poolServerName) {
this->_poolServerName = poolServerName; this->_poolServerName = poolServerName;
} }
NTPClient::NTPClient(UDP& udp, const char* poolServerName, int timeOffset) { NTPClient::NTPClient(UDP& udp, IPAddress poolServerIP) {
this->_udp = &udp;
this->_poolServerIP = poolServerIP;
this->_poolServerName = NULL;
}
NTPClient::NTPClient(UDP& udp, const char* poolServerName, long timeOffset) {
this->_udp = &udp; this->_udp = &udp;
this->_timeOffset = timeOffset; this->_timeOffset = timeOffset;
this->_poolServerName = poolServerName; this->_poolServerName = poolServerName;
} }
NTPClient::NTPClient(UDP& udp, const char* poolServerName, int timeOffset, unsigned long updateInterval) { NTPClient::NTPClient(UDP& udp, IPAddress poolServerIP, long timeOffset){
this->_udp = &udp;
this->_timeOffset = timeOffset;
this->_poolServerIP = poolServerIP;
this->_poolServerName = NULL;
}
NTPClient::NTPClient(UDP& udp, const char* poolServerName, long timeOffset, unsigned long updateInterval) {
this->_udp = &udp; this->_udp = &udp;
this->_timeOffset = timeOffset; this->_timeOffset = timeOffset;
this->_poolServerName = poolServerName; this->_poolServerName = poolServerName;
this->_updateInterval = updateInterval; this->_updateInterval = updateInterval;
} }
NTPClient::NTPClient(UDP& udp, IPAddress poolServerIP, long timeOffset, unsigned long updateInterval) {
this->_udp = &udp;
this->_timeOffset = timeOffset;
this->_poolServerIP = poolServerIP;
this->_poolServerName = NULL;
this->_updateInterval = updateInterval;
}
void NTPClient::begin() { void NTPClient::begin() {
this->begin(NTP_DEFAULT_LOCAL_PORT); this->begin(NTP_DEFAULT_LOCAL_PORT);
} }
void NTPClient::begin(int port) { void NTPClient::begin(unsigned int port) {
this->_port = port; this->_port = port;
this->_udp->begin(this->_port); this->_udp->begin(this->_port);
...@@ -60,37 +81,15 @@ void NTPClient::begin(int port) { ...@@ -60,37 +81,15 @@ void NTPClient::begin(int port) {
this->_udpSetup = true; this->_udpSetup = true;
} }
bool NTPClient::isValid(byte * ntpPacket)
{
//Perform a few validity checks on the packet
if((ntpPacket[0] & 0b11000000) == 0b11000000) //Check for LI=UNSYNC
return false;
if((ntpPacket[0] & 0b00111000) >> 3 < 0b100) //Check for Version >= 4
return false;
if((ntpPacket[0] & 0b00000111) != 0b100) //Check for Mode == Server
return false;
if((ntpPacket[1] < 1) || (ntpPacket[1] > 15)) //Check for valid Stratum
return false;
if( ntpPacket[16] == 0 && ntpPacket[17] == 0 &&
ntpPacket[18] == 0 && ntpPacket[19] == 0 &&
ntpPacket[20] == 0 && ntpPacket[21] == 0 &&
ntpPacket[22] == 0 && ntpPacket[22] == 0) //Check for ReferenceTimestamp != 0
return false;
return true;
}
bool NTPClient::forceUpdate() { bool NTPClient::forceUpdate() {
#ifdef DEBUG_NTPClient #ifdef DEBUG_NTPClient
Serial.println("Update from NTP Server"); Serial.println("Update from NTP Server");
#endif #endif
// flush any existing packets // flush any existing packets
while(this->_udp->parsePacket() != 0) while(this->_udp->parsePacket() != 0)
this->_udp->flush(); this->_udp->flush();
this->sendNTPPacket(); this->sendNTPPacket();
// Wait till data is there or timeout... // Wait till data is there or timeout...
...@@ -99,20 +98,14 @@ bool NTPClient::forceUpdate() { ...@@ -99,20 +98,14 @@ bool NTPClient::forceUpdate() {
do { do {
delay ( 10 ); delay ( 10 );
cb = this->_udp->parsePacket(); cb = this->_udp->parsePacket();
if(cb > 0)
{
this->_udp->read(this->_packetBuffer, NTP_PACKET_SIZE);
if(!this->isValid(this->_packetBuffer))
cb = 0;
}
if (timeout > 100) return false; // timeout after 1000 ms if (timeout > 100) return false; // timeout after 1000 ms
timeout++; timeout++;
} while (cb == 0); } while (cb == 0);
this->_lastUpdate = millis() - (10 * (timeout + 1)); // Account for delay in reading the time this->_lastUpdate = millis() - (10 * (timeout + 1)); // Account for delay in reading the time
this->_udp->read(this->_packetBuffer, NTP_PACKET_SIZE);
unsigned long highWord = word(this->_packetBuffer[40], this->_packetBuffer[41]); unsigned long highWord = word(this->_packetBuffer[40], this->_packetBuffer[41]);
unsigned long lowWord = word(this->_packetBuffer[42], this->_packetBuffer[43]); unsigned long lowWord = word(this->_packetBuffer[42], this->_packetBuffer[43]);
// combine the four bytes (two words) into a long integer // combine the four bytes (two words) into a long integer
...@@ -121,73 +114,53 @@ bool NTPClient::forceUpdate() { ...@@ -121,73 +114,53 @@ bool NTPClient::forceUpdate() {
this->_currentEpoc = secsSince1900 - SEVENZYYEARS; this->_currentEpoc = secsSince1900 - SEVENZYYEARS;
return true; return true; // return true after successful update
} }
bool NTPClient::update() { bool NTPClient::update() {
if ((millis() - this->_lastUpdate >= this->_updateInterval) // Update after _updateInterval if ((millis() - this->_lastUpdate >= this->_updateInterval) // Update after _updateInterval
|| this->_lastUpdate == 0) { // Update if there was no update yet. || this->_lastUpdate == 0) { // Update if there was no update yet.
if (!this->_udpSetup) this->begin(); // setup the UDP client if needed if (!this->_udpSetup || this->_port != NTP_DEFAULT_LOCAL_PORT) this->begin(this->_port); // setup the UDP client if needed
return this->forceUpdate(); return this->forceUpdate();
} }
return true; return false; // return false if update does not occur
}
bool NTPClient::isTimeSet() const {
return (this->_lastUpdate != 0); // returns true if the time has been set, else false
} }
unsigned long NTPClient::getEpochTime() { unsigned long NTPClient::getEpochTime() const {
return this->_timeOffset + // User offset return this->_timeOffset + // User offset
this->_currentEpoc + // Epoc returned by the NTP server this->_currentEpoc + // Epoch returned by the NTP server
((millis() - this->_lastUpdate) / 1000); // Time since last update ((millis() - this->_lastUpdate) / 1000); // Time since last update
} }
int NTPClient::getDay() { int NTPClient::getDay() const {
return (((this->getEpochTime() / 86400L) + 4 ) % 7); //0 is Sunday return (((this->getEpochTime() / 86400L) + 4 ) % 7); //0 is Sunday
} }
int NTPClient::getHours() { int NTPClient::getHours() const {
return ((this->getEpochTime() % 86400L) / 3600); return ((this->getEpochTime() % 86400L) / 3600);
} }
int NTPClient::getMinutes() { int NTPClient::getMinutes() const {
return ((this->getEpochTime() % 3600) / 60); return ((this->getEpochTime() % 3600) / 60);
} }
int NTPClient::getSeconds() { int NTPClient::getSeconds() const {
return (this->getEpochTime() % 60); return (this->getEpochTime() % 60);
} }
void NTPClient::getFormattedTime(char *formatted_time, unsigned long secs) { String NTPClient::getFormattedTime() const {
unsigned long rawTime = secs ? secs : this->getEpochTime(); unsigned long rawTime = this->getEpochTime();
unsigned int hours = (rawTime % 86400L) / 3600; unsigned long hours = (rawTime % 86400L) / 3600;
unsigned int minutes = (rawTime % 3600) / 60; String hoursStr = hours < 10 ? "0" + String(hours) : String(hours);
unsigned int seconds = rawTime % 60;
snprintf(formatted_time, 9, "%02d:%02d:%02d", hours, minutes, seconds);
}
// Based on https://github.com/PaulStoffregen/Time/blob/master/Time.cpp unsigned long minutes = (rawTime % 3600) / 60;
void NTPClient::getFormattedDate(char *formatted_date, unsigned long secs) { String minuteStr = minutes < 10 ? "0" + String(minutes) : String(minutes);
unsigned long rawTime = (secs ? secs : this->getEpochTime()) / 86400L; // in days
unsigned long days = 0, year = 1970;
uint8_t month;
static const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31};
while((days += (LEAP_YEAR(year) ? 366 : 365)) <= rawTime) unsigned long seconds = rawTime % 60;
year++; String secondStr = seconds < 10 ? "0" + String(seconds) : String(seconds);
rawTime -= days - (LEAP_YEAR(year) ? 366 : 365); // now it is days in this year, starting at 0
days=0;
for (month=0; month<12; month++) {
uint8_t monthLength;
if (month==1) { // february
monthLength = LEAP_YEAR(year) ? 29 : 28;
} else {
monthLength = monthDays[month];
}
if (rawTime < monthLength) break;
rawTime -= monthLength;
}
month++; // jan is month 1
rawTime++; // first day is day 1
char formatted_time[9]; return hoursStr + ":" + minuteStr + ":" + secondStr;
this->getFormattedTime(formatted_time, secs);
snprintf(formatted_date, 23, "%4lu-%02d-%02lu %s%+03d", year, month, rawTime, formatted_time, this->_timeOffset / 3600);
} }
void NTPClient::end() { void NTPClient::end() {
...@@ -204,28 +177,84 @@ void NTPClient::setUpdateInterval(unsigned long updateInterval) { ...@@ -204,28 +177,84 @@ void NTPClient::setUpdateInterval(unsigned long updateInterval) {
this->_updateInterval = updateInterval; this->_updateInterval = updateInterval;
} }
void NTPClient::setPoolServerName(const char* poolServerName) {
this->_poolServerName = poolServerName;
}
void NTPClient::sendNTPPacket() { void NTPClient::sendNTPPacket() {
// set all bytes in the buffer to 0 // set all bytes in the buffer to 0
memset(this->_packetBuffer, 0, NTP_PACKET_SIZE); memset(this->_packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request // Initialize values needed to form NTP request
// (see URL above for details on the packets)
this->_packetBuffer[0] = 0b11100011; // LI, Version, Mode this->_packetBuffer[0] = 0b11100011; // LI, Version, Mode
this->_packetBuffer[1] = 0; // Stratum, or type of clock this->_packetBuffer[1] = 0; // Stratum, or type of clock
this->_packetBuffer[2] = 6; // Polling Interval this->_packetBuffer[2] = 6; // Polling Interval
this->_packetBuffer[3] = 0xEC; // Peer Clock Precision this->_packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion // 8 bytes of zero for Root Delay & Root Dispersion
this->_packetBuffer[12] = 0x49; this->_packetBuffer[12] = 49;
this->_packetBuffer[13] = 0x4E; this->_packetBuffer[13] = 0x4E;
this->_packetBuffer[14] = 0x49; this->_packetBuffer[14] = 49;
this->_packetBuffer[15] = 0x52; this->_packetBuffer[15] = 52;
// all NTP fields have been given values, now // all NTP fields have been given values, now
// you can send a packet requesting a timestamp: // you can send a packet requesting a timestamp:
this->_udp->beginPacket(this->_poolServerName, 123); //NTP requests are to port 123 if (this->_poolServerName) {
this->_udp->beginPacket(this->_poolServerName, 123);
} else {
this->_udp->beginPacket(this->_poolServerIP, 123);
}
this->_udp->write(this->_packetBuffer, NTP_PACKET_SIZE); this->_udp->write(this->_packetBuffer, NTP_PACKET_SIZE);
this->_udp->endPacket(); this->_udp->endPacket();
} }
void NTPClient::setRandomPort(unsigned int minValue, unsigned int maxValue) {
randomSeed(analogRead(0));
this->_port = random(minValue, maxValue);
}
/*** Custom code for ampel-firmware ***/
void NTPClient::getFormattedTime(char *formatted_time, unsigned long secs) {
unsigned long rawTime = secs ? secs : this->getEpochTime();
unsigned int hours = (rawTime % 86400L) / 3600;
unsigned int minutes = (rawTime % 3600) / 60;
unsigned int seconds = rawTime % 60;
snprintf(formatted_time, 9, "%02d:%02d:%02d", hours, minutes, seconds);
}
#define LEAP_YEAR(Y) ( (Y>0) && !(Y%4) && ( (Y%100) || !(Y%400) ) )
// Based on https://github.com/PaulStoffregen/Time/blob/master/Time.cpp
void NTPClient::getFormattedDate(char *formatted_date, unsigned long secs) {
unsigned long rawTime = (secs ? secs : this->getEpochTime()) / 86400L; // in days
unsigned long days = 0;
unsigned int year = 1970;
uint8_t month;
static const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31};
while((days += (LEAP_YEAR(year) ? 366 : 365)) <= rawTime)
year++;
rawTime -= days - (LEAP_YEAR(year) ? 366 : 365); // now it is days in this year, starting at 0
days=0;
for (month=0; month<12; month++) {
uint8_t monthLength;
if (month==1) { // february
monthLength = LEAP_YEAR(year) ? 29 : 28;
} else {
monthLength = monthDays[month];
}
if (rawTime < monthLength) break;
rawTime -= monthLength;
}
month++; // jan is month 1
rawTime++; // first day is day 1
char formatted_time[9];
this->getFormattedTime(formatted_time, secs);
snprintf(formatted_date, 23, "%4d-%02d-%02lu %s%+03ld", year, month, rawTime, formatted_time, (this->_timeOffset / 3600) % 100);
}
void NTPClient::setEpochTime(unsigned long secs) { void NTPClient::setEpochTime(unsigned long secs) {
this->_currentEpoc = secs; this->_currentEpoc = secs;
} }
/**************************************************************/
\ No newline at end of file
...@@ -7,8 +7,6 @@ ...@@ -7,8 +7,6 @@
#define SEVENZYYEARS 2208988800UL #define SEVENZYYEARS 2208988800UL
#define NTP_PACKET_SIZE 48 #define NTP_PACKET_SIZE 48
#define NTP_DEFAULT_LOCAL_PORT 1337 #define NTP_DEFAULT_LOCAL_PORT 1337
#define LEAP_YEAR(Y) ( (Y>0) && !(Y%4) && ( (Y%100) || !(Y%400) ) )
class NTPClient { class NTPClient {
private: private:
...@@ -16,8 +14,9 @@ class NTPClient { ...@@ -16,8 +14,9 @@ class NTPClient {
bool _udpSetup = false; bool _udpSetup = false;
const char* _poolServerName = "pool.ntp.org"; // Default time server const char* _poolServerName = "pool.ntp.org"; // Default time server
int _port = NTP_DEFAULT_LOCAL_PORT; IPAddress _poolServerIP;
int _timeOffset = 0; unsigned int _port = NTP_DEFAULT_LOCAL_PORT;
long _timeOffset = 0;
unsigned long _updateInterval = 60000; // In ms unsigned long _updateInterval = 60000; // In ms
...@@ -27,14 +26,28 @@ class NTPClient { ...@@ -27,14 +26,28 @@ class NTPClient {
byte _packetBuffer[NTP_PACKET_SIZE]; byte _packetBuffer[NTP_PACKET_SIZE];
void sendNTPPacket(); void sendNTPPacket();
bool isValid(byte * ntpPacket);
public: public:
NTPClient(UDP& udp); NTPClient(UDP& udp);
NTPClient(UDP& udp, int timeOffset); NTPClient(UDP& udp, long timeOffset);
NTPClient(UDP& udp, const char* poolServerName); NTPClient(UDP& udp, const char* poolServerName);
NTPClient(UDP& udp, const char* poolServerName, int timeOffset); NTPClient(UDP& udp, const char* poolServerName, long timeOffset);
NTPClient(UDP& udp, const char* poolServerName, int timeOffset, unsigned long updateInterval); NTPClient(UDP& udp, const char* poolServerName, long timeOffset, unsigned long updateInterval);
NTPClient(UDP& udp, IPAddress poolServerIP);
NTPClient(UDP& udp, IPAddress poolServerIP, long timeOffset);
NTPClient(UDP& udp, IPAddress poolServerIP, long timeOffset, unsigned long updateInterval);
/**
* Set time server name
*
* @param poolServerName
*/
void setPoolServerName(const char* poolServerName);
/**
* Set random local port
*/
void setRandomPort(unsigned int minValue = 49152, unsigned int maxValue = 65535);
/** /**
* Starts the underlying UDP client with the default local port * Starts the underlying UDP client with the default local port
...@@ -44,7 +57,7 @@ class NTPClient { ...@@ -44,7 +57,7 @@ class NTPClient {
/** /**
* Starts the underlying UDP client with the specified local port * Starts the underlying UDP client with the specified local port
*/ */
void begin(int port); void begin(unsigned int port);
/** /**
* This should be called in the main loop of your application. By default an update from the NTP Server is only * This should be called in the main loop of your application. By default an update from the NTP Server is only
...@@ -61,10 +74,17 @@ class NTPClient { ...@@ -61,10 +74,17 @@ class NTPClient {
*/ */
bool forceUpdate(); bool forceUpdate();
int getDay(); /**
int getHours(); * This allows to check if the NTPClient successfully received a NTP packet and set the time.
int getMinutes(); *
int getSeconds(); * @return true if time has been set, else false
*/
bool isTimeSet() const;
int getDay() const;
int getHours() const;
int getMinutes() const;
int getSeconds() const;
/** /**
* Changes the time offset. Useful for changing timezones dynamically * Changes the time offset. Useful for changing timezones dynamically
...@@ -78,28 +98,37 @@ class NTPClient { ...@@ -78,28 +98,37 @@ class NTPClient {
void setUpdateInterval(unsigned long updateInterval); void setUpdateInterval(unsigned long updateInterval);
/** /**
* @return secs argument (or 0 for current time) formatted like `hh:mm:ss` * @return time formatted like `hh:mm:ss`
*/ */
void getFormattedTime(char *formatted_time, unsigned long secs = 0); String getFormattedTime() const;
/** /**
* @return time in seconds since Jan. 1, 1970 * @return time in seconds since Jan. 1, 1970
*/ */
unsigned long getEpochTime(); unsigned long getEpochTime() const;
/**
* @return secs argument (or 0 for current date) formatted to ISO 8601
* like `2004-02-12T15:19:21+00:00`
*/
void getFormattedDate(char *formatted_date, unsigned long secs = 0);
/** /**
* Stops the underlying UDP client * Stops the underlying UDP client
*/ */
void end(); void end();
/*** Custom code for ampel-firmware ***/
/**
* @return secs argument (or 0 for current time) formatted like `hh:mm:ss`
*/
void getFormattedTime(char *formatted_time, unsigned long secs = 0);
/**
* @return secs argument (or 0 for current date) formatted to ISO 8601
* like `2004-02-12T15:19:21+00:00`
*/
void getFormattedDate(char *formatted_date, unsigned long secs = 0);
/** /**
* Replace the NTP-fetched time with seconds since Jan. 1, 1970 * Replace the NTP-fetched time with seconds since Jan. 1, 1970
*/ */
void setEpochTime(unsigned long secs); void setEpochTime(unsigned long secs);
/**************************************************************/
}; };
# NTPClient # NTPClient
[![Build Status](https://travis-ci.org/arduino-libraries/NTPClient.svg?branch=master)](https://travis-ci.org/arduino-libraries/NTPClient) [![Check Arduino status](https://github.com/arduino-libraries/NTPClient/actions/workflows/check-arduino.yml/badge.svg)](https://github.com/arduino-libraries/NTPClient/actions/workflows/check-arduino.yml)
[![Compile Examples status](https://github.com/arduino-libraries/NTPClient/actions/workflows/compile-examples.yml/badge.svg)](https://github.com/arduino-libraries/NTPClient/actions/workflows/compile-examples.yml)
[![Spell Check status](https://github.com/arduino-libraries/NTPClient/actions/workflows/spell-check.yml/badge.svg)](https://github.com/arduino-libraries/NTPClient/actions/workflows/spell-check.yml)
Connect to a NTP server, here is how: Connect to a NTP server, here is how:
...@@ -22,7 +24,7 @@ WiFiUDP ntpUDP; ...@@ -22,7 +24,7 @@ WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP); NTPClient timeClient(ntpUDP);
// You can specify the time server pool and the offset, (in seconds) // You can specify the time server pool and the offset, (in seconds)
// additionaly you can specify the update interval (in milliseconds). // additionally you can specify the update interval (in milliseconds).
// NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 3600, 60000); // NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 3600, 60000);
void setup(){ void setup(){
...@@ -45,3 +47,6 @@ void loop() { ...@@ -45,3 +47,6 @@ void loop() {
delay(1000); delay(1000);
} }
``` ```
## Function documentation
`getEpochTime` returns the Unix epoch, which are the seconds elapsed since 00:00:00 UTC on 1 January 1970 (leap seconds are ignored, every day is treated as having 86400 seconds). **Attention**: If you have set a time offset this time offset will be added to your epoch timestamp.
...@@ -12,9 +12,13 @@ begin KEYWORD2 ...@@ -12,9 +12,13 @@ begin KEYWORD2
end KEYWORD2 end KEYWORD2
update KEYWORD2 update KEYWORD2
forceUpdate KEYWORD2 forceUpdate KEYWORD2
isTimeSet KEYWORD2
getDay KEYWORD2 getDay KEYWORD2
getHours KEYWORD2 getHours KEYWORD2
getMinutes KEYWORD2 getMinutes KEYWORD2
getSeconds KEYWORD2 getSeconds KEYWORD2
getFormattedTime KEYWORD2 getFormattedTime KEYWORD2
getEpochTime KEYWORD2 getEpochTime KEYWORD2
setTimeOffset KEYWORD2
setUpdateInterval KEYWORD2
setPoolServerName KEYWORD2
name=NTPClient name=NTPClient
version=3.1.0 version=3.2.0
author=Fabrice Weinberg author=Fabrice Weinberg
maintainer=Fabrice Weinberg <fabrice@weinberg.me> maintainer=Fabrice Weinberg <fabrice@weinberg.me>
sentence=An NTPClient to connect to a time server sentence=An NTPClient to connect to a time server
......
/* /*
PubSubClient.cpp - A simple client for MQTT. PubSubClient.cpp - A simple client for MQTT.
Nick O'Leary Nick O'Leary
http://knolleary.net http://knolleary.net
*/ */
#include "PubSubClient.h" #include "PubSubClient.h"
#include "Arduino.h" #include "Arduino.h"
PubSubClient::PubSubClient() { PubSubClient::PubSubClient() {
this->_state = MQTT_DISCONNECTED; this->_state = MQTT_DISCONNECTED;
this->_client = NULL; this->_client = NULL;
this->stream = NULL; this->stream = NULL;
setCallback(NULL); setCallback(NULL);
this->bufferSize = 0; this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE); setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE); setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT); setSocketTimeout(MQTT_SOCKET_TIMEOUT);
} }
PubSubClient::PubSubClient(Client& client) { PubSubClient::PubSubClient(Client &client) {
this->_state = MQTT_DISCONNECTED; this->_state = MQTT_DISCONNECTED;
setClient(client); setClient(client);
this->stream = NULL; this->stream = NULL;
this->bufferSize = 0; this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE); setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE); setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT); setSocketTimeout(MQTT_SOCKET_TIMEOUT);
} }
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client) { PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client &client) {
this->_state = MQTT_DISCONNECTED; this->_state = MQTT_DISCONNECTED;
setServer(addr, port); setServer(addr, port);
setClient(client); setClient(client);
this->stream = NULL; this->stream = NULL;
this->bufferSize = 0; this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE); setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE); setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT); setSocketTimeout(MQTT_SOCKET_TIMEOUT);
} }
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client, Stream& stream) { PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client &client, Stream &stream) {
this->_state = MQTT_DISCONNECTED; this->_state = MQTT_DISCONNECTED;
setServer(addr,port); setServer(addr, port);
setClient(client); setClient(client);
setStream(stream); setStream(stream);
this->bufferSize = 0; this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE); setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE); setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT); setSocketTimeout(MQTT_SOCKET_TIMEOUT);
} }
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client &client) {
this->_state = MQTT_DISCONNECTED; this->_state = MQTT_DISCONNECTED;
setServer(addr, port); setServer(addr, port);
setCallback(callback); setCallback(callback);
setClient(client); setClient(client);
this->stream = NULL; this->stream = NULL;
this->bufferSize = 0; this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE); setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE); setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT); setSocketTimeout(MQTT_SOCKET_TIMEOUT);
} }
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client &client, Stream &stream) {
this->_state = MQTT_DISCONNECTED; this->_state = MQTT_DISCONNECTED;
setServer(addr,port); setServer(addr, port);
setCallback(callback); setCallback(callback);
setClient(client); setClient(client);
setStream(stream); setStream(stream);
this->bufferSize = 0; this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE); setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE); setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT); setSocketTimeout(MQTT_SOCKET_TIMEOUT);
} }
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client) { PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client &client) {
this->_state = MQTT_DISCONNECTED; this->_state = MQTT_DISCONNECTED;
setServer(ip, port); setServer(ip, port);
setClient(client); setClient(client);
this->stream = NULL; this->stream = NULL;
this->bufferSize = 0; this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE); setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE); setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT); setSocketTimeout(MQTT_SOCKET_TIMEOUT);
} }
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client, Stream& stream) { PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client &client, Stream &stream) {
this->_state = MQTT_DISCONNECTED; this->_state = MQTT_DISCONNECTED;
setServer(ip,port); setServer(ip, port);
setClient(client); setClient(client);
setStream(stream); setStream(stream);
this->bufferSize = 0; this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE); setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE); setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT); setSocketTimeout(MQTT_SOCKET_TIMEOUT);
} }
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client &client) {
this->_state = MQTT_DISCONNECTED; this->_state = MQTT_DISCONNECTED;
setServer(ip, port); setServer(ip, port);
setCallback(callback); setCallback(callback);
setClient(client); setClient(client);
this->stream = NULL; this->stream = NULL;
this->bufferSize = 0; this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE); setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE); setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT); setSocketTimeout(MQTT_SOCKET_TIMEOUT);
} }
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client &client, Stream &stream) {
this->_state = MQTT_DISCONNECTED; this->_state = MQTT_DISCONNECTED;
setServer(ip,port); setServer(ip, port);
setCallback(callback); setCallback(callback);
setClient(client); setClient(client);
setStream(stream); setStream(stream);
this->bufferSize = 0; this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE); setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE); setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT); setSocketTimeout(MQTT_SOCKET_TIMEOUT);
} }
PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client) { PubSubClient::PubSubClient(const char *domain, uint16_t port, Client &client) {
this->_state = MQTT_DISCONNECTED; this->_state = MQTT_DISCONNECTED;
setServer(domain,port); setServer(domain, port);
setClient(client); setClient(client);
this->stream = NULL; this->stream = NULL;
this->bufferSize = 0; this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE); setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE); setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT); setSocketTimeout(MQTT_SOCKET_TIMEOUT);
} }
PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client, Stream& stream) { PubSubClient::PubSubClient(const char *domain, uint16_t port, Client &client, Stream &stream) {
this->_state = MQTT_DISCONNECTED; this->_state = MQTT_DISCONNECTED;
setServer(domain,port); setServer(domain, port);
setClient(client); setClient(client);
setStream(stream); setStream(stream);
this->bufferSize = 0; this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE); setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE); setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT); setSocketTimeout(MQTT_SOCKET_TIMEOUT);
} }
PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { PubSubClient::PubSubClient(const char *domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client &client) {
this->_state = MQTT_DISCONNECTED; this->_state = MQTT_DISCONNECTED;
setServer(domain,port); setServer(domain, port);
setCallback(callback); setCallback(callback);
setClient(client); setClient(client);
this->stream = NULL; this->stream = NULL;
this->bufferSize = 0; this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE); setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE); setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT); setSocketTimeout(MQTT_SOCKET_TIMEOUT);
} }
PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { PubSubClient::PubSubClient(const char *domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client &client, Stream &stream) {
this->_state = MQTT_DISCONNECTED; this->_state = MQTT_DISCONNECTED;
setServer(domain,port); setServer(domain, port);
setCallback(callback); setCallback(callback);
setClient(client); setClient(client);
setStream(stream); setStream(stream);
this->bufferSize = 0; this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE); setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE); setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT); setSocketTimeout(MQTT_SOCKET_TIMEOUT);
} }
PubSubClient::~PubSubClient() { PubSubClient::~PubSubClient() {
...@@ -163,424 +163,431 @@ PubSubClient::~PubSubClient() { ...@@ -163,424 +163,431 @@ PubSubClient::~PubSubClient() {
} }
boolean PubSubClient::connect(const char *id) { boolean PubSubClient::connect(const char *id) {
return connect(id,NULL,NULL,0,0,0,0,1); return connect(id, NULL, NULL, 0, 0, 0, 0, 1);
} }
boolean PubSubClient::connect(const char *id, const char *user, const char *pass) { boolean PubSubClient::connect(const char *id, const char *user, const char *pass) {
return connect(id,user,pass,0,0,0,0,1); return connect(id, user, pass, 0, 0, 0, 0, 1);
} }
boolean PubSubClient::connect(const char *id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) { boolean PubSubClient::connect(const char *id, const char *willTopic, uint8_t willQos, boolean willRetain,
return connect(id,NULL,NULL,willTopic,willQos,willRetain,willMessage,1); const char *willMessage) {
return connect(id, NULL, NULL, willTopic, willQos, willRetain, willMessage, 1);
} }
boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) { boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char *willTopic,
return connect(id,user,pass,willTopic,willQos,willRetain,willMessage,1); uint8_t willQos, boolean willRetain, const char *willMessage) {
return connect(id, user, pass, willTopic, willQos, willRetain, willMessage, 1);
} }
boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession) { boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char *willTopic,
if (!connected()) { uint8_t willQos, boolean willRetain, const char *willMessage, boolean cleanSession) {
int result = 0; if (!connected()) {
int result = 0;
if (_client->connected()) {
result = 1;
} else {
if (domain != NULL) {
result = _client->connect(this->domain, this->port);
} else {
result = _client->connect(this->ip, this->port);
}
}
if(_client->connected()) { if (result == 1) {
result = 1; nextMsgId = 1;
} else { // Leave room in the buffer for header and variable length field
if (domain != NULL) { uint16_t length = MQTT_MAX_HEADER_SIZE;
result = _client->connect(this->domain, this->port); unsigned int j;
} else {
result = _client->connect(this->ip, this->port);
}
}
if (result == 1) {
nextMsgId = 1;
// Leave room in the buffer for header and variable length field
uint16_t length = MQTT_MAX_HEADER_SIZE;
unsigned int j;
#if MQTT_VERSION == MQTT_VERSION_3_1 #if MQTT_VERSION == MQTT_VERSION_3_1
uint8_t d[9] = {0x00,0x06,'M','Q','I','s','d','p', MQTT_VERSION}; uint8_t d[9] = {0x00,0x06,'M','Q','I','s','d','p', MQTT_VERSION};
#define MQTT_HEADER_VERSION_LENGTH 9 #define MQTT_HEADER_VERSION_LENGTH 9
#elif MQTT_VERSION == MQTT_VERSION_3_1_1 #elif MQTT_VERSION == MQTT_VERSION_3_1_1
uint8_t d[7] = {0x00,0x04,'M','Q','T','T',MQTT_VERSION}; uint8_t d[7] = { 0x00, 0x04, 'M', 'Q', 'T', 'T', MQTT_VERSION };
#define MQTT_HEADER_VERSION_LENGTH 7 #define MQTT_HEADER_VERSION_LENGTH 7
#endif #endif
for (j = 0;j<MQTT_HEADER_VERSION_LENGTH;j++) { for (j = 0; j < MQTT_HEADER_VERSION_LENGTH; j++) {
this->buffer[length++] = d[j]; this->buffer[length++] = d[j];
} }
uint8_t v; uint8_t v;
if (willTopic) { if (willTopic) {
v = 0x04|(willQos<<3)|(willRetain<<5); v = 0x04 | (willQos << 3) | (willRetain << 5);
} else { } else {
v = 0x00; v = 0x00;
} }
if (cleanSession) { if (cleanSession) {
v = v|0x02; v = v | 0x02;
} }
if(user != NULL) { if (user != NULL) {
v = v|0x80; v = v | 0x80;
if(pass != NULL) { if (pass != NULL) {
v = v|(0x80>>1); v = v | (0x80 >> 1);
} }
} }
this->buffer[length++] = v; this->buffer[length++] = v;
this->buffer[length++] = ((this->keepAlive) >> 8); this->buffer[length++] = ((this->keepAlive) >> 8);
this->buffer[length++] = ((this->keepAlive) & 0xFF); this->buffer[length++] = ((this->keepAlive) & 0xFF);
CHECK_STRING_LENGTH(length,id) CHECK_STRING_LENGTH(length, id)
length = writeString(id,this->buffer,length); length = writeString(id, this->buffer, length);
if (willTopic) { if (willTopic) {
CHECK_STRING_LENGTH(length,willTopic) CHECK_STRING_LENGTH(length, willTopic)
length = writeString(willTopic,this->buffer,length); length = writeString(willTopic, this->buffer, length);
CHECK_STRING_LENGTH(length,willMessage) CHECK_STRING_LENGTH(length, willMessage)
length = writeString(willMessage,this->buffer,length); length = writeString(willMessage, this->buffer, length);
} }
if(user != NULL) { if (user != NULL) {
CHECK_STRING_LENGTH(length,user) CHECK_STRING_LENGTH(length, user)
length = writeString(user,this->buffer,length); length = writeString(user, this->buffer, length);
if(pass != NULL) { if (pass != NULL) {
CHECK_STRING_LENGTH(length,pass) CHECK_STRING_LENGTH(length, pass)
length = writeString(pass,this->buffer,length); length = writeString(pass, this->buffer, length);
} }
} }
write(MQTTCONNECT,this->buffer,length-MQTT_MAX_HEADER_SIZE); write(MQTTCONNECT, this->buffer, length - MQTT_MAX_HEADER_SIZE);
lastInActivity = lastOutActivity = millis(); lastInActivity = lastOutActivity = millis();
while (!_client->available()) { while (!_client->available()) {
unsigned long t = millis(); unsigned long t = millis();
if (t-lastInActivity >= ((int32_t) this->socketTimeout*1000UL)) { if (t - lastInActivity >= ((int32_t) this->socketTimeout * 1000UL)) {
_state = MQTT_CONNECTION_TIMEOUT; _state = MQTT_CONNECTION_TIMEOUT;
_client->stop(); _client->stop();
return false; return false;
} }
} }
uint8_t llen; uint8_t llen;
uint32_t len = readPacket(&llen); uint32_t len = readPacket(&llen);
if (len == 4) { if (len == 4) {
if (buffer[3] == 0) { if (buffer[3] == 0) {
lastInActivity = millis(); lastInActivity = millis();
pingOutstanding = false; pingOutstanding = false;
_state = MQTT_CONNECTED; _state = MQTT_CONNECTED;
return true; return true;
} else {
_state = buffer[3];
}
}
_client->stop();
} else { } else {
_state = MQTT_CONNECT_FAILED; _state = buffer[3];
} }
return false; }
_client->stop();
} else {
_state = MQTT_CONNECT_FAILED;
} }
return true; return false;
}
return true;
} }
// reads a byte into result // reads a byte into result
boolean PubSubClient::readByte(uint8_t * result) { boolean PubSubClient::readByte(uint8_t *result) {
uint32_t previousMillis = millis(); uint32_t previousMillis = millis();
while(!_client->available()) { while (!_client->available()) {
yield(); yield();
uint32_t currentMillis = millis(); uint32_t currentMillis = millis();
if(currentMillis - previousMillis >= ((int32_t) this->socketTimeout * 1000)){ if (currentMillis - previousMillis >= ((int32_t) this->socketTimeout * 1000)) {
return false; return false;
} }
} }
*result = _client->read(); *result = _client->read();
return true; return true;
} }
// reads a byte into result[*index] and increments index // reads a byte into result[*index] and increments index
boolean PubSubClient::readByte(uint8_t * result, uint16_t * index){ boolean PubSubClient::readByte(uint8_t *result, uint16_t *index) {
uint16_t current_index = *index; uint16_t current_index = *index;
uint8_t * write_address = &(result[current_index]); uint8_t *write_address = &(result[current_index]);
if(readByte(write_address)){ if (readByte(write_address)) {
*index = current_index + 1; *index = current_index + 1;
return true; return true;
} }
return false; return false;
} }
uint32_t PubSubClient::readPacket(uint8_t* lengthLength) { uint32_t PubSubClient::readPacket(uint8_t *lengthLength) {
uint16_t len = 0; uint16_t len = 0;
if(!readByte(this->buffer, &len)) return 0; if (!readByte(this->buffer, &len))
bool isPublish = (this->buffer[0]&0xF0) == MQTTPUBLISH; return 0;
uint32_t multiplier = 1; bool isPublish = (this->buffer[0] & 0xF0) == MQTTPUBLISH;
uint32_t length = 0; uint32_t multiplier = 1;
uint8_t digit = 0; uint32_t length = 0;
uint16_t skip = 0; uint8_t digit = 0;
uint32_t start = 0; uint16_t skip = 0;
uint32_t start = 0;
do {
if (len == 5) { do {
// Invalid remaining length encoding - kill the connection if (len == 5) {
_state = MQTT_DISCONNECTED; // Invalid remaining length encoding - kill the connection
_client->stop(); _state = MQTT_DISCONNECTED;
return 0; _client->stop();
} return 0;
if(!readByte(&digit)) return 0;
this->buffer[len++] = digit;
length += (digit & 127) * multiplier;
multiplier <<=7; //multiplier *= 128
} while ((digit & 128) != 0);
*lengthLength = len-1;
if (isPublish) {
// Read in topic length to calculate bytes to skip over for Stream writing
if(!readByte(this->buffer, &len)) return 0;
if(!readByte(this->buffer, &len)) return 0;
skip = (this->buffer[*lengthLength+1]<<8)+this->buffer[*lengthLength+2];
start = 2;
if (this->buffer[0]&MQTTQOS1) {
// skip message id
skip += 2;
}
} }
uint32_t idx = len; if (!readByte(&digit))
return 0;
for (uint32_t i = start;i<length;i++) { this->buffer[len++] = digit;
if(!readByte(&digit)) return 0; length += (digit & 127) * multiplier;
if (this->stream) { multiplier <<= 7; //multiplier *= 128
if (isPublish && idx-*lengthLength-2>skip) { } while ((digit & 128) != 0);
this->stream->write(digit); *lengthLength = len - 1;
}
} if (isPublish) {
// Read in topic length to calculate bytes to skip over for Stream writing
if (len < this->bufferSize) { if (!readByte(this->buffer, &len))
this->buffer[len] = digit; return 0;
len++; if (!readByte(this->buffer, &len))
} return 0;
idx++; skip = (this->buffer[*lengthLength + 1] << 8) + this->buffer[*lengthLength + 2];
start = 2;
if (this->buffer[0] & MQTTQOS1) {
// skip message id
skip += 2;
}
}
uint32_t idx = len;
for (uint32_t i = start; i < length; i++) {
if (!readByte(&digit))
return 0;
if (this->stream) {
if (isPublish && idx - *lengthLength - 2 > skip) {
this->stream->write(digit);
}
} }
if (!this->stream && idx > this->bufferSize) { if (len < this->bufferSize) {
len = 0; // This will cause the packet to be ignored. this->buffer[len] = digit;
len++;
} }
return len; idx++;
}
if (!this->stream && idx > this->bufferSize) {
len = 0; // This will cause the packet to be ignored.
}
return len;
} }
boolean PubSubClient::loop() { boolean PubSubClient::loop() {
if (connected()) { if (connected()) {
unsigned long t = millis(); unsigned long t = millis();
if ((t - lastInActivity > this->keepAlive*1000UL) || (t - lastOutActivity > this->keepAlive*1000UL)) { if ((t - lastInActivity > this->keepAlive * 1000UL) || (t - lastOutActivity > this->keepAlive * 1000UL)) {
if (pingOutstanding) { if (pingOutstanding) {
this->_state = MQTT_CONNECTION_TIMEOUT; this->_state = MQTT_CONNECTION_TIMEOUT;
_client->stop(); _client->stop();
return false; return false;
} else {
this->buffer[0] = MQTTPINGREQ;
this->buffer[1] = 0;
_client->write(this->buffer, 2);
lastOutActivity = t;
lastInActivity = t;
pingOutstanding = true;
}
}
if (_client->available()) {
uint8_t llen;
uint16_t len = readPacket(&llen);
uint16_t msgId = 0;
uint8_t *payload;
if (len > 0) {
lastInActivity = t;
uint8_t type = this->buffer[0] & 0xF0;
if (type == MQTTPUBLISH) {
if (callback) {
uint16_t tl = (this->buffer[llen + 1] << 8) + this->buffer[llen + 2]; /* topic length in bytes */
memmove(this->buffer + llen + 2, this->buffer + llen + 3, tl); /* move topic inside buffer 1 byte to front */
this->buffer[llen + 2 + tl] = 0; /* end the topic as a 'C' string with \x00 */
char *topic = (char*) this->buffer + llen + 2;
// msgId only present for QOS>0
if ((this->buffer[0] & 0x06) == MQTTQOS1) {
msgId = (this->buffer[llen + 3 + tl] << 8) + this->buffer[llen + 3 + tl + 1];
payload = this->buffer + llen + 3 + tl + 2;
callback(topic, payload, len - llen - 3 - tl - 2);
this->buffer[0] = MQTTPUBACK;
this->buffer[1] = 2;
this->buffer[2] = (msgId >> 8);
this->buffer[3] = (msgId & 0xFF);
_client->write(this->buffer, 4);
lastOutActivity = t;
} else { } else {
this->buffer[0] = MQTTPINGREQ; payload = this->buffer + llen + 3 + tl;
this->buffer[1] = 0; callback(topic, payload, len - llen - 3 - tl);
_client->write(this->buffer,2);
lastOutActivity = t;
lastInActivity = t;
pingOutstanding = true;
}
}
if (_client->available()) {
uint8_t llen;
uint16_t len = readPacket(&llen);
uint16_t msgId = 0;
uint8_t *payload;
if (len > 0) {
lastInActivity = t;
uint8_t type = this->buffer[0]&0xF0;
if (type == MQTTPUBLISH) {
if (callback) {
uint16_t tl = (this->buffer[llen+1]<<8)+this->buffer[llen+2]; /* topic length in bytes */
memmove(this->buffer+llen+2,this->buffer+llen+3,tl); /* move topic inside buffer 1 byte to front */
this->buffer[llen+2+tl] = 0; /* end the topic as a 'C' string with \x00 */
char *topic = (char*) this->buffer+llen+2;
// msgId only present for QOS>0
if ((this->buffer[0]&0x06) == MQTTQOS1) {
msgId = (this->buffer[llen+3+tl]<<8)+this->buffer[llen+3+tl+1];
payload = this->buffer+llen+3+tl+2;
callback(topic,payload,len-llen-3-tl-2);
this->buffer[0] = MQTTPUBACK;
this->buffer[1] = 2;
this->buffer[2] = (msgId >> 8);
this->buffer[3] = (msgId & 0xFF);
_client->write(this->buffer,4);
lastOutActivity = t;
} else {
payload = this->buffer+llen+3+tl;
callback(topic,payload,len-llen-3-tl);
}
}
} else if (type == MQTTPINGREQ) {
this->buffer[0] = MQTTPINGRESP;
this->buffer[1] = 0;
_client->write(this->buffer,2);
} else if (type == MQTTPINGRESP) {
pingOutstanding = false;
}
} else if (!connected()) {
// readPacket has closed the connection
return false;
} }
}
} else if (type == MQTTPINGREQ) {
this->buffer[0] = MQTTPINGRESP;
this->buffer[1] = 0;
_client->write(this->buffer, 2);
} else if (type == MQTTPINGRESP) {
pingOutstanding = false;
} }
return true; } else if (!connected()) {
// readPacket has closed the connection
return false;
}
} }
return false; return true;
}
return false;
} }
boolean PubSubClient::publish(const char* topic, const char* payload) { boolean PubSubClient::publish(const char *topic, const char *payload) {
return publish(topic,(const uint8_t*)payload, payload ? strnlen(payload, this->bufferSize) : 0,false); return publish(topic, (const uint8_t*) payload, payload ? strnlen(payload, this->bufferSize) : 0, false);
} }
boolean PubSubClient::publish(const char* topic, const char* payload, boolean retained) { boolean PubSubClient::publish(const char *topic, const char *payload, boolean retained) {
return publish(topic,(const uint8_t*)payload, payload ? strnlen(payload, this->bufferSize) : 0,retained); return publish(topic, (const uint8_t*) payload, payload ? strnlen(payload, this->bufferSize) : 0, retained);
} }
boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength) { boolean PubSubClient::publish(const char *topic, const uint8_t *payload, unsigned int plength) {
return publish(topic, payload, plength, false); return publish(topic, payload, plength, false);
} }
boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) { boolean PubSubClient::publish(const char *topic, const uint8_t *payload, unsigned int plength, boolean retained) {
if (connected()) { if (connected()) {
if (this->bufferSize < MQTT_MAX_HEADER_SIZE + 2+strnlen(topic, this->bufferSize) + plength) { if (this->bufferSize < MQTT_MAX_HEADER_SIZE + 2 + strnlen(topic, this->bufferSize) + plength) {
// Too long // Too long
return false; return false;
} }
// Leave room in the buffer for header and variable length field // Leave room in the buffer for header and variable length field
uint16_t length = MQTT_MAX_HEADER_SIZE; uint16_t length = MQTT_MAX_HEADER_SIZE;
length = writeString(topic,this->buffer,length); length = writeString(topic, this->buffer, length);
// Add payload // Add payload
uint16_t i; uint16_t i;
for (i=0;i<plength;i++) { for (i = 0; i < plength; i++) {
this->buffer[length++] = payload[i]; this->buffer[length++] = payload[i];
} }
// Write the header // Write the header
uint8_t header = MQTTPUBLISH; uint8_t header = MQTTPUBLISH;
if (retained) { if (retained) {
header |= 1; header |= 1;
}
return write(header,this->buffer,length-MQTT_MAX_HEADER_SIZE);
} }
return false; return write(header, this->buffer, length - MQTT_MAX_HEADER_SIZE);
}
return false;
} }
boolean PubSubClient::publish_P(const char* topic, const char* payload, boolean retained) { boolean PubSubClient::publish_P(const char *topic, const char *payload, boolean retained) {
return publish_P(topic, (const uint8_t*)payload, payload ? strnlen(payload, this->bufferSize) : 0, retained); return publish_P(topic, (const uint8_t*) payload, payload ? strnlen(payload, this->bufferSize) : 0, retained);
} }
boolean PubSubClient::publish_P(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) { boolean PubSubClient::publish_P(const char *topic, const uint8_t *payload, unsigned int plength, boolean retained) {
uint8_t llen = 0; uint8_t llen = 0;
uint8_t digit; uint8_t digit;
unsigned int rc = 0; unsigned int rc = 0;
uint16_t tlen; uint16_t tlen;
unsigned int pos = 0; unsigned int pos = 0;
unsigned int i; unsigned int i;
uint8_t header; uint8_t header;
unsigned int len; unsigned int len;
int expectedLength; uint32_t expectedLength;
if (!connected()) { if (!connected()) {
return false; return false;
} }
tlen = strnlen(topic, this->bufferSize); tlen = strnlen(topic, this->bufferSize);
header = MQTTPUBLISH; header = MQTTPUBLISH;
if (retained) { if (retained) {
header |= 1; header |= 1;
}
this->buffer[pos++] = header;
len = plength + 2 + tlen;
do {
digit = len & 127; //digit = len %128
len >>= 7; //len = len / 128
if (len > 0) {
digit |= 0x80;
} }
this->buffer[pos++] = header; this->buffer[pos++] = digit;
len = plength + 2 + tlen; llen++;
do { } while (len > 0);
digit = len & 127; //digit = len %128
len >>= 7; //len = len / 128
if (len > 0) {
digit |= 0x80;
}
this->buffer[pos++] = digit;
llen++;
} while(len>0);
pos = writeString(topic,this->buffer,pos); pos = writeString(topic, this->buffer, pos);
rc += _client->write(this->buffer,pos); rc += _client->write(this->buffer, pos);
for (i=0;i<plength;i++) { for (i = 0; i < plength; i++) {
rc += _client->write((char)pgm_read_byte_near(payload + i)); rc += _client->write((char) pgm_read_byte_near(payload + i));
} }
lastOutActivity = millis(); lastOutActivity = millis();
expectedLength = 1 + llen + 2 + tlen + plength; expectedLength = 1 + llen + 2 + tlen + plength;
return (rc == expectedLength); return (rc == expectedLength);
} }
boolean PubSubClient::beginPublish(const char* topic, unsigned int plength, boolean retained) { boolean PubSubClient::beginPublish(const char *topic, unsigned int plength, boolean retained) {
if (connected()) { if (connected()) {
// Send the header and variable length field // Send the header and variable length field
uint16_t length = MQTT_MAX_HEADER_SIZE; uint16_t length = MQTT_MAX_HEADER_SIZE;
length = writeString(topic,this->buffer,length); length = writeString(topic, this->buffer, length);
uint8_t header = MQTTPUBLISH; uint8_t header = MQTTPUBLISH;
if (retained) { if (retained) {
header |= 1; header |= 1;
}
size_t hlen = buildHeader(header, this->buffer, plength+length-MQTT_MAX_HEADER_SIZE);
uint16_t rc = _client->write(this->buffer+(MQTT_MAX_HEADER_SIZE-hlen),length-(MQTT_MAX_HEADER_SIZE-hlen));
lastOutActivity = millis();
return (rc == (length-(MQTT_MAX_HEADER_SIZE-hlen)));
} }
return false; size_t hlen = buildHeader(header, this->buffer, plength + length - MQTT_MAX_HEADER_SIZE);
uint16_t rc = _client->write(this->buffer + (MQTT_MAX_HEADER_SIZE - hlen), length - (MQTT_MAX_HEADER_SIZE - hlen));
lastOutActivity = millis();
return (rc == (length - (MQTT_MAX_HEADER_SIZE - hlen)));
}
return false;
} }
int PubSubClient::endPublish() { int PubSubClient::endPublish() {
return 1; return 1;
} }
size_t PubSubClient::write(uint8_t data) { size_t PubSubClient::write(uint8_t data) {
lastOutActivity = millis(); lastOutActivity = millis();
return _client->write(data); return _client->write(data);
} }
size_t PubSubClient::write(const uint8_t *buffer, size_t size) { size_t PubSubClient::write(const uint8_t *buffer, size_t size) {
lastOutActivity = millis(); lastOutActivity = millis();
return _client->write(buffer,size); return _client->write(buffer, size);
} }
size_t PubSubClient::buildHeader(uint8_t header, uint8_t* buf, uint16_t length) { size_t PubSubClient::buildHeader(uint8_t header, uint8_t *buf, uint16_t length) {
uint8_t lenBuf[4]; uint8_t lenBuf[4];
uint8_t llen = 0; uint8_t llen = 0;
uint8_t digit; uint8_t digit;
uint8_t pos = 0; uint8_t pos = 0;
uint16_t len = length; uint16_t len = length;
do { do {
digit = len & 127; //digit = len %128 digit = len & 127; //digit = len %128
len >>= 7; //len = len / 128 len >>= 7; //len = len / 128
if (len > 0) { if (len > 0) {
digit |= 0x80; digit |= 0x80;
}
lenBuf[pos++] = digit;
llen++;
} while(len>0);
buf[4-llen] = header;
for (int i=0;i<llen;i++) {
buf[MQTT_MAX_HEADER_SIZE-llen+i] = lenBuf[i];
} }
return llen+1; // Full header size is variable length bit plus the 1-byte fixed header lenBuf[pos++] = digit;
llen++;
} while (len > 0);
buf[4 - llen] = header;
for (int i = 0; i < llen; i++) {
buf[MQTT_MAX_HEADER_SIZE - llen + i] = lenBuf[i];
}
return llen + 1; // Full header size is variable length bit plus the 1-byte fixed header
} }
boolean PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length) { boolean PubSubClient::write(uint8_t header, uint8_t *buf, uint16_t length) {
uint16_t rc; uint16_t rc;
uint8_t hlen = buildHeader(header, buf, length); uint8_t hlen = buildHeader(header, buf, length);
#ifdef MQTT_MAX_TRANSFER_SIZE #ifdef MQTT_MAX_TRANSFER_SIZE
uint8_t* writeBuf = buf+(MQTT_MAX_HEADER_SIZE-hlen); uint8_t* writeBuf = buf+(MQTT_MAX_HEADER_SIZE-hlen);
...@@ -596,174 +603,173 @@ boolean PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length) { ...@@ -596,174 +603,173 @@ boolean PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length) {
} }
return result; return result;
#else #else
rc = _client->write(buf+(MQTT_MAX_HEADER_SIZE-hlen),length+hlen); rc = _client->write(buf + (MQTT_MAX_HEADER_SIZE - hlen), length + hlen);
lastOutActivity = millis(); lastOutActivity = millis();
return (rc == hlen+length); return (rc == hlen + length);
#endif #endif
} }
boolean PubSubClient::subscribe(const char* topic) { boolean PubSubClient::subscribe(const char *topic) {
return subscribe(topic, 0); return subscribe(topic, 0);
} }
boolean PubSubClient::subscribe(const char* topic, uint8_t qos) { boolean PubSubClient::subscribe(const char *topic, uint8_t qos) {
size_t topicLength = strnlen(topic, this->bufferSize); size_t topicLength = strnlen(topic, this->bufferSize);
if (topic == 0) { if (topic == 0) {
return false; return false;
} }
if (qos > 1) { if (qos > 1) {
return false;
}
if (this->bufferSize < 9 + topicLength) {
// Too long
return false;
}
if (connected()) {
// Leave room in the buffer for header and variable length field
uint16_t length = MQTT_MAX_HEADER_SIZE;
nextMsgId++;
if (nextMsgId == 0) {
nextMsgId = 1;
}
this->buffer[length++] = (nextMsgId >> 8);
this->buffer[length++] = (nextMsgId & 0xFF);
length = writeString((char*)topic, this->buffer,length);
this->buffer[length++] = qos;
return write(MQTTSUBSCRIBE|MQTTQOS1,this->buffer,length-MQTT_MAX_HEADER_SIZE);
}
return false; return false;
}
if (this->bufferSize < 9 + topicLength) {
// Too long
return false;
}
if (connected()) {
// Leave room in the buffer for header and variable length field
uint16_t length = MQTT_MAX_HEADER_SIZE;
nextMsgId++;
if (nextMsgId == 0) {
nextMsgId = 1;
}
this->buffer[length++] = (nextMsgId >> 8);
this->buffer[length++] = (nextMsgId & 0xFF);
length = writeString((char*) topic, this->buffer, length);
this->buffer[length++] = qos;
return write(MQTTSUBSCRIBE | MQTTQOS1, this->buffer, length - MQTT_MAX_HEADER_SIZE);
}
return false;
} }
boolean PubSubClient::unsubscribe(const char* topic) { boolean PubSubClient::unsubscribe(const char *topic) {
size_t topicLength = strnlen(topic, this->bufferSize); size_t topicLength = strnlen(topic, this->bufferSize);
if (topic == 0) { if (topic == 0) {
return false;
}
if (this->bufferSize < 9 + topicLength) {
// Too long
return false;
}
if (connected()) {
uint16_t length = MQTT_MAX_HEADER_SIZE;
nextMsgId++;
if (nextMsgId == 0) {
nextMsgId = 1;
}
this->buffer[length++] = (nextMsgId >> 8);
this->buffer[length++] = (nextMsgId & 0xFF);
length = writeString(topic, this->buffer,length);
return write(MQTTUNSUBSCRIBE|MQTTQOS1,this->buffer,length-MQTT_MAX_HEADER_SIZE);
}
return false; return false;
}
if (this->bufferSize < 9 + topicLength) {
// Too long
return false;
}
if (connected()) {
uint16_t length = MQTT_MAX_HEADER_SIZE;
nextMsgId++;
if (nextMsgId == 0) {
nextMsgId = 1;
}
this->buffer[length++] = (nextMsgId >> 8);
this->buffer[length++] = (nextMsgId & 0xFF);
length = writeString(topic, this->buffer, length);
return write(MQTTUNSUBSCRIBE | MQTTQOS1, this->buffer, length - MQTT_MAX_HEADER_SIZE);
}
return false;
} }
void PubSubClient::disconnect() { void PubSubClient::disconnect() {
this->buffer[0] = MQTTDISCONNECT; this->buffer[0] = MQTTDISCONNECT;
this->buffer[1] = 0; this->buffer[1] = 0;
_client->write(this->buffer,2); _client->write(this->buffer, 2);
_state = MQTT_DISCONNECTED; _state = MQTT_DISCONNECTED;
_client->flush(); _client->flush();
_client->stop(); _client->stop();
lastInActivity = lastOutActivity = millis(); lastInActivity = lastOutActivity = millis();
} }
uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, uint16_t pos) { uint16_t PubSubClient::writeString(const char *string, uint8_t *buf, uint16_t pos) {
const char* idp = string; const char *idp = string;
uint16_t i = 0; uint16_t i = 0;
pos += 2; pos += 2;
while (*idp) { while (*idp) {
buf[pos++] = *idp++; buf[pos++] = *idp++;
i++; i++;
} }
buf[pos-i-2] = (i >> 8); buf[pos - i - 2] = (i >> 8);
buf[pos-i-1] = (i & 0xFF); buf[pos - i - 1] = (i & 0xFF);
return pos; return pos;
} }
boolean PubSubClient::connected() { boolean PubSubClient::connected() {
boolean rc; boolean rc;
if (_client == NULL ) { if (_client == NULL) {
rc = false; rc = false;
} else {
rc = (int) _client->connected();
if (!rc) {
if (this->_state == MQTT_CONNECTED) {
this->_state = MQTT_CONNECTION_LOST;
_client->flush();
_client->stop();
}
} else { } else {
rc = (int)_client->connected(); return this->_state == MQTT_CONNECTED;
if (!rc) {
if (this->_state == MQTT_CONNECTED) {
this->_state = MQTT_CONNECTION_LOST;
_client->flush();
_client->stop();
}
} else {
return this->_state == MQTT_CONNECTED;
}
} }
return rc; }
return rc;
} }
PubSubClient& PubSubClient::setServer(uint8_t * ip, uint16_t port) { PubSubClient& PubSubClient::setServer(uint8_t *ip, uint16_t port) {
IPAddress addr(ip[0],ip[1],ip[2],ip[3]); IPAddress addr(ip[0], ip[1], ip[2], ip[3]);
return setServer(addr,port); return setServer(addr, port);
} }
PubSubClient& PubSubClient::setServer(IPAddress ip, uint16_t port) { PubSubClient& PubSubClient::setServer(IPAddress ip, uint16_t port) {
this->ip = ip; this->ip = ip;
this->port = port; this->port = port;
this->domain = NULL; this->domain = NULL;
return *this; return *this;
} }
PubSubClient& PubSubClient::setServer(const char * domain, uint16_t port) { PubSubClient& PubSubClient::setServer(const char *domain, uint16_t port) {
this->domain = domain; this->domain = domain;
this->port = port; this->port = port;
return *this; return *this;
} }
PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE) { PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE) {
this->callback = callback; this->callback = callback;
return *this; return *this;
} }
PubSubClient& PubSubClient::setClient(Client& client){ PubSubClient& PubSubClient::setClient(Client &client) {
this->_client = &client; this->_client = &client;
return *this; return *this;
} }
PubSubClient& PubSubClient::setStream(Stream& stream){ PubSubClient& PubSubClient::setStream(Stream &stream) {
this->stream = &stream; this->stream = &stream;
return *this; return *this;
} }
int PubSubClient::state() { int PubSubClient::state() {
return this->_state; return this->_state;
} }
boolean PubSubClient::setBufferSize(uint16_t size) { boolean PubSubClient::setBufferSize(uint16_t size) {
if (size == 0) { if (size == 0) {
// Cannot set it back to 0 // Cannot set it back to 0
return false; return false;
} }
if (this->bufferSize == 0) { if (this->bufferSize == 0) {
this->buffer = (uint8_t*)malloc(size); this->buffer = (uint8_t*) malloc(size);
} else {
uint8_t *newBuffer = (uint8_t*) realloc(this->buffer, size);
if (newBuffer != NULL) {
this->buffer = newBuffer;
} else { } else {
uint8_t* newBuffer = (uint8_t*)realloc(this->buffer, size); return false;
if (newBuffer != NULL) {
this->buffer = newBuffer;
} else {
return false;
}
} }
this->bufferSize = size; }
return (this->buffer != NULL); this->bufferSize = size;
return (this->buffer != NULL);
} }
uint16_t PubSubClient::getBufferSize() { uint16_t PubSubClient::getBufferSize() {
return this->bufferSize; return this->bufferSize;
} }
PubSubClient& PubSubClient::setKeepAlive(uint16_t keepAlive) { PubSubClient& PubSubClient::setKeepAlive(uint16_t keepAlive) {
this->keepAlive = keepAlive; this->keepAlive = keepAlive;
return *this; return *this;
} }
PubSubClient& PubSubClient::setSocketTimeout(uint16_t timeout) { PubSubClient& PubSubClient::setSocketTimeout(uint16_t timeout) {
this->socketTimeout = timeout; this->socketTimeout = timeout;
return *this; return *this;
} }
...@@ -14,25 +14,34 @@ SCD30 KEYWORD1 ...@@ -14,25 +14,34 @@ SCD30 KEYWORD1
SCD30 KEYWORD2 SCD30 KEYWORD2
begin KEYWORD2 begin KEYWORD2
isConnected KEYWORD2
enableDebugging KEYWORD2 enableDebugging KEYWORD2
beginMeasuring KEYWORD2 beginMeasuring KEYWORD2
StopMeasurement KEYWORD2 StopMeasurement KEYWORD2
setAmbientPressure KEYWORD2
getSettingValue KEYWORD2 getSettingValue KEYWORD2
getForcedRecalibration KEYWORD2
getMeasurementInterval KEYWORD2
getTemperatureOffset KEYWORD2
getAltitudeCompensation KEYWORD2
getFirmwareVersion KEYWORD2 getFirmwareVersion KEYWORD2
getCO2 KEYWORD2 getCO2 KEYWORD2
getHumidity KEYWORD2 getHumidity KEYWORD2
getTemperature KEYWORD2 getTemperature KEYWORD2
getMeasurementInterval KEYWORD2
setMeasurementInterval KEYWORD2 setMeasurementInterval KEYWORD2
setAmbientPressure KEYWORD2
getAltitudeCompensation KEYWORD2
setAltitudeCompensation KEYWORD2 setAltitudeCompensation KEYWORD2
getAutoSelfCalibration KEYWORD2
setAutoSelfCalibration KEYWORD2 setAutoSelfCalibration KEYWORD2
getForcedRecalibration KEYWORD2
setForcedRecalibrationFactor KEYWORD2 setForcedRecalibrationFactor KEYWORD2
getTemperatureOffset KEYWORD2
setTemperatureOffset KEYWORD2 setTemperatureOffset KEYWORD2
getAutoSelfCalibration KEYWORD2
dataAvailable KEYWORD2 dataAvailable KEYWORD2
readMeasurement KEYWORD2 readMeasurement KEYWORD2
reset KEYWORD2 reset KEYWORD2
......
name=SparkFun SCD30 Arduino Library name=SparkFun SCD30 Arduino Library
version=1.0.13 version=1.0.17
author=SparkFun Electronics author=SparkFun Electronics
maintainer=SparkFun Electronics <sparkfun.com> maintainer=SparkFun Electronics <sparkfun.com>
sentence=Library for the Sensirion SCD30 CO2 Sensor sentence=Library for the Sensirion SCD30 CO2 Sensor
......
...@@ -7,9 +7,9 @@ ...@@ -7,9 +7,9 @@
Written by Nathan Seidle @ SparkFun Electronics, May 22nd, 2018 Written by Nathan Seidle @ SparkFun Electronics, May 22nd, 2018
Updated February 1st 2021 to include some of the features of paulvha's version of the library Updated February 1st 2021 to include some of the features of paulvha's version of the library
(while maintaining backward-compatibility): (while maintaining backward-compatibility):
https://github.com/paulvha/scd30 https://github.com/paulvha/scd30
Thank you Paul! Thank you Paul!
The SCD30 measures CO2 with accuracy of +/- 30ppm. The SCD30 measures CO2 with accuracy of +/- 30ppm.
...@@ -32,14 +32,14 @@ SCD30::SCD30(void) ...@@ -32,14 +32,14 @@ SCD30::SCD30(void)
// Constructor // Constructor
} }
//Initialize the Serial port // Initialize the Serial port
#ifdef USE_TEENSY3_I2C_LIB #ifdef USE_TEENSY3_I2C_LIB
bool SCD30::begin(i2c_t3 &wirePort, bool autoCalibrate, bool measBegin) bool SCD30::begin(i2c_t3 &wirePort, bool autoCalibrate, bool measBegin)
#else #else
bool SCD30::begin(TwoWire &wirePort, bool autoCalibrate, bool measBegin) bool SCD30::begin(TwoWire &wirePort, bool autoCalibrate, bool measBegin)
#endif #endif
{ {
_i2cPort = &wirePort; //Grab which port the user wants us to use _i2cPort = &wirePort; // Grab which port the user wants us to use
/* Especially during obtaining the ACK BIT after a byte sent the SCD30 is using clock stretching (but NOT only there)! /* Especially during obtaining the ACK BIT after a byte sent the SCD30 is using clock stretching (but NOT only there)!
* The need for clock stretching is described in the Sensirion_CO2_Sensors_SCD30_Interface_Description.pdf * The need for clock stretching is described in the Sensirion_CO2_Sensors_SCD30_Interface_Description.pdf
...@@ -58,128 +58,153 @@ bool SCD30::begin(TwoWire &wirePort, bool autoCalibrate, bool measBegin) ...@@ -58,128 +58,153 @@ bool SCD30::begin(TwoWire &wirePort, bool autoCalibrate, bool measBegin)
_i2cPort->setClockStretchLimit(200000); _i2cPort->setClockStretchLimit(200000);
#endif #endif
uint16_t fwVer; if (isConnected() == false)
if (getFirmwareVersion(&fwVer) == false) // Read the firmware version. Return false if the CRC check fails.
return (false); return (false);
if (_printDebug == true)
{
_debugPort->print(F("SCD30 begin: got firmware version 0x"));
_debugPort->println(fwVer, HEX);
}
if (measBegin == false) // Exit now if measBegin is false if (measBegin == false) // Exit now if measBegin is false
return (true); return (true);
//Check for device to respond correctly // Check for device to respond correctly
if (beginMeasuring() == true) //Start continuous measurements if (beginMeasuring() == true) // Start continuous measurements
{ {
setMeasurementInterval(2); //2 seconds between measurements setMeasurementInterval(2); // 2 seconds between measurements
setAutoSelfCalibration(autoCalibrate); //Enable auto-self-calibration setAutoSelfCalibration(autoCalibrate); // Enable auto-self-calibration
return (true); return (true);
} }
return (false); //Something went wrong return (false); // Something went wrong
}
// Returns true if device responds to a firmware request
bool SCD30::isConnected()
{
uint16_t fwVer;
if (getFirmwareVersion(&fwVer) == false) // Read the firmware version. Return false if the CRC check fails.
return (false);
if (_printDebug == true)
{
_debugPort->print(F("Firmware version 0x"));
_debugPort->println(fwVer, HEX);
}
return (true);
} }
//Calling this function with nothing sets the debug port to Serial // Calling this function with nothing sets the debug port to Serial
//You can also call it with other streams like Serial1, SerialUSB, etc. // You can also call it with other streams like Serial1, SerialUSB, etc.
void SCD30::enableDebugging(Stream &debugPort) void SCD30::enableDebugging(Stream &debugPort)
{ {
_debugPort = &debugPort; _debugPort = &debugPort;
_printDebug = true; _printDebug = true;
} }
//Returns the latest available CO2 level // Returns the latest available CO2 level
//If the current level has already been reported, trigger a new read // If the current level has already been reported, trigger a new read
uint16_t SCD30::getCO2(void) uint16_t SCD30::getCO2(void)
{ {
if (co2HasBeenReported == true) //Trigger a new read if (co2HasBeenReported == true) // Trigger a new read
readMeasurement(); //Pull in new co2, humidity, and temp into global vars {
if (readMeasurement() == false) // Pull in new co2, humidity, and temp into global vars
co2 = 0; // Failed to read sensor
}
co2HasBeenReported = true; co2HasBeenReported = true;
return (uint16_t)co2; //Cut off decimal as co2 is 0 to 10,000 return (uint16_t)co2; // Cut off decimal as co2 is 0 to 10,000
} }
//Returns the latest available humidity // Returns the latest available humidity
//If the current level has already been reported, trigger a new read // If the current level has already been reported, trigger a new read
float SCD30::getHumidity(void) float SCD30::getHumidity(void)
{ {
if (humidityHasBeenReported == true) //Trigger a new read if (humidityHasBeenReported == true) // Trigger a new read
readMeasurement(); //Pull in new co2, humidity, and temp into global vars if (readMeasurement() == false) // Pull in new co2, humidity, and temp into global vars
humidity = 0; // Failed to read sensor
humidityHasBeenReported = true; humidityHasBeenReported = true;
return humidity; return humidity;
} }
//Returns the latest available temperature // Returns the latest available temperature
//If the current level has already been reported, trigger a new read // If the current level has already been reported, trigger a new read
float SCD30::getTemperature(void) float SCD30::getTemperature(void)
{ {
if (temperatureHasBeenReported == true) //Trigger a new read if (temperatureHasBeenReported == true) // Trigger a new read
readMeasurement(); //Pull in new co2, humidity, and temp into global vars if (readMeasurement() == false) // Pull in new co2, humidity, and temp into global vars
temperature = 0; // Failed to read sensor
temperatureHasBeenReported = true; temperatureHasBeenReported = true;
return temperature; return temperature;
} }
//Enables or disables the ASC // Enables or disables the ASC
bool SCD30::setAutoSelfCalibration(bool enable) bool SCD30::setAutoSelfCalibration(bool enable)
{ {
if (enable) if (enable)
return sendCommand(COMMAND_AUTOMATIC_SELF_CALIBRATION, 1); //Activate continuous ASC return sendCommand(COMMAND_AUTOMATIC_SELF_CALIBRATION, 1); // Activate continuous ASC
else else
return sendCommand(COMMAND_AUTOMATIC_SELF_CALIBRATION, 0); //Deactivate continuous ASC return sendCommand(COMMAND_AUTOMATIC_SELF_CALIBRATION, 0); // Deactivate continuous ASC
} }
//Set the forced recalibration factor. See 1.3.7. // Set the forced recalibration factor. See 1.3.7.
//The reference CO2 concentration has to be within the range 400 ppm ≤ cref(CO2) ≤ 2000 ppm. // The reference CO2 concentration has to be within the range 400 ppm ≤ cref(CO2) ≤ 2000 ppm.
bool SCD30::setForcedRecalibrationFactor(uint16_t concentration) bool SCD30::setForcedRecalibrationFactor(uint16_t concentration)
{ {
if (concentration < 400 || concentration > 2000) if (concentration < 400 || concentration > 2000)
{ {
return false; //Error check. return false; // Error check.
} }
return sendCommand(COMMAND_SET_FORCED_RECALIBRATION_FACTOR, concentration); return sendCommand(COMMAND_SET_FORCED_RECALIBRATION_FACTOR, concentration);
} }
//Get the temperature offset. See 1.3.8. // Get the temperature offset. See 1.3.8.
float SCD30::getTemperatureOffset(void) float SCD30::getTemperatureOffset(void)
{ {
uint16_t response = readRegister(COMMAND_SET_TEMPERATURE_OFFSET); uint16_t response = readRegister(COMMAND_SET_TEMPERATURE_OFFSET);
return (((float)response) / 100.0);
}
//Set the temperature offset. See 1.3.8.
bool SCD30::setTemperatureOffset(float tempOffset)
{
union union
{ {
int16_t signed16; int16_t signed16;
uint16_t unsigned16; uint16_t unsigned16;
} signedUnsigned; // Avoid any ambiguity casting int16_t to uint16_t } signedUnsigned; // Avoid any ambiguity casting int16_t to uint16_t
signedUnsigned.signed16 = tempOffset * 100; signedUnsigned.signed16 = response;
return sendCommand(COMMAND_SET_TEMPERATURE_OFFSET, signedUnsigned.unsigned16);
return (((float)signedUnsigned.signed16) / 100.0);
} }
//Get the altitude compenstation. See 1.3.9. // Set the temperature offset to remove module heating from temp reading
bool SCD30::setTemperatureOffset(float tempOffset)
{
// Temp offset is only positive. See: https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library/issues/27#issuecomment-971986826
//"The SCD30 offset temperature is obtained by subtracting the reference temperature from the SCD30 output temperature"
// https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/9.5_CO2/Sensirion_CO2_Sensors_SCD30_Low_Power_Mode.pdf
if (tempOffset < 0.0)
return (false);
uint16_t value = tempOffset * 100;
return sendCommand(COMMAND_SET_TEMPERATURE_OFFSET, value);
}
// Get the altitude compenstation. See 1.3.9.
uint16_t SCD30::getAltitudeCompensation(void) uint16_t SCD30::getAltitudeCompensation(void)
{ {
return readRegister(COMMAND_SET_ALTITUDE_COMPENSATION); return readRegister(COMMAND_SET_ALTITUDE_COMPENSATION);
} }
//Set the altitude compenstation. See 1.3.9. // Set the altitude compenstation. See 1.3.9.
bool SCD30::setAltitudeCompensation(uint16_t altitude) bool SCD30::setAltitudeCompensation(uint16_t altitude)
{ {
return sendCommand(COMMAND_SET_ALTITUDE_COMPENSATION, altitude); return sendCommand(COMMAND_SET_ALTITUDE_COMPENSATION, altitude);
} }
//Set the pressure compenstation. This is passed during measurement startup. // Set the pressure compenstation. This is passed during measurement startup.
//mbar can be 700 to 1200 // mbar can be 700 to 1200
bool SCD30::setAmbientPressure(uint16_t pressure_mbar) bool SCD30::setAmbientPressure(uint16_t pressure_mbar)
{ {
if (pressure_mbar < 700 || pressure_mbar > 1200) if (pressure_mbar < 700 || pressure_mbar > 1200)
...@@ -192,33 +217,34 @@ bool SCD30::setAmbientPressure(uint16_t pressure_mbar) ...@@ -192,33 +217,34 @@ bool SCD30::setAmbientPressure(uint16_t pressure_mbar)
// SCD30 soft reset // SCD30 soft reset
void SCD30::reset() void SCD30::reset()
{ {
sendCommand(COMMAND_RESET); sendCommand(COMMAND_RESET);
} }
// Get the current ASC setting // Get the current ASC setting
bool SCD30::getAutoSelfCalibration() bool SCD30::getAutoSelfCalibration()
{ {
uint16_t response = readRegister(COMMAND_AUTOMATIC_SELF_CALIBRATION); uint16_t response = readRegister(COMMAND_AUTOMATIC_SELF_CALIBRATION);
if (response == 1) { if (response == 1)
{
return true; return true;
} }
else { else
return false; {
return false;
} }
} }
//Begins continuous measurements // Begins continuous measurements
//Continuous measurement status is saved in non-volatile memory. When the sensor // Continuous measurement status is saved in non-volatile memory. When the sensor
//is powered down while continuous measurement mode is active SCD30 will measure // is powered down while continuous measurement mode is active SCD30 will measure
//continuously after repowering without sending the measurement command. // continuously after repowering without sending the measurement command.
//Returns true if successful // Returns true if successful
bool SCD30::beginMeasuring(uint16_t pressureOffset) bool SCD30::beginMeasuring(uint16_t pressureOffset)
{ {
return (sendCommand(COMMAND_CONTINUOUS_MEASUREMENT, pressureOffset)); return (sendCommand(COMMAND_CONTINUOUS_MEASUREMENT, pressureOffset));
} }
//Overload - no pressureOffset // Overload - no pressureOffset
bool SCD30::beginMeasuring(void) bool SCD30::beginMeasuring(void)
{ {
return (beginMeasuring(0)); return (beginMeasuring(0));
...@@ -227,17 +253,26 @@ bool SCD30::beginMeasuring(void) ...@@ -227,17 +253,26 @@ bool SCD30::beginMeasuring(void)
// Stop continuous measurement // Stop continuous measurement
bool SCD30::StopMeasurement(void) bool SCD30::StopMeasurement(void)
{ {
return(sendCommand(COMMAND_STOP_MEAS)); return (sendCommand(COMMAND_STOP_MEAS));
} }
//Sets interval between measurements // Sets interval between measurements
//2 seconds to 1800 seconds (30 minutes) // 2 seconds to 1800 seconds (30 minutes)
bool SCD30::setMeasurementInterval(uint16_t interval) bool SCD30::setMeasurementInterval(uint16_t interval)
{ {
return sendCommand(COMMAND_SET_MEASUREMENT_INTERVAL, interval); return sendCommand(COMMAND_SET_MEASUREMENT_INTERVAL, interval);
} }
//Returns true when data is available // Gets interval between measurements
// 2 seconds to 1800 seconds (30 minutes)
uint16_t SCD30::getMeasurementInterval(void)
{
uint16_t interval = 0;
getSettingValue(COMMAND_SET_MEASUREMENT_INTERVAL, &interval);
return (interval);
}
// Returns true when data is available
bool SCD30::dataAvailable() bool SCD30::dataAvailable()
{ {
uint16_t response = readRegister(COMMAND_GET_DATA_READY); uint16_t response = readRegister(COMMAND_GET_DATA_READY);
...@@ -247,24 +282,27 @@ bool SCD30::dataAvailable() ...@@ -247,24 +282,27 @@ bool SCD30::dataAvailable()
return (false); return (false);
} }
//Get 18 bytes from SCD30 // Get 18 bytes from SCD30
//Updates global variables with floats // Updates global variables with floats
//Returns true if success // Returns true if success
bool SCD30::readMeasurement() bool SCD30::readMeasurement()
{ {
//Verify we have data from the sensor // Verify we have data from the sensor
if (dataAvailable() == false) if (dataAvailable() == false)
return (false); return (false);
ByteToFl tempCO2; tempCO2.value = 0; ByteToFl tempCO2;
ByteToFl tempHumidity; tempHumidity.value = 0; tempCO2.value = 0;
ByteToFl tempTemperature; tempTemperature.value = 0; ByteToFl tempHumidity;
tempHumidity.value = 0;
ByteToFl tempTemperature;
tempTemperature.value = 0;
_i2cPort->beginTransmission(SCD30_ADDRESS); _i2cPort->beginTransmission(SCD30_ADDRESS);
_i2cPort->write(COMMAND_READ_MEASUREMENT >> 8); //MSB _i2cPort->write(COMMAND_READ_MEASUREMENT >> 8); // MSB
_i2cPort->write(COMMAND_READ_MEASUREMENT & 0xFF); //LSB _i2cPort->write(COMMAND_READ_MEASUREMENT & 0xFF); // LSB
if (_i2cPort->endTransmission() != 0) if (_i2cPort->endTransmission() != 0)
return (0); //Sensor did not ACK return (0); // Sensor did not ACK
delay(3); delay(3);
...@@ -283,25 +321,25 @@ bool SCD30::readMeasurement() ...@@ -283,25 +321,25 @@ bool SCD30::readMeasurement()
case 1: case 1:
case 3: case 3:
case 4: case 4:
tempCO2.array[x < 3 ? 3-x : 4-x] = incoming; tempCO2.array[x < 3 ? 3 - x : 4 - x] = incoming;
bytesToCrc[x % 3] = incoming; bytesToCrc[x % 3] = incoming;
break; break;
case 6: case 6:
case 7: case 7:
case 9: case 9:
case 10: case 10:
tempTemperature.array[x < 9 ? 9-x : 10-x] = incoming; tempTemperature.array[x < 9 ? 9 - x : 10 - x] = incoming;
bytesToCrc[x % 3] = incoming; bytesToCrc[x % 3] = incoming;
break; break;
case 12: case 12:
case 13: case 13:
case 15: case 15:
case 16: case 16:
tempHumidity.array[x < 15 ? 15-x : 16-x] = incoming; tempHumidity.array[x < 15 ? 15 - x : 16 - x] = incoming;
bytesToCrc[x % 3] = incoming; bytesToCrc[x % 3] = incoming;
break; break;
default: default:
//Validate CRC // Validate CRC
uint8_t foundCrc = computeCRC8(bytesToCrc, 2); uint8_t foundCrc = computeCRC8(bytesToCrc, 2);
if (foundCrc != incoming) if (foundCrc != incoming)
{ {
...@@ -337,28 +375,28 @@ bool SCD30::readMeasurement() ...@@ -337,28 +375,28 @@ bool SCD30::readMeasurement()
_debugPort->println(F("readMeasurement: encountered error reading SCD30 data.")); _debugPort->println(F("readMeasurement: encountered error reading SCD30 data."));
return false; return false;
} }
//Now copy the uint32s into their associated floats // Now copy the uint32s into their associated floats
co2 = tempCO2.value; co2 = tempCO2.value;
temperature = tempTemperature.value; temperature = tempTemperature.value;
humidity = tempHumidity.value; humidity = tempHumidity.value;
//Mark our global variables as fresh // Mark our global variables as fresh
co2HasBeenReported = false; co2HasBeenReported = false;
humidityHasBeenReported = false; humidityHasBeenReported = false;
temperatureHasBeenReported = false; temperatureHasBeenReported = false;
return (true); //Success! New data available in globals. return (true); // Success! New data available in globals.
} }
//Gets a setting by reading the appropriate register. // Gets a setting by reading the appropriate register.
//Returns true if the CRC is valid. // Returns true if the CRC is valid.
bool SCD30::getSettingValue(uint16_t registerAddress, uint16_t *val) bool SCD30::getSettingValue(uint16_t registerAddress, uint16_t *val)
{ {
_i2cPort->beginTransmission(SCD30_ADDRESS); _i2cPort->beginTransmission(SCD30_ADDRESS);
_i2cPort->write(registerAddress >> 8); //MSB _i2cPort->write(registerAddress >> 8); // MSB
_i2cPort->write(registerAddress & 0xFF); //LSB _i2cPort->write(registerAddress & 0xFF); // LSB
if (_i2cPort->endTransmission() != 0) if (_i2cPort->endTransmission() != 0)
return (false); //Sensor did not ACK return (false); // Sensor did not ACK
delay(3); delay(3);
...@@ -384,14 +422,14 @@ bool SCD30::getSettingValue(uint16_t registerAddress, uint16_t *val) ...@@ -384,14 +422,14 @@ bool SCD30::getSettingValue(uint16_t registerAddress, uint16_t *val)
return (false); return (false);
} }
//Gets two bytes from SCD30 // Gets two bytes from SCD30
uint16_t SCD30::readRegister(uint16_t registerAddress) uint16_t SCD30::readRegister(uint16_t registerAddress)
{ {
_i2cPort->beginTransmission(SCD30_ADDRESS); _i2cPort->beginTransmission(SCD30_ADDRESS);
_i2cPort->write(registerAddress >> 8); //MSB _i2cPort->write(registerAddress >> 8); // MSB
_i2cPort->write(registerAddress & 0xFF); //LSB _i2cPort->write(registerAddress & 0xFF); // LSB
if (_i2cPort->endTransmission() != 0) if (_i2cPort->endTransmission() != 0)
return (0); //Sensor did not ACK return (0); // Sensor did not ACK
delay(3); delay(3);
...@@ -402,49 +440,49 @@ uint16_t SCD30::readRegister(uint16_t registerAddress) ...@@ -402,49 +440,49 @@ uint16_t SCD30::readRegister(uint16_t registerAddress)
uint8_t lsb = _i2cPort->read(); uint8_t lsb = _i2cPort->read();
return ((uint16_t)msb << 8 | lsb); return ((uint16_t)msb << 8 | lsb);
} }
return (0); //Sensor did not respond return (0); // Sensor did not respond
} }
//Sends a command along with arguments and CRC // Sends a command along with arguments and CRC
bool SCD30::sendCommand(uint16_t command, uint16_t arguments) bool SCD30::sendCommand(uint16_t command, uint16_t arguments)
{ {
uint8_t data[2]; uint8_t data[2];
data[0] = arguments >> 8; data[0] = arguments >> 8;
data[1] = arguments & 0xFF; data[1] = arguments & 0xFF;
uint8_t crc = computeCRC8(data, 2); //Calc CRC on the arguments only, not the command uint8_t crc = computeCRC8(data, 2); // Calc CRC on the arguments only, not the command
_i2cPort->beginTransmission(SCD30_ADDRESS); _i2cPort->beginTransmission(SCD30_ADDRESS);
_i2cPort->write(command >> 8); //MSB _i2cPort->write(command >> 8); // MSB
_i2cPort->write(command & 0xFF); //LSB _i2cPort->write(command & 0xFF); // LSB
_i2cPort->write(arguments >> 8); //MSB _i2cPort->write(arguments >> 8); // MSB
_i2cPort->write(arguments & 0xFF); //LSB _i2cPort->write(arguments & 0xFF); // LSB
_i2cPort->write(crc); _i2cPort->write(crc);
if (_i2cPort->endTransmission() != 0) if (_i2cPort->endTransmission() != 0)
return (false); //Sensor did not ACK return (false); // Sensor did not ACK
return (true); return (true);
} }
//Sends just a command, no arguments, no CRC // Sends just a command, no arguments, no CRC
bool SCD30::sendCommand(uint16_t command) bool SCD30::sendCommand(uint16_t command)
{ {
_i2cPort->beginTransmission(SCD30_ADDRESS); _i2cPort->beginTransmission(SCD30_ADDRESS);
_i2cPort->write(command >> 8); //MSB _i2cPort->write(command >> 8); // MSB
_i2cPort->write(command & 0xFF); //LSB _i2cPort->write(command & 0xFF); // LSB
if (_i2cPort->endTransmission() != 0) if (_i2cPort->endTransmission() != 0)
return (false); //Sensor did not ACK return (false); // Sensor did not ACK
return (true); return (true);
} }
//Given an array and a number of bytes, this calculate CRC8 for those bytes // Given an array and a number of bytes, this calculate CRC8 for those bytes
//CRC is only calc'd on the data portion (two bytes) of the four bytes being sent // CRC is only calc'd on the data portion (two bytes) of the four bytes being sent
//From: http://www.sunshine2k.de/articles/coding/crc/understanding_crc.html // From: http://www.sunshine2k.de/articles/coding/crc/understanding_crc.html
//Tested with: http://www.sunshine2k.de/coding/javascript/crc/crc_js.html // Tested with: http://www.sunshine2k.de/coding/javascript/crc/crc_js.html
//x^8+x^5+x^4+1 = 0x31 // x^8+x^5+x^4+1 = 0x31
uint8_t SCD30::computeCRC8(uint8_t data[], uint8_t len) uint8_t SCD30::computeCRC8(uint8_t data[], uint8_t len)
{ {
uint8_t crc = 0xFF; //Init with 0xFF uint8_t crc = 0xFF; // Init with 0xFF
for (uint8_t x = 0; x < len; x++) for (uint8_t x = 0; x < len; x++)
{ {
...@@ -459,5 +497,5 @@ uint8_t SCD30::computeCRC8(uint8_t data[], uint8_t len) ...@@ -459,5 +497,5 @@ uint8_t SCD30::computeCRC8(uint8_t data[], uint8_t len)
} }
} }
return crc; //No output reflection return crc; // No output reflection
} }
...@@ -40,10 +40,10 @@ ...@@ -40,10 +40,10 @@
#include <Wire.h> #include <Wire.h>
#endif #endif
//The default I2C address for the SCD30 is 0x61. // The default I2C address for the SCD30 is 0x61.
#define SCD30_ADDRESS 0x61 #define SCD30_ADDRESS 0x61
//Available commands // Available commands
#define COMMAND_CONTINUOUS_MEASUREMENT 0x0010 #define COMMAND_CONTINUOUS_MEASUREMENT 0x0010
#define COMMAND_SET_MEASUREMENT_INTERVAL 0x4600 #define COMMAND_SET_MEASUREMENT_INTERVAL 0x4600
...@@ -70,38 +70,43 @@ public: ...@@ -70,38 +70,43 @@ public:
bool begin(bool autoCalibrate) { return begin(Wire, autoCalibrate); } bool begin(bool autoCalibrate) { return begin(Wire, autoCalibrate); }
#ifdef USE_TEENSY3_I2C_LIB #ifdef USE_TEENSY3_I2C_LIB
bool begin(i2c_t3 &wirePort = Wire, bool autoCalibrate = false, bool measBegin = true); //By default use Wire port bool begin(i2c_t3 &wirePort = Wire, bool autoCalibrate = false, bool measBegin = true); // By default use Wire port
#else #else
bool begin(TwoWire &wirePort = Wire, bool autoCalibrate = false, bool measBegin = true); //By default use Wire port bool begin(TwoWire &wirePort = Wire, bool autoCalibrate = false, bool measBegin = true); // By default use Wire port
#endif #endif
void enableDebugging(Stream &debugPort = Serial); //Turn on debug printing. If user doesn't specify then Serial will be used. bool isConnected();
void enableDebugging(Stream &debugPort = Serial); // Turn on debug printing. If user doesn't specify then Serial will be used.
bool beginMeasuring(uint16_t pressureOffset); bool beginMeasuring(uint16_t pressureOffset);
bool beginMeasuring(void); bool beginMeasuring(void);
bool StopMeasurement(void); // paulvha bool StopMeasurement(void); // paulvha
// based on paulvha bool setAmbientPressure(uint16_t pressure_mbar);
bool getSettingValue(uint16_t registerAddress, uint16_t *val); bool getSettingValue(uint16_t registerAddress, uint16_t *val);
bool getForcedRecalibration(uint16_t *val) { return (getSettingValue(COMMAND_SET_FORCED_RECALIBRATION_FACTOR, val)); }
bool getMeasurementInterval(uint16_t *val) { return (getSettingValue(COMMAND_SET_MEASUREMENT_INTERVAL, val)); }
bool getTemperatureOffset(uint16_t *val) { return (getSettingValue(COMMAND_SET_TEMPERATURE_OFFSET, val)); }
bool getAltitudeCompensation(uint16_t *val) { return (getSettingValue(COMMAND_SET_ALTITUDE_COMPENSATION, val)); }
bool getFirmwareVersion(uint16_t *val) { return (getSettingValue(COMMAND_READ_FW_VER, val)); } bool getFirmwareVersion(uint16_t *val) { return (getSettingValue(COMMAND_READ_FW_VER, val)); }
uint16_t getCO2(void); uint16_t getCO2(void);
float getHumidity(void); float getHumidity(void);
float getTemperature(void); float getTemperature(void);
float getTemperatureOffset(void);
uint16_t getAltitudeCompensation(void);
uint16_t getMeasurementInterval(void);
bool getMeasurementInterval(uint16_t *val) { return (getSettingValue(COMMAND_SET_MEASUREMENT_INTERVAL, val)); }
bool setMeasurementInterval(uint16_t interval); bool setMeasurementInterval(uint16_t interval);
bool setAmbientPressure(uint16_t pressure_mbar);
uint16_t getAltitudeCompensation(void);
bool getAltitudeCompensation(uint16_t *val) { return (getSettingValue(COMMAND_SET_ALTITUDE_COMPENSATION, val)); }
bool setAltitudeCompensation(uint16_t altitude); bool setAltitudeCompensation(uint16_t altitude);
bool getAutoSelfCalibration(void);
bool setAutoSelfCalibration(bool enable); bool setAutoSelfCalibration(bool enable);
bool getForcedRecalibration(uint16_t *val) { return (getSettingValue(COMMAND_SET_FORCED_RECALIBRATION_FACTOR, val)); }
bool setForcedRecalibrationFactor(uint16_t concentration); bool setForcedRecalibrationFactor(uint16_t concentration);
float getTemperatureOffset(void);
bool getTemperatureOffset(uint16_t *val) { return (getSettingValue(COMMAND_SET_TEMPERATURE_OFFSET, val)); }
bool setTemperatureOffset(float tempOffset); bool setTemperatureOffset(float tempOffset);
bool getAutoSelfCalibration(void);
bool dataAvailable(); bool dataAvailable();
bool readMeasurement(); bool readMeasurement();
...@@ -116,25 +121,25 @@ public: ...@@ -116,25 +121,25 @@ public:
uint8_t computeCRC8(uint8_t data[], uint8_t len); uint8_t computeCRC8(uint8_t data[], uint8_t len);
private: private:
//Variables // Variables
#ifdef USE_TEENSY3_I2C_LIB #ifdef USE_TEENSY3_I2C_LIB
i2c_t3 *_i2cPort; //The generic connection to user's chosen I2C hardware i2c_t3 *_i2cPort; // The generic connection to user's chosen I2C hardware
#else #else
TwoWire *_i2cPort; //The generic connection to user's chosen I2C hardware TwoWire *_i2cPort; // The generic connection to user's chosen I2C hardware
#endif #endif
//Global main datums // Global main datums
float co2 = 0; float co2 = 0;
float temperature = 0; float temperature = 0;
float humidity = 0; float humidity = 0;
//These track the staleness of the current data // These track the staleness of the current data
//This allows us to avoid calling readMeasurement() every time individual datums are requested // This allows us to avoid calling readMeasurement() every time individual datums are requested
bool co2HasBeenReported = true; bool co2HasBeenReported = true;
bool humidityHasBeenReported = true; bool humidityHasBeenReported = true;
bool temperatureHasBeenReported = true; bool temperatureHasBeenReported = true;
//Debug // Debug
Stream *_debugPort; //The stream to send debug messages to if enabled. Usually Serial. Stream *_debugPort; // The stream to send debug messages to if enabled. Usually Serial.
boolean _printDebug = false; //Flag to print debugging variables boolean _printDebug = false; // Flag to print debugging variables
}; };
#endif #endif
#include "util.h" #include "util.h"
#include "sensor_console.h"
namespace config {
const char *ntp_server = NTP_SERVER;
const long utc_offset_in_seconds = UTC_OFFSET_IN_SECONDS; // UTC+1
}
#if defined(ESP8266) #if defined(ESP8266)
# include <ESP8266WiFi.h> // required to get MAC address
const char *current_board = "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) #elif defined(ESP32)
# include <WiFi.h> // required to get MAC address
const char *current_board = "ESP32"; const char *current_board = "ESP32";
#else #else
const char *current_board = "UNKNOWN"; const char *current_board = "UNKNOWN";
#endif #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() { void Ampel::showFreeSpace() {
Serial.print(F("Free heap space : ")); Serial.print(F("Free heap space : "));
Serial.print(ESP.getFreeHeap()); Serial.print(ESP.getFreeHeap());
...@@ -90,7 +44,6 @@ char* getSensorId() { ...@@ -90,7 +44,6 @@ char* getSensorId() {
Ampel::Ampel() : Ampel::Ampel() :
board(current_board), sensorId(getSensorId()), macAddress(getMacString()), max_loop_duration(0) { 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("free", Ampel::showFreeSpace, F("(Displays available heap space)"));
sensor_console::defineCommand("reset", []() { sensor_console::defineCommand("reset", []() {
ESP.restart(); ESP.restart();
......
#ifndef AMPEL_UTIL_H_INCLUDED #ifndef AMPEL_UTIL_H_INCLUDED
#define 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 <stdint.h> // For uint32_t
#include "src/lib/NTPClient-master/NTPClient.h" // NTP
#if defined(ESP8266) #if defined(ESP8266)
# include <ESP8266WiFi.h> // required to get MAC address
# define esp_get_max_free_block_size() ESP.getMaxFreeBlockSize() # define esp_get_max_free_block_size() ESP.getMaxFreeBlockSize()
# define esp_get_heap_fragmentation() ESP.getHeapFragmentation() # define esp_get_heap_fragmentation() ESP.getHeapFragmentation()
#elif defined(ESP32) #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_max_free_block_size() ESP.getMaxAllocHeap() //largest block of heap that can be allocated.
# define esp_get_heap_fragmentation() "?" // apparently not available for ESP32 # define esp_get_heap_fragmentation() "?" // apparently not available for ESP32
#endif #endif
namespace ntp {
void initialize();
void update();
void getLocalTime(char *timestamp);
}
namespace util { namespace util {
template<typename Tpa, typename Tpb> template<typename Tpa, typename Tpb>
inline auto min(const Tpa &a, const Tpb &b) -> decltype(a < b ? a : b) { inline auto min(const Tpa &a, const Tpb &b) -> decltype(a < b ? a : b) {
...@@ -48,7 +36,4 @@ public: ...@@ -48,7 +36,4 @@ public:
extern Ampel 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 #endif
#include "web_server.h" #include "web_server.h"
#if defined(ESP8266)
# include <ESP8266WebServer.h>
#elif defined(ESP32)
# include <WebServer.h>
#endif
#include "config.h"
#include "util.h"
#include "ntp.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 config { namespace config {
// Values should be defined in config.h // Values should be defined in config.h
#ifdef HTTP_USER #ifdef HTTP_USER
...@@ -236,7 +258,7 @@ namespace web_server { ...@@ -236,7 +258,7 @@ namespace web_server {
mqtt::connected ? "Yes" : "No", mqtt::last_successful_publish, config::mqtt_sending_interval, mqtt::connected ? "Yes" : "No", mqtt::last_successful_publish, config::mqtt_sending_interval,
#endif #endif
#if defined(AMPEL_LORAWAN) && defined(ESP32) #if defined(AMPEL_LORAWAN) && defined(ESP32)
lorawan::connected ? "Yes" : "No", LMIC_FREQUENCY_PLAN, lorawan::last_transmission, lorawan::connected ? "Yes" : "No", config::lorawan_frequency_plan, lorawan::last_transmission,
config::lorawan_sending_interval, config::lorawan_sending_interval,
#endif #endif
config::temperature_offset, config::auto_calibrate_sensor ? "Yes" : "No", ampel.sensorId, ampel.sensorId, config::temperature_offset, config::auto_calibrate_sensor ? "Yes" : "No", ampel.sensorId, ampel.sensorId,
......
Markdown is supported
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