@ -1,2 +1,3 @@ | |||
*.gz.h -diff -merge | |||
*.gz.h linguist-generated=true | |||
*.ini text eol=lf |
@ -1,15 +1,13 @@ | |||
.clang_complete | |||
core_version.h | |||
custom.h | |||
.DS_Store | |||
.gcc-flags.json | |||
.pioenvs | |||
.piolibdeps | |||
.python-version | |||
.travis.yml | |||
.vscode | |||
.vscode/.browse.c_cpp.db* | |||
.vscode/c_cpp_properties.json | |||
.vscode/launch.json | |||
.pioenvs | |||
.piolibdeps | |||
.clang_complete | |||
core_version.h | |||
custom.h | |||
.DS_Store | |||
.gcc-flags.json | |||
.python-version | |||
.travis.yml | |||
.vscode | |||
.vscode/.browse.c_cpp.db* | |||
.vscode/c_cpp_properties.json | |||
.vscode/launch.json | |||
.pio | |||
libraries/ |
@ -1,19 +0,0 @@ | |||
/* Flash Split for 1M chips, no SPIFFS, 1 sector for EEPROM */ | |||
/* sketch 999KB */ | |||
/* eeprom 4KB */ | |||
/* reserved 16KB */ | |||
MEMORY | |||
{ | |||
dport0_0_seg : org = 0x3FF00000, len = 0x10 | |||
dram0_0_seg : org = 0x3FFE8000, len = 0x14000 | |||
iram1_0_seg : org = 0x40100000, len = 0x8000 | |||
irom0_0_seg : org = 0x40201010, len = 0xf9ff0 | |||
} | |||
PROVIDE ( _SPIFFS_start = 0x402FB000 ); | |||
PROVIDE ( _SPIFFS_end = 0x402FB000 ); | |||
PROVIDE ( _SPIFFS_page = 0x0 ); | |||
PROVIDE ( _SPIFFS_block = 0x0 ); | |||
INCLUDE "../ld/eagle.app.v6.common.ld" |
@ -1,19 +0,0 @@ | |||
/* Flash Split for 1M chips, no SPIFFS, 2 sectors for EEPROM */ | |||
/* sketch 995KB */ | |||
/* eeprom 8KB */ | |||
/* reserved 16KB */ | |||
MEMORY | |||
{ | |||
dport0_0_seg : org = 0x3FF00000, len = 0x10 | |||
dram0_0_seg : org = 0x3FFE8000, len = 0x14000 | |||
iram1_0_seg : org = 0x40100000, len = 0x8000 | |||
irom0_0_seg : org = 0x40201010, len = 0xf8ff0 | |||
} | |||
PROVIDE ( _SPIFFS_start = 0x402FA000 ); | |||
PROVIDE ( _SPIFFS_end = 0x402FA000 ); | |||
PROVIDE ( _SPIFFS_page = 0x0 ); | |||
PROVIDE ( _SPIFFS_block = 0x0 ); | |||
INCLUDE "../ld/eagle.app.v6.common.ld" |
@ -1,19 +0,0 @@ | |||
/* Flash Split for 512K chips, no SPIFFS, 1 sector for EEPROM */ | |||
/* sketch 487KB */ | |||
/* eeprom 4KB */ | |||
/* reserved 16KB */ | |||
MEMORY | |||
{ | |||
dport0_0_seg : org = 0x3FF00000, len = 0x10 | |||
dram0_0_seg : org = 0x3FFE8000, len = 0x14000 | |||
iram1_0_seg : org = 0x40100000, len = 0x8000 | |||
irom0_0_seg : org = 0x40201010, len = 0x79ff0 | |||
} | |||
PROVIDE ( _SPIFFS_start = 0x4027B000 ); | |||
PROVIDE ( _SPIFFS_end = 0x4027B000 ); | |||
PROVIDE ( _SPIFFS_page = 0x0 ); | |||
PROVIDE ( _SPIFFS_block = 0x0 ); | |||
INCLUDE "../ld/eagle.app.v6.common.ld" |
@ -0,0 +1,97 @@ | |||
/* | |||
* | |||
* Created: 29.03.2018 | |||
* | |||
* Authors: | |||
* | |||
* Assembled from the code released on Stackoverflow by: | |||
* Dennis (instructable.com/member/nqtronix) | https://stackoverflow.com/questions/23032002/c-c-how-to-get-integer-unix-timestamp-of-build-time-not-string | |||
* and | |||
* Alexis Wilke | https://stackoverflow.com/questions/10538444/do-you-know-of-a-c-macro-to-compute-unix-time-and-date | |||
* | |||
* Assembled by Jean Rabault | |||
* | |||
* UNIX_TIMESTAMP gives the UNIX timestamp (unsigned long integer of seconds since 1st Jan 1970) of compilation from macros using the compiler defined __TIME__ macro. | |||
* This should include Gregorian calendar leap days, in particular the 29ths of February, 100 and 400 years modulo leaps. | |||
* | |||
* Careful: __TIME__ is the local time of the computer, NOT the UTC time in general! | |||
* | |||
*/ | |||
#ifndef COMPILE_TIME_H_ | |||
#define COMPILE_TIME_H_ | |||
// Some definitions for calculation | |||
#define SEC_PER_MIN 60UL | |||
#define SEC_PER_HOUR 3600UL | |||
#define SEC_PER_DAY 86400UL | |||
#define SEC_PER_YEAR (SEC_PER_DAY*365) | |||
// extracts 1..4 characters from a string and interprets it as a decimal value | |||
#define CONV_STR2DEC_1(str, i) (str[i]>'0'?str[i]-'0':0) | |||
#define CONV_STR2DEC_2(str, i) (CONV_STR2DEC_1(str, i)*10 + str[i+1]-'0') | |||
#define CONV_STR2DEC_3(str, i) (CONV_STR2DEC_2(str, i)*10 + str[i+2]-'0') | |||
#define CONV_STR2DEC_4(str, i) (CONV_STR2DEC_3(str, i)*10 + str[i+3]-'0') | |||
// Custom "glue logic" to convert the month name to a usable number | |||
#define GET_MONTH(str, i) (str[i]=='J' && str[i+1]=='a' && str[i+2]=='n' ? 1 : \ | |||
str[i]=='F' && str[i+1]=='e' && str[i+2]=='b' ? 2 : \ | |||
str[i]=='M' && str[i+1]=='a' && str[i+2]=='r' ? 3 : \ | |||
str[i]=='A' && str[i+1]=='p' && str[i+2]=='r' ? 4 : \ | |||
str[i]=='M' && str[i+1]=='a' && str[i+2]=='y' ? 5 : \ | |||
str[i]=='J' && str[i+1]=='u' && str[i+2]=='n' ? 6 : \ | |||
str[i]=='J' && str[i+1]=='u' && str[i+2]=='l' ? 7 : \ | |||
str[i]=='A' && str[i+1]=='u' && str[i+2]=='g' ? 8 : \ | |||
str[i]=='S' && str[i+1]=='e' && str[i+2]=='p' ? 9 : \ | |||
str[i]=='O' && str[i+1]=='c' && str[i+2]=='t' ? 10 : \ | |||
str[i]=='N' && str[i+1]=='o' && str[i+2]=='v' ? 11 : \ | |||
str[i]=='D' && str[i+1]=='e' && str[i+2]=='c' ? 12 : 0) | |||
// extract the information from the time string given by __TIME__ and __DATE__ | |||
#define __TIME_SECOND__ (CONV_STR2DEC_2(__TIME__, 6)) | |||
#define __TIME_MINUTE__ (CONV_STR2DEC_2(__TIME__, 3)) | |||
#define __TIME_HOUR__ (CONV_STR2DEC_2(__TIME__, 0)) | |||
#define __TIME_DAY__ (CONV_STR2DEC_2(__DATE__, 4)) | |||
#define __TIME_MONTH__ (GET_MONTH(__DATE__, 0)) | |||
#define __TIME_YEAR__ (CONV_STR2DEC_4(__DATE__, 7)) | |||
// Days in February | |||
#define _UNIX_TIMESTAMP_FDAY(year) \ | |||
(((year) % 400) == 0UL ? 29UL : \ | |||
(((year) % 100) == 0UL ? 28UL : \ | |||
(((year) % 4) == 0UL ? 29UL : \ | |||
28UL))) | |||
// Days in the year | |||
#define _UNIX_TIMESTAMP_YDAY(year, month, day) \ | |||
( \ | |||
/* January */ day \ | |||
/* February */ + (month >= 2 ? 31UL : 0UL) \ | |||
/* March */ + (month >= 3 ? _UNIX_TIMESTAMP_FDAY(year) : 0UL) \ | |||
/* April */ + (month >= 4 ? 31UL : 0UL) \ | |||
/* May */ + (month >= 5 ? 30UL : 0UL) \ | |||
/* June */ + (month >= 6 ? 31UL : 0UL) \ | |||
/* July */ + (month >= 7 ? 30UL : 0UL) \ | |||
/* August */ + (month >= 8 ? 31UL : 0UL) \ | |||
/* September */+ (month >= 9 ? 31UL : 0UL) \ | |||
/* October */ + (month >= 10 ? 30UL : 0UL) \ | |||
/* November */ + (month >= 11 ? 31UL : 0UL) \ | |||
/* December */ + (month >= 12 ? 30UL : 0UL) \ | |||
) | |||
// get the UNIX timestamp from a digits representation | |||
#define _UNIX_TIMESTAMP(year, month, day, hour, minute, second) \ | |||
( /* time */ second \ | |||
+ minute * SEC_PER_MIN \ | |||
+ hour * SEC_PER_HOUR \ | |||
+ /* year day (month + day) */ (_UNIX_TIMESTAMP_YDAY(year, month, day) - 1) * SEC_PER_DAY \ | |||
+ /* year */ (year - 1970UL) * SEC_PER_YEAR \ | |||
+ ((year - 1969UL) / 4UL) * SEC_PER_DAY \ | |||
- ((year - 1901UL) / 100UL) * SEC_PER_DAY \ | |||
+ ((year - 1601UL) / 400UL) * SEC_PER_DAY \ | |||
) | |||
// the UNIX timestamp | |||
#define __UNIX_TIMESTAMP__ (_UNIX_TIMESTAMP(__TIME_YEAR__, __TIME_MONTH__, __TIME_DAY__, __TIME_HOUR__, __TIME_MINUTE__, __TIME_SECOND__)) | |||
#endif |
@ -0,0 +1,47 @@ | |||
// ----------------------------------------------------------------------------- | |||
// printf-like debug methods | |||
// ----------------------------------------------------------------------------- | |||
#pragma once | |||
void debugSendImpl(const char*); | |||
void _debugSend(const char * format, va_list args) { | |||
char temp[64]; | |||
int len = ets_vsnprintf(temp, sizeof(temp), format, args); | |||
if (len < 64) { debugSendImpl(temp); return; } | |||
auto buffer = new char[len + 1]; | |||
ets_vsnprintf(buffer, len + 1, format, args); | |||
debugSendImpl(buffer); | |||
delete[] buffer; | |||
} | |||
void debugSend(const char* format, ...) { | |||
va_list args; | |||
va_start(args, format); | |||
_debugSend(format, args); | |||
va_end(args); | |||
} | |||
void debugSend_P(PGM_P format_P, ...) { | |||
char format[strlen_P(format_P) + 1]; | |||
memcpy_P(format, format_P, sizeof(format)); | |||
va_list args; | |||
va_start(args, format_P); | |||
_debugSend(format, args); | |||
va_end(args); | |||
} |
@ -0,0 +1,228 @@ | |||
/* ---------------------------- Original copyright ----------------------------- | |||
* | |||
* Encoder Library, for measuring quadrature encoded signals | |||
* http://www.pjrc.com/teensy/td_libs_Encoder.html | |||
* Copyright (c) 2011,2013 PJRC.COM, LLC - Paul Stoffregen <paul@pjrc.com> | |||
* | |||
* Version 1.2 - fix -2 bug in C-only code | |||
* Version 1.1 - expand to support boards with up to 60 interrupts | |||
* Version 1.0 - initial release | |||
* | |||
* Permission is hereby granted, free of charge, to any person obtaining a copy | |||
* of this software and associated documentation files (the "Software"), to deal | |||
* in the Software without restriction, including without limitation the rights | |||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
* copies of the Software, and to permit persons to whom the Software is | |||
* furnished to do so, subject to the following conditions: | |||
* | |||
* The above copyright notice and this permission notice shall be included in | |||
* all copies or substantial portions of the Software. | |||
* | |||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
* THE SOFTWARE. | |||
* | |||
* ----------------------------------------------------------------------------- | |||
* | |||
* Encoder.h, updated for ESP8266 use in ESPurna. Other hardware is not supported. | |||
* | |||
* - Added ESP-specific attributes to ISR handlers to place them in IRAM. | |||
* - Reduced per-encoder structure sizes - only 5 Encoders can be used on ESP8266, | |||
* and we can directly reference pin number instead of storing both register and bitmask | |||
* | |||
*/ | |||
#pragma once | |||
// _______ _______ | |||
// Pin1 ______| |_______| |______ Pin1 | |||
// negative <--- _______ _______ __ --> positive | |||
// Pin2 __| |_______| |_______| Pin2 | |||
// new new old old | |||
// pin2 pin1 pin2 pin1 Result | |||
// ---- ---- ---- ---- ------ | |||
// 0 0 0 0 no movement | |||
// 0 0 0 1 +1 | |||
// 0 0 1 0 -1 | |||
// 0 0 1 1 +2 (assume pin1 edges only) | |||
// 0 1 0 0 -1 | |||
// 0 1 0 1 no movement | |||
// 0 1 1 0 -2 (assume pin1 edges only) | |||
// 0 1 1 1 +1 | |||
// 1 0 0 0 +1 | |||
// 1 0 0 1 -2 (assume pin1 edges only) | |||
// 1 0 1 0 no movement | |||
// 1 0 1 1 -1 | |||
// 1 1 0 0 +2 (assume pin1 edges only) | |||
// 1 1 0 1 -1 | |||
// 1 1 1 0 +1 | |||
// 1 1 1 1 no movement | |||
namespace EncoderLibrary { | |||
typedef struct { | |||
uint8_t pin1; | |||
uint8_t pin2; | |||
uint8_t state; | |||
int32_t position; | |||
} encoder_values_t; | |||
constexpr const unsigned char ENCODERS_MAXIMUM {5u}; | |||
encoder_values_t * EncoderValues[ENCODERS_MAXIMUM] = {nullptr}; | |||
uint8_t _encoderFindStorage() { | |||
for (uint8_t i = 0; i < ENCODERS_MAXIMUM; i++) { | |||
if (EncoderValues[i] == nullptr) { | |||
return i; | |||
} | |||
} | |||
return ENCODERS_MAXIMUM; | |||
} | |||
void _encoderCleanStorage(uint8_t pin1, uint8_t pin2) { | |||
for (uint8_t i = 0; i < ENCODERS_MAXIMUM; i++) { | |||
if (EncoderValues[i] == nullptr) continue; | |||
if (((EncoderValues[i])->pin1 == pin1) && ((EncoderValues[i])->pin2 == pin2)) { | |||
EncoderValues[i] = nullptr; | |||
break; | |||
} | |||
} | |||
} | |||
// update() is not meant to be called from outside Encoder, | |||
// but it is public to allow static interrupt routines. | |||
void ICACHE_RAM_ATTR update(encoder_values_t *target) { | |||
uint8_t p1val = GPIP(target->pin1); | |||
uint8_t p2val = GPIP(target->pin2); | |||
uint8_t state = target->state & 3; | |||
if (p1val) state |= 4; | |||
if (p2val) state |= 8; | |||
target->state = (state >> 2); | |||
switch (state) { | |||
case 1: case 7: case 8: case 14: | |||
target->position++; | |||
return; | |||
case 2: case 4: case 11: case 13: | |||
target->position--; | |||
return; | |||
case 3: case 12: | |||
target->position += 2; | |||
return; | |||
case 6: case 9: | |||
target->position -= 2; | |||
return; | |||
} | |||
} | |||
// 2 pins per encoder, 1 isr per encoder | |||
void ICACHE_RAM_ATTR isr0() { update(EncoderValues[0]); } | |||
void ICACHE_RAM_ATTR isr1() { update(EncoderValues[1]); } | |||
void ICACHE_RAM_ATTR isr2() { update(EncoderValues[2]); } | |||
void ICACHE_RAM_ATTR isr3() { update(EncoderValues[3]); } | |||
void ICACHE_RAM_ATTR isr4() { update(EncoderValues[4]); } | |||
constexpr void (*_isr_funcs[5])() = { | |||
isr0, isr1, isr2, isr3, isr4 | |||
}; | |||
class Encoder { | |||
private: | |||
encoder_values_t values; | |||
public: | |||
Encoder(uint8_t pin1, uint8_t pin2) { | |||
values.pin1 = pin1; | |||
values.pin2 = pin2; | |||
pinMode(values.pin1, INPUT_PULLUP); | |||
pinMode(values.pin2, INPUT_PULLUP); | |||
values.position = 0; | |||
// allow time for a passive R-C filter to charge | |||
// through the pullup resistors, before reading | |||
// the initial state | |||
delayMicroseconds(2000); | |||
uint8_t current = 0; | |||
if (GPIP(values.pin1)) { | |||
current |= 1; | |||
} | |||
if (GPIP(values.pin2)) { | |||
current |= 2; | |||
} | |||
values.state = current; | |||
attach(); | |||
} | |||
~Encoder() { | |||
detach(); | |||
} | |||
uint8_t pin1() { | |||
return values.pin1; | |||
} | |||
uint8_t pin2() { | |||
return values.pin2; | |||
} | |||
int32_t read() { | |||
noInterrupts(); | |||
update(&values); | |||
int32_t ret = values.position; | |||
interrupts(); | |||
return ret; | |||
} | |||
void write(int32_t position) { | |||
noInterrupts(); | |||
values.position = position; | |||
interrupts(); | |||
} | |||
bool attach() { | |||
uint8_t index = _encoderFindStorage(); | |||
if (index >= ENCODERS_MAXIMUM) return false; | |||
EncoderValues[index] = &values; | |||
attachInterrupt(values.pin1, _isr_funcs[index], CHANGE); | |||
attachInterrupt(values.pin2, _isr_funcs[index], CHANGE); | |||
return true; | |||
} | |||
void detach() { | |||
noInterrupts(); | |||
_encoderCleanStorage(values.pin1, values.pin2); | |||
detachInterrupt(values.pin1); | |||
detachInterrupt(values.pin2); | |||
interrupts(); | |||
} | |||
}; | |||
} | |||
using EncoderLibrary::Encoder; |
@ -0,0 +1,112 @@ | |||
/* | |||
Show extended heap stats when EspClass::getHeapStats() is available | |||
*/ | |||
#pragma once | |||
#include <type_traits> | |||
struct heap_stats_t { | |||
uint32_t available; | |||
uint16_t usable; | |||
uint8_t frag_pct; | |||
}; | |||
namespace EspClass_has_getHeapStats { | |||
struct _detector { | |||
template<typename T, typename = decltype( | |||
std::declval<T>().getHeapStats(0,0,0))> | |||
static std::true_type detect(int); | |||
template<typename> | |||
static std::false_type detect(...); | |||
}; | |||
template <typename T> | |||
struct detector : public _detector { | |||
using result = decltype( | |||
std::declval<detector>().template detect<T>(0)); | |||
}; | |||
template <typename T> | |||
struct typed_check : public detector<T>::result { | |||
}; | |||
typed_check<EspClass> check{}; | |||
}; | |||
template <typename T> | |||
void _getHeapStats(std::true_type&, T& instance, heap_stats_t& stats) { | |||
instance.getHeapStats(&stats.available, &stats.usable, &stats.frag_pct); | |||
} | |||
template <typename T> | |||
void _getHeapStats(std::false_type&, T& instance, heap_stats_t& stats) { | |||
stats.available = instance.getFreeHeap(); | |||
stats.usable = 0; | |||
stats.frag_pct = 0; | |||
} | |||
void getHeapStats(heap_stats_t& stats) { | |||
_getHeapStats(EspClass_has_getHeapStats::check, ESP, stats); | |||
} | |||
// WTF | |||
// Calling ESP.getFreeHeap() is making the system crash on a specific | |||
// AiLight bulb, but anywhere else it should work as expected | |||
static bool _heap_value_wtf = false; | |||
heap_stats_t getHeapStats() { | |||
heap_stats_t stats; | |||
if (_heap_value_wtf) { | |||
stats.available = 9999; | |||
stats.usable = 9999; | |||
stats.frag_pct = 0; | |||
return stats; | |||
} | |||
getHeapStats(stats); | |||
return stats; | |||
} | |||
void wtfHeap(bool value) { | |||
_heap_value_wtf = value; | |||
} | |||
unsigned int getFreeHeap() { | |||
return ESP.getFreeHeap(); | |||
} | |||
static unsigned int _initial_heap_value = 0; | |||
void setInitialFreeHeap() { | |||
_initial_heap_value = getFreeHeap(); | |||
} | |||
unsigned int getInitialFreeHeap() { | |||
if (0 == _initial_heap_value) { | |||
setInitialFreeHeap(); | |||
} | |||
return _initial_heap_value; | |||
} | |||
void infoMemory(const char* name, const heap_stats_t& stats) { | |||
infoMemory(name, getInitialFreeHeap(), stats.available); | |||
} | |||
void infoHeapStats(const char* name, const heap_stats_t& stats) { | |||
DEBUG_MSG_P( | |||
PSTR("[MAIN] %-6s: %5u contiguous bytes available (%u%% fragmentation)\n"), | |||
name, | |||
stats.usable, | |||
stats.frag_pct | |||
); | |||
} | |||
void infoHeapStats(bool show_frag_stats = true) { | |||
const auto stats = getHeapStats(); | |||
infoMemory("Heap", stats); | |||
if (show_frag_stats && EspClass_has_getHeapStats::check) { | |||
infoHeapStats("Heap", stats); | |||
} | |||
} |
@ -0,0 +1,247 @@ | |||
// ----------------------------------------------------------------------------- | |||
// WiFiClientSecure validation helpers | |||
// ----------------------------------------------------------------------------- | |||
#pragma once | |||
#if SECURE_CLIENT != SECURE_CLIENT_NONE | |||
#if SECURE_CLIENT == SECURE_CLIENT_BEARSSL | |||
#include <WiFiClientSecureBearSSL.h> | |||
#elif SECURE_CLIENT == SECURE_CLIENT_AXTLS | |||
#include <WiFiClientSecureAxTLS.h> | |||
#endif | |||
namespace SecureClientHelpers { | |||
using host_callback_f = std::function<String()>; | |||
using check_callback_f = std::function<int()>; | |||
using fp_callback_f = std::function<String()>; | |||
using cert_callback_f = std::function<const char*()>; | |||
using mfln_callback_f = std::function<uint16_t()>; | |||
const char * _secureClientCheckAsString(int check) { | |||
switch (check) { | |||
case SECURE_CLIENT_CHECK_NONE: return "no validation"; | |||
case SECURE_CLIENT_CHECK_FINGERPRINT: return "fingerprint validation"; | |||
case SECURE_CLIENT_CHECK_CA: return "CA validation"; | |||
default: return "unknown"; | |||
} | |||
} | |||
#if SECURE_CLIENT == SECURE_CLIENT_AXTLS | |||
using SecureClientClass = axTLS::WiFiClientSecure; | |||
struct SecureClientConfig { | |||
SecureClientConfig(const char* tag, host_callback_f host_cb, check_callback_f check_cb, fp_callback_f fp_cb, bool debug = false) : | |||
tag(tag), | |||
on_host(host_cb), | |||
on_check(check_cb), | |||
on_fingerprint(fp_cb), | |||
debug(debug) | |||
{} | |||
String tag; | |||
host_callback_f on_host; | |||
check_callback_f on_check; | |||
fp_callback_f on_fingerprint; | |||
bool debug; | |||
}; | |||
struct SecureClientChecks { | |||
SecureClientChecks(SecureClientConfig& config) : | |||
config(config) | |||
{} | |||
int getCheck() { | |||
return (config.on_check) ? config.on_check() : (SECURE_CLIENT_CHECK); | |||
} | |||
bool beforeConnected(SecureClientClass& client) { | |||
return true; | |||
} | |||
// Special condition for legacy client! | |||
// Otherwise, we are required to connect twice. And it is deemed broken & deprecated anyways... | |||
bool afterConnected(SecureClientClass& client) { | |||
bool result = false; | |||
int check = getCheck(); | |||
if(config.debug) { | |||
DEBUG_MSG_P(PSTR("[%s] Using SSL check type: %s\n"), config.tag.c_str(), _secureClientCheckAsString(check)); | |||
} | |||
if (check == SECURE_CLIENT_CHECK_NONE) { | |||
if (config.debug) DEBUG_MSG_P(PSTR("[%s] !!! Secure connection will not be validated !!!\n"), config.tag.c_str()); | |||
result = true; | |||
} else if (check == SECURE_CLIENT_CHECK_FINGERPRINT) { | |||
if (config.on_fingerprint) { | |||
char _buffer[60] = {0}; | |||
if (config.on_fingerprint && config.on_host && sslFingerPrintChar(config.on_fingerprint().c_str(), _buffer)) { | |||
result = client.verify(_buffer, config.on_host().c_str()); | |||
} | |||
if (!result) DEBUG_MSG_P(PSTR("[%s] Wrong fingerprint, cannot connect\n"), config.tag.c_str()); | |||
} | |||
} else if (check == SECURE_CLIENT_CHECK_CA) { | |||
if (config.debug) DEBUG_MSG_P(PSTR("[%s] CA verification is not supported with axTLS client\n"), config.tag.c_str()); | |||
} | |||
return result; | |||
} | |||
SecureClientConfig& config; | |||
bool debug; | |||
}; | |||
#endif // SECURE_CLIENT_AXTLS | |||
#if SECURE_CLIENT == SECURE_CLIENT_BEARSSL | |||
using SecureClientClass = BearSSL::WiFiClientSecure; | |||
struct SecureClientConfig { | |||
SecureClientConfig(const char* tag, check_callback_f check_cb, cert_callback_f cert_cb, fp_callback_f fp_cb, mfln_callback_f mfln_cb, bool debug = false) : | |||
tag(tag), | |||
on_check(check_cb), | |||
on_certificate(cert_cb), | |||
on_fingerprint(fp_cb), | |||
on_mfln(mfln_cb), | |||
debug(debug) | |||
{} | |||
String tag; | |||
check_callback_f on_check; | |||
cert_callback_f on_certificate; | |||
fp_callback_f on_fingerprint; | |||
mfln_callback_f on_mfln; | |||
bool debug; | |||
}; | |||
struct SecureClientChecks { | |||
SecureClientChecks(SecureClientConfig& config) : | |||
config(config) | |||
{} | |||
int getCheck() { | |||
return (config.on_check) ? config.on_check() : (SECURE_CLIENT_CHECK); | |||
} | |||
bool prepareMFLN(SecureClientClass& client) { | |||
const uint16_t requested_mfln = (config.on_mfln) ? config.on_mfln() : (SECURE_CLIENT_MFLN); | |||
bool result = false; | |||
switch (requested_mfln) { | |||
// default, do nothing | |||
case 0: | |||
result = true; | |||
break; | |||
// match valid sizes only | |||
case 512: | |||
case 1024: | |||
case 2048: | |||
case 4096: | |||
{ | |||
client.setBufferSizes(requested_mfln, requested_mfln); | |||
result = true; | |||
if (config.debug) { | |||
DEBUG_MSG_P(PSTR("[%s] MFLN buffer size set to %u\n"), config.tag.c_str(), requested_mfln); | |||
} | |||
break; | |||
} | |||
default: | |||
{ | |||
if (config.debug) { | |||
DEBUG_MSG_P(PSTR("[%s] Warning: MFLN buffer size must be one of 512, 1024, 2048 or 4096\n"), config.tag.c_str()); | |||
} | |||
} | |||
} | |||
return result; | |||
} | |||
bool beforeConnected(SecureClientClass& client) { | |||
int check = getCheck(); | |||
bool settime = (check == SECURE_CLIENT_CHECK_CA); | |||
if(config.debug) { | |||
DEBUG_MSG_P(PSTR("[%s] Using SSL check type: %s\n"), config.tag.c_str(), _secureClientCheckAsString(check)); | |||
} | |||
if (!ntpSynced() && settime) { | |||
if (config.debug) DEBUG_MSG_P(PSTR("[%s] Time not synced! Cannot use CA validation\n"), config.tag.c_str()); | |||
return false; | |||
} | |||
prepareMFLN(client); | |||
if (check == SECURE_CLIENT_CHECK_NONE) { | |||
if (config.debug) DEBUG_MSG_P(PSTR("[%s] !!! Secure connection will not be validated !!!\n"), config.tag.c_str()); | |||
client.setInsecure(); | |||
} else if (check == SECURE_CLIENT_CHECK_FINGERPRINT) { | |||
uint8_t _buffer[20] = {0}; | |||
if (config.on_fingerprint && sslFingerPrintArray(config.on_fingerprint().c_str(), _buffer)) { | |||
client.setFingerprint(_buffer); | |||
} | |||
} else if (check == SECURE_CLIENT_CHECK_CA) { | |||
client.setX509Time(ntpLocal2UTC(now())); | |||
if (!certs.getCount()) { | |||
if (config.on_certificate) certs.append(config.on_certificate()); | |||
} | |||
client.setTrustAnchors(&certs); | |||
} | |||
return true; | |||
} | |||
bool afterConnected(SecureClientClass&) { | |||
return true; | |||
} | |||
bool debug; | |||
SecureClientConfig& config; | |||
BearSSL::X509List certs; | |||
}; | |||
#endif // SECURE_CLIENT_BEARSSL | |||
class SecureClient { | |||
public: | |||
SecureClient(SecureClientConfig& config) : | |||
_config(config), | |||
_checks(_config), | |||
_client(std::make_unique<SecureClientClass>()) | |||
{} | |||
bool afterConnected() { | |||
return _checks.afterConnected(get()); | |||
} | |||
bool beforeConnected() { | |||
return _checks.beforeConnected(get()); | |||
} | |||
SecureClientClass& get() { | |||
return *_client.get(); | |||
} | |||
private: | |||
SecureClientConfig _config; | |||
SecureClientChecks _checks; | |||
std::unique_ptr<SecureClientClass> _client; | |||
}; | |||
}; | |||
using SecureClientConfig = SecureClientHelpers::SecureClientConfig; | |||
using SecureClientChecks = SecureClientHelpers::SecureClientChecks; | |||
using SecureClient = SecureClientHelpers::SecureClient; | |||
#endif // SECURE_CLIENT != SECURE_CLIENT_NONE |
@ -0,0 +1,68 @@ | |||
// ----------------------------------------------------------------------------- | |||
// Parse char string as URL | |||
// | |||
// Adapted from HTTPClient::beginInternal() | |||
// https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp | |||
// | |||
// ----------------------------------------------------------------------------- | |||
#pragma once | |||
struct URL { | |||
String value; | |||
String protocol; | |||
String host; | |||
String path; | |||
uint16_t port; | |||
URL(const char* url) { init(url); } | |||
URL(const String& url) { init(url); } | |||
void init(String url); | |||
}; | |||
void URL::init(String url) { | |||
this->value = url; | |||
// cut the protocol part | |||
int index = url.indexOf("://"); | |||
if (index > 0) { | |||
this->protocol = url.substring(0, index); | |||
url.remove(0, (index + 3)); | |||
} | |||
if (this->protocol == "http") { | |||
this->port = 80; | |||
} else if (this->protocol == "https") { | |||
this->port = 443; | |||
} | |||
// cut the host part | |||
String _host; | |||
index = url.indexOf('/'); | |||
if (index >= 0) { | |||
_host = url.substring(0, index); | |||
} else { | |||
_host = url; | |||
} | |||
// store the remaining part as path | |||
if (index >= 0) { | |||
url.remove(0, index); | |||
this->path = url; | |||
} else { | |||
this->path = "/"; | |||
} | |||
// separate host from port, when present | |||
index = _host.indexOf(':'); | |||
if (index >= 0) { | |||
this->port = _host.substring(index + 1).toInt(); | |||
this->host = _host.substring(0, index); | |||
} else { | |||
this->host = _host; | |||
} | |||
} |
@ -0,0 +1,26 @@ | |||
// ----------------------------------------------------------------------------- | |||
// Light | |||
// ----------------------------------------------------------------------------- | |||
#pragma once | |||
namespace Light { | |||
constexpr const unsigned char VALUE_MIN = LIGHT_MIN_VALUE; | |||
constexpr const unsigned char VALUE_MAX = LIGHT_MAX_VALUE; | |||
constexpr const unsigned int BRIGHTNESS_MIN = LIGHT_MIN_BRIGHTNESS; | |||
constexpr const unsigned int BRIGHTNESS_MAX = LIGHT_MAX_BRIGHTNESS; | |||
// Default to the Philips Hue value that HA also use. | |||
// https://developers.meethue.com/documentation/core-concepts | |||
constexpr const unsigned int MIREDS_COLDWHITE = LIGHT_COLDWHITE_MIRED; | |||
constexpr const unsigned int MIREDS_WARMWHITE = LIGHT_WARMWHITE_MIRED; | |||
constexpr const unsigned int KELVIN_WARMWHITE = LIGHT_WARMWHITE_KELVIN; | |||
constexpr const unsigned int KELVIN_COLDWHITE = LIGHT_COLDWHITE_KELVIN; | |||
constexpr const unsigned int PWM_MIN = LIGHT_MIN_PWM; | |||
constexpr const unsigned int PWM_MAX = LIGHT_MAX_PWM; | |||
constexpr const unsigned int PWM_LIMIT = LIGHT_LIMIT_PWM; | |||
} | |||
@ -1,295 +0,0 @@ | |||
/* | |||
OTA MODULE | |||
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com> | |||
*/ | |||
#include "ArduinoOTA.h" | |||
// ----------------------------------------------------------------------------- | |||
// Arduino OTA | |||
// ----------------------------------------------------------------------------- | |||
void _otaConfigure() { | |||
ArduinoOTA.setPort(OTA_PORT); | |||
ArduinoOTA.setHostname(getSetting("hostname").c_str()); | |||
#if USE_PASSWORD | |||
ArduinoOTA.setPassword(getAdminPass().c_str()); | |||
#endif | |||
} | |||
void _otaLoop() { | |||
ArduinoOTA.handle(); | |||
} | |||
// ----------------------------------------------------------------------------- | |||
// Terminal OTA | |||
// ----------------------------------------------------------------------------- | |||
#if TERMINAL_SUPPORT || OTA_MQTT_SUPPORT | |||
#include <ESPAsyncTCP.h> | |||
AsyncClient * _ota_client; | |||
char * _ota_host; | |||
char * _ota_url; | |||
unsigned int _ota_port = 80; | |||
unsigned long _ota_size = 0; | |||
const char OTA_REQUEST_TEMPLATE[] PROGMEM = | |||
"GET %s HTTP/1.1\r\n" | |||
"Host: %s\r\n" | |||
"User-Agent: ESPurna\r\n" | |||
"Connection: close\r\n" | |||
"Content-Type: application/x-www-form-urlencoded\r\n" | |||
"Content-Length: 0\r\n\r\n\r\n"; | |||
void _otaFrom(const char * host, unsigned int port, const char * url) { | |||
if (_ota_host) free(_ota_host); | |||
if (_ota_url) free(_ota_url); | |||
_ota_host = strdup(host); | |||
_ota_url = strdup(url); | |||
_ota_port = port; | |||
_ota_size = 0; | |||
if (_ota_client == NULL) { | |||
_ota_client = new AsyncClient(); | |||
} | |||
_ota_client->onDisconnect([](void *s, AsyncClient *c) { | |||
DEBUG_MSG_P(PSTR("\n")); | |||
if (Update.end(true)){ | |||
DEBUG_MSG_P(PSTR("[OTA] Success: %u bytes\n"), _ota_size); | |||
deferredReset(100, CUSTOM_RESET_OTA); | |||
} else { | |||
#ifdef DEBUG_PORT | |||
Update.printError(DEBUG_PORT); | |||
#endif | |||
eepromRotate(true); | |||
} | |||
DEBUG_MSG_P(PSTR("[OTA] Disconnected\n")); | |||
_ota_client->free(); | |||
delete _ota_client; | |||
_ota_client = NULL; | |||
free(_ota_host); | |||
_ota_host = NULL; | |||
free(_ota_url); | |||
_ota_url = NULL; | |||
}, 0); | |||
_ota_client->onTimeout([](void *s, AsyncClient *c, uint32_t time) { | |||
_ota_client->close(true); | |||
}, 0); | |||
_ota_client->onData([](void * arg, AsyncClient * c, void * data, size_t len) { | |||
char * p = (char *) data; | |||
if (_ota_size == 0) { | |||
Update.runAsync(true); | |||
if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) { | |||
#ifdef DEBUG_PORT | |||
Update.printError(DEBUG_PORT); | |||
#endif | |||
} | |||
p = strstr((char *)data, "\r\n\r\n") + 4; | |||
len = len - (p - (char *) data); | |||
} | |||
if (!Update.hasError()) { | |||
if (Update.write((uint8_t *) p, len) != len) { | |||
#ifdef DEBUG_PORT | |||
Update.printError(DEBUG_PORT); | |||
#endif | |||
} | |||
} | |||
_ota_size += len; | |||
DEBUG_MSG_P(PSTR("[OTA] Progress: %u bytes\r"), _ota_size); | |||
delay(0); | |||
}, NULL); | |||
_ota_client->onConnect([](void * arg, AsyncClient * client) { | |||
#if ASYNC_TCP_SSL_ENABLED | |||
if (443 == _ota_port) { | |||
uint8_t fp[20] = {0}; | |||
sslFingerPrintArray(getSetting("otafp", OTA_GITHUB_FP).c_str(), fp); | |||
SSL * ssl = _ota_client->getSSL(); | |||
if (ssl_match_fingerprint(ssl, fp) != SSL_OK) { | |||
DEBUG_MSG_P(PSTR("[OTA] Warning: certificate doesn't match\n")); | |||
} | |||
} | |||
#endif | |||
// Disabling EEPROM rotation to prevent writing to EEPROM after the upgrade | |||
eepromRotate(false); | |||
DEBUG_MSG_P(PSTR("[OTA] Downloading %s\n"), _ota_url); | |||
char buffer[strlen_P(OTA_REQUEST_TEMPLATE) + strlen(_ota_url) + strlen(_ota_host)]; | |||
snprintf_P(buffer, sizeof(buffer), OTA_REQUEST_TEMPLATE, _ota_url, _ota_host); | |||
client->write(buffer); | |||
}, NULL); | |||
#if ASYNC_TCP_SSL_ENABLED | |||
bool connected = _ota_client->connect(host, port, 443 == port); | |||
#else | |||
bool connected = _ota_client->connect(host, port); | |||
#endif | |||
if (!connected) { | |||
DEBUG_MSG_P(PSTR("[OTA] Connection failed\n")); | |||
_ota_client->close(true); | |||
} | |||
} | |||
void _otaFrom(String url) { | |||
if (!url.startsWith("http://") && !url.startsWith("https://")) { | |||
DEBUG_MSG_P(PSTR("[OTA] Incorrect URL specified\n")); | |||
return; | |||
} | |||
// Port from protocol | |||
unsigned int port = 80; | |||
if (url.startsWith("https://")) port = 443; | |||
url = url.substring(url.indexOf("/") + 2); | |||
// Get host | |||
String host = url.substring(0, url.indexOf("/")); | |||
// Explicit port | |||
int p = host.indexOf(":"); | |||
if (p > 0) { | |||
port = host.substring(p + 1).toInt(); | |||
host = host.substring(0, p); | |||
} | |||
// Get URL | |||
String uri = url.substring(url.indexOf("/")); | |||
_otaFrom(host.c_str(), port, uri.c_str()); | |||
} | |||
#endif // TERMINAL_SUPPORT || OTA_MQTT_SUPPORT | |||
#if TERMINAL_SUPPORT | |||
void _otaInitCommands() { | |||
terminalRegisterCommand(F("OTA"), [](Embedis* e) { | |||
if (e->argc < 2) { | |||
terminalError(F("Wrong arguments")); | |||
} else { | |||
terminalOK(); | |||
String url = String(e->argv[1]); | |||
_otaFrom(url); | |||
} | |||
}); | |||
} | |||
#endif // TERMINAL_SUPPORT | |||
#if OTA_MQTT_SUPPORT | |||
void _otaMQTTCallback(unsigned int type, const char * topic, const char * payload) { | |||
if (type == MQTT_CONNECT_EVENT) { | |||
mqttSubscribe(MQTT_TOPIC_OTA); | |||
} | |||
if (type == MQTT_MESSAGE_EVENT) { | |||
// Match topic | |||
String t = mqttMagnitude((char *) topic); | |||
if (t.equals(MQTT_TOPIC_OTA)) { | |||
DEBUG_MSG_P(PSTR("[OTA] Initiating from URL: %s\n"), payload); | |||
_otaFrom(payload); | |||
} | |||
} | |||
} | |||
#endif // OTA_MQTT_SUPPORT | |||
// ----------------------------------------------------------------------------- | |||
void otaSetup() { | |||
_otaConfigure(); | |||
#if TERMINAL_SUPPORT | |||
_otaInitCommands(); | |||
#endif | |||
#if OTA_MQTT_SUPPORT | |||
mqttRegister(_otaMQTTCallback); | |||
#endif | |||
// Main callbacks | |||
espurnaRegisterLoop(_otaLoop); | |||
espurnaRegisterReload(_otaConfigure); | |||
// ------------------------------------------------------------------------- | |||
ArduinoOTA.onStart([]() { | |||
// Disabling EEPROM rotation to prevent writing to EEPROM after the upgrade | |||
eepromRotate(false); | |||
DEBUG_MSG_P(PSTR("[OTA] Start\n")); | |||
#if WEB_SUPPORT | |||
wsSend_P(PSTR("{\"message\": 2}")); | |||
#endif | |||
}); | |||
ArduinoOTA.onEnd([]() { | |||
DEBUG_MSG_P(PSTR("\n")); | |||
DEBUG_MSG_P(PSTR("[OTA] Done, restarting...\n")); | |||
#if WEB_SUPPORT | |||
wsSend_P(PSTR("{\"action\": \"reload\"}")); | |||
#endif | |||
deferredReset(100, CUSTOM_RESET_OTA); | |||
}); | |||
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { | |||
static unsigned int _progOld; | |||
unsigned int _prog = (progress / (total / 100)); | |||
if (_prog != _progOld) { | |||
DEBUG_MSG_P(PSTR("[OTA] Progress: %u%%\r"), _prog); | |||
_progOld = _prog; | |||
} | |||
}); | |||
ArduinoOTA.onError([](ota_error_t error) { | |||
#if DEBUG_SUPPORT | |||
DEBUG_MSG_P(PSTR("\n[OTA] Error #%u: "), error); | |||
if (error == OTA_AUTH_ERROR) DEBUG_MSG_P(PSTR("Auth Failed\n")); | |||
else if (error == OTA_BEGIN_ERROR) DEBUG_MSG_P(PSTR("Begin Failed\n")); | |||
else if (error == OTA_CONNECT_ERROR) DEBUG_MSG_P(PSTR("Connect Failed\n")); | |||
else if (error == OTA_RECEIVE_ERROR) DEBUG_MSG_P(PSTR("Receive Failed\n")); | |||
else if (error == OTA_END_ERROR) DEBUG_MSG_P(PSTR("End Failed\n")); | |||
#endif | |||
eepromRotate(true); | |||
}); | |||
ArduinoOTA.begin(); | |||
} |
@ -0,0 +1,101 @@ | |||
/* | |||
ARDUINO OTA MODULE | |||
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com> | |||
*/ | |||
#if OTA_ARDUINOOTA_SUPPORT | |||
// TODO: allocate ArduinoOTAClass on-demand, stop using global instance | |||
void _arduinoOtaConfigure() { | |||
ArduinoOTA.setPort(OTA_PORT); | |||
ArduinoOTA.setHostname(getSetting("hostname").c_str()); | |||
#if USE_PASSWORD | |||
ArduinoOTA.setPassword(getAdminPass().c_str()); | |||
#endif | |||
ArduinoOTA.begin(); | |||
} | |||
void _arduinoOtaLoop() { | |||
ArduinoOTA.handle(); | |||
} | |||
void _arduinoOtaOnStart() { | |||
// Disabling EEPROM rotation to prevent writing to EEPROM after the upgrade | |||
eepromRotate(false); | |||
// Because ArduinoOTA is synchronous, force backup right now instead of waiting for the next loop() | |||
eepromBackup(0); | |||
DEBUG_MSG_P(PSTR("[OTA] Start\n")); | |||
#if WEB_SUPPORT | |||
wsSend_P(PSTR("{\"message\": 2}")); | |||
#endif | |||
} | |||
void _arduinoOtaOnEnd() { | |||
DEBUG_MSG_P(PSTR("\n")); | |||
DEBUG_MSG_P(PSTR("[OTA] Done, restarting...\n")); | |||
#if WEB_SUPPORT | |||
wsSend_P(PSTR("{\"action\": \"reload\"}")); | |||
#endif | |||
deferredReset(100, CUSTOM_RESET_OTA); | |||
} | |||
void _arduinoOtaOnProgress(unsigned int progress, unsigned int total) { | |||
// Removed to avoid websocket ping back during upgrade (see #1574) | |||
// TODO: implement as separate from debugging message | |||
#if WEB_SUPPORT | |||
if (wsConnected()) return; | |||
#endif | |||
static unsigned int _progOld; | |||
unsigned int _prog = (progress / (total / 100)); | |||
if (_prog != _progOld) { | |||
DEBUG_MSG_P(PSTR("[OTA] Progress: %u%%\r"), _prog); | |||
_progOld = _prog; | |||
} | |||
} | |||
void _arduinoOtaOnError(ota_error_t error) { | |||
#if DEBUG_SUPPORT | |||
DEBUG_MSG_P(PSTR("\n[OTA] Error #%u: "), error); | |||
if (error == OTA_AUTH_ERROR) DEBUG_MSG_P(PSTR("Auth Failed\n")); | |||
else if (error == OTA_BEGIN_ERROR) DEBUG_MSG_P(PSTR("Begin Failed\n")); | |||
else if (error == OTA_CONNECT_ERROR) DEBUG_MSG_P(PSTR("Connect Failed\n")); | |||
else if (error == OTA_RECEIVE_ERROR) DEBUG_MSG_P(PSTR("Receive Failed\n")); | |||
else if (error == OTA_END_ERROR) DEBUG_MSG_P(PSTR("End Failed\n")); | |||
#endif | |||
eepromRotate(true); | |||
} | |||
void arduinoOtaSetup() { | |||
espurnaRegisterLoop(_arduinoOtaLoop); | |||
espurnaRegisterReload(_arduinoOtaConfigure); | |||
ArduinoOTA.onStart(_arduinoOtaOnStart); | |||
ArduinoOTA.onEnd(_arduinoOtaOnEnd); | |||
ArduinoOTA.onError(_arduinoOtaOnError); | |||
ArduinoOTA.onProgress(_arduinoOtaOnProgress); | |||
_arduinoOtaConfigure(); | |||
} | |||
#endif // OTA_ARDUINOOTA_SUPPORT |
@ -0,0 +1,227 @@ | |||
/* | |||
ASYNC CLIENT OTA MODULE | |||
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com> | |||
*/ | |||
#if OTA_CLIENT == OTA_CLIENT_ASYNCTCP | |||
// ----------------------------------------------------------------------------- | |||
// Terminal OTA command | |||
// ----------------------------------------------------------------------------- | |||
#if TERMINAL_SUPPORT || OTA_MQTT_SUPPORT | |||
#include <ESPAsyncTCP.h> | |||
#include "libs/URL.h" | |||
std::unique_ptr<AsyncClient> _ota_client = nullptr; | |||
unsigned long _ota_size = 0; | |||
bool _ota_connected = false; | |||
std::unique_ptr<URL> _ota_url = nullptr; | |||
const char OTA_REQUEST_TEMPLATE[] PROGMEM = | |||
"GET %s HTTP/1.1\r\n" | |||
"Host: %s\r\n" | |||
"User-Agent: ESPurna\r\n" | |||
"Connection: close\r\n" | |||
"Content-Type: application/x-www-form-urlencoded\r\n" | |||
"Content-Length: 0\r\n\r\n\r\n"; | |||
void _otaClientOnDisconnect(void *s, AsyncClient *c) { | |||
DEBUG_MSG_P(PSTR("\n")); | |||
if (Update.end(true)){ | |||
DEBUG_MSG_P(PSTR("[OTA] Success: %u bytes\n"), _ota_size); | |||
deferredReset(100, CUSTOM_RESET_OTA); | |||
} else { | |||
#ifdef DEBUG_PORT | |||
Update.printError(DEBUG_PORT); | |||
#endif | |||
eepromRotate(true); | |||
} | |||
DEBUG_MSG_P(PSTR("[OTA] Disconnected\n")); | |||
_ota_connected = false; | |||
_ota_url = nullptr; | |||
_ota_client = nullptr; | |||
} | |||
void _otaClientOnTimeout(void *s, AsyncClient *c, uint32_t time) { | |||
_ota_connected = false; | |||
_ota_url = nullptr; | |||
_ota_client->close(true); | |||
} | |||
void _otaClientOnData(void * arg, AsyncClient * c, void * data, size_t len) { | |||
char * p = (char *) data; | |||
if (_ota_size == 0) { | |||
Update.runAsync(true); | |||
if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) { | |||
#ifdef DEBUG_PORT | |||
Update.printError(DEBUG_PORT); | |||
#endif | |||
c->close(true); | |||
return; | |||
} | |||
p = strstr((char *)data, "\r\n\r\n") + 4; | |||
len = len - (p - (char *) data); | |||
} | |||
if (!Update.hasError()) { | |||
if (Update.write((uint8_t *) p, len) != len) { | |||
#ifdef DEBUG_PORT | |||
Update.printError(DEBUG_PORT); | |||
#endif | |||
c->close(true); | |||
return; | |||
} | |||
} | |||
_ota_size += len; | |||
DEBUG_MSG_P(PSTR("[OTA] Progress: %u bytes\r"), _ota_size); | |||
delay(0); | |||
} | |||
void _otaClientOnConnect(void *arg, AsyncClient *client) { | |||
#if ASYNC_TCP_SSL_ENABLED | |||
int check = getSetting("otaScCheck", OTA_SECURE_CLIENT_CHECK).toInt(); | |||
if ((check == SECURE_CLIENT_CHECK_FINGERPRINT) && (443 == _ota_url->port)) { | |||
uint8_t fp[20] = {0}; | |||
sslFingerPrintArray(getSetting("otafp", OTA_FINGERPRINT).c_str(), fp); | |||
SSL * ssl = _ota_client->getSSL(); | |||
if (ssl_match_fingerprint(ssl, fp) != SSL_OK) { | |||
DEBUG_MSG_P(PSTR("[OTA] Warning: certificate fingerpint doesn't match\n")); | |||
client->close(true); | |||
return; | |||
} | |||
} | |||
#endif | |||
// Disabling EEPROM rotation to prevent writing to EEPROM after the upgrade | |||
eepromRotate(false); | |||
DEBUG_MSG_P(PSTR("[OTA] Downloading %s\n"), _ota_url->path.c_str()); | |||
char buffer[strlen_P(OTA_REQUEST_TEMPLATE) + _ota_url->path.length() + _ota_url->host.length()]; | |||
snprintf_P(buffer, sizeof(buffer), OTA_REQUEST_TEMPLATE, _ota_url->path.c_str(), _ota_url->host.c_str()); | |||
client->write(buffer); | |||
} | |||
void _otaClientFrom(const String& url) { | |||
if (_ota_connected) { | |||
DEBUG_MSG_P(PSTR("[OTA] Already connected\n")); | |||
return; | |||
} | |||
_ota_size = 0; | |||
if (_ota_url) _ota_url = nullptr; | |||
_ota_url = std::make_unique<URL>(url); | |||
/* | |||
DEBUG_MSG_P(PSTR("[OTA] proto:%s host:%s port:%u path:%s\n"), | |||
_ota_url->protocol.c_str(), | |||
_ota_url->host.c_str(), | |||
_ota_url->port, | |||
_ota_url->path.c_str() | |||
); | |||
*/ | |||
// we only support HTTP | |||
if ((!_ota_url->protocol.equals("http")) && (!_ota_url->protocol.equals("https"))) { | |||
DEBUG_MSG_P(PSTR("[OTA] Incorrect URL specified\n")); | |||
_ota_url = nullptr; | |||
return; | |||
} | |||
if (!_ota_client) { | |||
_ota_client = std::make_unique<AsyncClient>(); | |||
} | |||
_ota_client->onDisconnect(_otaClientOnDisconnect, nullptr); | |||
_ota_client->onTimeout(_otaClientOnTimeout, nullptr); | |||
_ota_client->onData(_otaClientOnData, nullptr); | |||
_ota_client->onConnect(_otaClientOnConnect, nullptr); | |||
#if ASYNC_TCP_SSL_ENABLED | |||
_ota_connected = _ota_client->connect(_ota_url->host.c_str(), _ota_url->port, 443 == _ota_url->port); | |||
#else | |||
_ota_connected = _ota_client->connect(_ota_url->host.c_str(), _ota_url->port); | |||
#endif | |||
if (!_ota_connected) { | |||
DEBUG_MSG_P(PSTR("[OTA] Connection failed\n")); | |||
_ota_url = nullptr; | |||
_ota_client->close(true); | |||
} | |||
} | |||
#endif // TERMINAL_SUPPORT || OTA_MQTT_SUPPORT | |||
#if TERMINAL_SUPPORT | |||
void _otaClientInitCommands() { | |||
terminalRegisterCommand(F("OTA"), [](Embedis* e) { | |||
if (e->argc < 2) { | |||
terminalError(F("OTA <url>")); | |||
} else { | |||
_otaClientFrom(String(e->argv[1])); | |||
terminalOK(); | |||
} | |||
}); | |||
} | |||
#endif // TERMINAL_SUPPORT | |||
#if OTA_MQTT_SUPPORT | |||
void _otaClientMqttCallback(unsigned int type, const char * topic, const char * payload) { | |||
if (type == MQTT_CONNECT_EVENT) { | |||
mqttSubscribe(MQTT_TOPIC_OTA); | |||
} | |||
if (type == MQTT_MESSAGE_EVENT) { | |||
String t = mqttMagnitude((char *) topic); | |||
if (t.equals(MQTT_TOPIC_OTA)) { | |||
DEBUG_MSG_P(PSTR("[OTA] Initiating from URL: %s\n"), payload); | |||
_otaClientFrom(payload); | |||
} | |||
} | |||
} | |||
#endif // OTA_MQTT_SUPPORT | |||
// ----------------------------------------------------------------------------- | |||
void otaClientSetup() { | |||
#if TERMINAL_SUPPORT | |||
_otaClientInitCommands(); | |||
#endif | |||
#if (MQTT_SUPPORT && OTA_MQTT_SUPPORT) | |||
mqttRegister(_otaClientMqttCallback); | |||
#endif | |||
} | |||
#endif // OTA_CLIENT == OTA_CLIENT_ASYNCTCP |
@ -0,0 +1,264 @@ | |||
/* | |||
HTTP(s) OTA MODULE | |||
Copyright (C) 2019 by Maxim Prokhorov <prokhorov dot max at outlook dot com> | |||
*/ | |||
// ----------------------------------------------------------------------------- | |||
// OTA by using Core's HTTP(s) updater | |||
// ----------------------------------------------------------------------------- | |||
#if OTA_CLIENT == OTA_CLIENT_HTTPUPDATE | |||
#include <memory> | |||
#include <ESP8266HTTPClient.h> | |||
#include <ESP8266httpUpdate.h> | |||
#include "libs/URL.h" | |||
#if SECURE_CLIENT != SECURE_CLIENT_NONE | |||
#if OTA_SECURE_CLIENT_INCLUDE_CA | |||
#include "static/ota_client_trusted_root_ca.h" | |||
#else | |||
#include "static/digicert_evroot_pem.h" | |||
#define _ota_client_trusted_root_ca _ssl_digicert_ev_root_ca | |||
#endif | |||
#endif // SECURE_CLIENT != SECURE_CLIENT_NONE | |||
void _otaClientRunUpdater(WiFiClient* client, const String& url, const String& fp = "") { | |||
UNUSED(client); | |||
UNUSED(fp); | |||
// Disabling EEPROM rotation to prevent writing to EEPROM after the upgrade | |||
eepromRotate(false); | |||
DEBUG_MSG_P(PSTR("[OTA] Downloading %s ...\n"), url.c_str()); | |||
// TODO: support currentVersion (string arg after 'url') | |||
// NOTE: ESPhttpUpdate.update(..., fp) will **always** fail with empty fingerprint | |||
// NOTE: It is possible to support BearSSL with 2.4.2 by using uint8_t[20] instead of String for fingerprint argument | |||
ESPhttpUpdate.rebootOnUpdate(false); | |||
t_httpUpdate_return result = HTTP_UPDATE_NO_UPDATES; | |||
// We expect both .update(url, "", String_fp) and .update(url) to survice until axTLS is removed from the Core | |||
#if (SECURE_CLIENT == SECURE_CLIENT_AXTLS) | |||
if (url.startsWith("https://")) { | |||
result = ESPhttpUpdate.update(url, "", fp); | |||
} else { | |||
result = ESPhttpUpdate.update(url); | |||
} | |||
#elif OTA_CLIENT_HTTPUPDATE_2_3_0_COMPATIBLE | |||
result = ESPhttpUpdate.update(url); | |||
#else | |||
result = ESPhttpUpdate.update(*client, url); | |||
#endif | |||
switch (result) { | |||
case HTTP_UPDATE_FAILED: | |||
DEBUG_MSG_P(PSTR("[OTA] Update failed (error %d): %s\n"), ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str()); | |||
eepromRotate(true); | |||
break; | |||
case HTTP_UPDATE_NO_UPDATES: | |||
DEBUG_MSG_P(PSTR("[OTA] No updates")); | |||
eepromRotate(true); | |||
break; | |||
case HTTP_UPDATE_OK: | |||
DEBUG_MSG_P(PSTR("[OTA] Done, restarting...")); | |||
deferredReset(500, CUSTOM_RESET_OTA); // wait a bit more than usual | |||
break; | |||
} | |||
} | |||
#if OTA_CLIENT_HTTPUPDATE_2_3_0_COMPATIBLE | |||
void _otaClientFromHttp(const String& url) { | |||
_otaClientRunUpdater(nullptr, url, ""); | |||
} | |||
#else | |||
void _otaClientFromHttp(const String& url) { | |||
auto client = std::make_unique<WiFiClient>(); | |||
_otaClientRunUpdater(client.get(), url, ""); | |||
} | |||
#endif | |||
#if SECURE_CLIENT == SECURE_CLIENT_BEARSSL | |||
void _otaClientFromHttps(const String& url) { | |||
int check = getSetting("otaScCheck", OTA_SECURE_CLIENT_CHECK).toInt(); | |||
bool settime = (check == SECURE_CLIENT_CHECK_CA); | |||
if (!ntpSynced() && settime) { | |||
DEBUG_MSG_P(PSTR("[OTA] Time not synced!\n")); | |||
return; | |||
} | |||
// unique_ptr self-destructs after exiting function scope | |||
// create WiFiClient on heap to use less stack space | |||
auto client = std::make_unique<BearSSL::WiFiClientSecure>(); | |||
if (check == SECURE_CLIENT_CHECK_NONE) { | |||
DEBUG_MSG_P(PSTR("[OTA] !!! Connection will not be validated !!!\n")); | |||
client->setInsecure(); | |||
} | |||
if (check == SECURE_CLIENT_CHECK_FINGERPRINT) { | |||
String fp_string = getSetting("otafp", OTA_FINGERPRINT); | |||
if (!fp_string.length()) { | |||
DEBUG_MSG_P(PSTR("[OTA] Requested fingerprint auth, but 'otafp' is not set\n")); | |||
return; | |||
} | |||
uint8_t fp_bytes[20] = {0}; | |||
sslFingerPrintArray(fp_string.c_str(), fp_bytes); | |||
client->setFingerprint(fp_bytes); | |||
} | |||
BearSSL::X509List *ca = nullptr; | |||
if (check == SECURE_CLIENT_CHECK_CA) { | |||
ca = new BearSSL::X509List(_ota_client_trusted_root_ca); | |||
// because we do not support libc methods of getting time, force client to use ntpclientlib's current time | |||
// XXX: local2utc method use is detrimental when DST happening. now() should be utc | |||
client->setX509Time(ntpLocal2UTC(now())); | |||
client->setTrustAnchors(ca); | |||
} | |||
// TODO: RX and TX buffer sizes must be equal? | |||
const uint16_t requested_mfln = getSetting("otaScMFLN", OTA_SECURE_CLIENT_MFLN).toInt(); | |||
switch (requested_mfln) { | |||
// default, do nothing | |||
case 0: | |||
break; | |||
// match valid sizes only | |||
case 512: | |||
case 1024: | |||
case 2048: | |||
case 4096: | |||
{ | |||
client->setBufferSizes(requested_mfln, requested_mfln); | |||
break; | |||
} | |||
default: | |||
DEBUG_MSG_P(PSTR("[OTA] Warning: MFLN buffer size must be one of 512, 1024, 2048 or 4096\n")); | |||
} | |||
_otaClientRunUpdater(client.get(), url); | |||
} | |||
#endif // SECURE_CLIENT_BEARSSL | |||
#if SECURE_CLIENT == SECURE_CLIENT_AXTLS | |||
void _otaClientFromHttps(const String& url) { | |||
const int check = getSetting("otaScCheck", OTA_SECURE_CLIENT_CHECK).toInt(); | |||
String fp_string; | |||
if (check == SECURE_CLIENT_CHECK_FINGERPRINT) { | |||
fp_string = getSetting("otafp", OTA_FINGERPRINT); | |||
if (!fp_string.length() || !sslCheckFingerPrint(fp_string.c_str())) { | |||
DEBUG_MSG_P(PSTR("[OTA] Wrong fingerprint\n")); | |||
return; | |||
} | |||
} | |||
_otaClientRunUpdater(nullptr, url, fp_string); | |||
} | |||
#endif // SECURE_CLIENT_AXTLS | |||
void _otaClientFrom(const String& url) { | |||
if (url.startsWith("http://")) { | |||
_otaClientFromHttp(url); | |||
return; | |||
} | |||
#if SECURE_CLIENT != SECURE_CLIENT_NONE | |||
if (url.startsWith("https://")) { | |||
_otaClientFromHttps(url); | |||
return; | |||
} | |||
#endif | |||
DEBUG_MSG_P(PSTR("[OTA] Incorrect URL specified\n")); | |||
} | |||
#if TERMINAL_SUPPORT | |||
void _otaClientInitCommands() { | |||
terminalRegisterCommand(F("OTA"), [](Embedis* e) { | |||
if (e->argc < 2) { | |||
terminalError(F("OTA <url>")); | |||
} else { | |||
_otaClientFrom(String(e->argv[1])); | |||
terminalOK(); | |||
} | |||
}); | |||
} | |||
#endif // TERMINAL_SUPPORT | |||
#if (MQTT_SUPPORT && OTA_MQTT_SUPPORT) | |||
bool _ota_do_update = false; | |||
String _ota_url; | |||
void _otaClientLoop() { | |||
if (_ota_do_update) { | |||
_otaClientFrom(_ota_url); | |||
_ota_do_update = false; | |||
_ota_url = ""; | |||
} | |||
} | |||
void _otaClientMqttCallback(unsigned int type, const char * topic, const char * payload) { | |||
if (type == MQTT_CONNECT_EVENT) { | |||
mqttSubscribe(MQTT_TOPIC_OTA); | |||
} | |||
if (type == MQTT_MESSAGE_EVENT) { | |||
String t = mqttMagnitude((char *) topic); | |||
if (t.equals(MQTT_TOPIC_OTA)) { | |||
DEBUG_MSG_P(PSTR("[OTA] Queuing from URL: %s\n"), payload); | |||
_ota_do_update = true; | |||
_ota_url = payload; | |||
} | |||
} | |||
} | |||
#endif // MQTT_SUPPORT | |||
// ----------------------------------------------------------------------------- | |||
void otaClientSetup() { | |||
#if TERMINAL_SUPPORT | |||
_otaClientInitCommands(); | |||
#endif | |||
#if (MQTT_SUPPORT && OTA_MQTT_SUPPORT) | |||
mqttRegister(_otaClientMqttCallback); | |||
espurnaRegisterLoop(_otaClientLoop); | |||
#endif | |||
} | |||
#endif // OTA_CLIENT == OTA_CLIENT_HTTPUPDATE |
@ -0,0 +1,249 @@ | |||
// ----------------------------------------------------------------------------- | |||
// ADE7853 Sensor over I2C | |||
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com> | |||
// Implemented by Antonio López <tonilopezmr at gmail dot com> | |||
// ----------------------------------------------------------------------------- | |||
#if SENSOR_SUPPORT && ADE7953_SUPPORT | |||
#pragma once | |||
#undef I2C_SUPPORT | |||
#define I2C_SUPPORT 1 // Explicitly request I2C support. | |||
#include "Arduino.h" | |||
#include "I2CSensor.h" | |||
#include <Wire.h> | |||
// ----------------------------------------------------------------------------- | |||
// ADE7953 - Energy (Shelly 2.5) | |||
// | |||
// Based on datasheet from https://www.analog.com/en/products/ade7953.html | |||
// Based on Tasmota code https://github.com/arendst/Sonoff-Tasmota/blob/development/sonoff/xnrg_07_ade7953.ino | |||
// | |||
// I2C Address: 0x38 | |||
// ----------------------------------------------------------------------------- | |||
#define ADE7953_PREF 1540 | |||
#define ADE7953_UREF 26000 | |||
#define ADE7953_IREF 10000 | |||
#define ADE7953_ALL_RELAYS 0 | |||
#define ADE7953_RELAY_1 1 | |||
#define ADE7953_RELAY_2 2 | |||
#define ADE7953_VOLTAGE 1 | |||
#define ADE7953_TOTAL_DEVICES 3 | |||
class ADE7953Sensor : public I2CSensor { | |||
protected: | |||
struct reading_t { | |||
float current = 0.0; | |||
float power = 0.0; | |||
float energy = 0.0; | |||
}; | |||
public: | |||
// --------------------------------------------------------------------- | |||
// Public | |||
// --------------------------------------------------------------------- | |||
ADE7953Sensor(): I2CSensor() { | |||
_sensor_id = SENSOR_ADE7953_ID; | |||
_readings.resize(ADE7953_TOTAL_DEVICES); | |||
_energy_offsets.resize(ADE7953_TOTAL_DEVICES); | |||
_count = _readings.size() * ADE7953_TOTAL_DEVICES + ADE7953_VOLTAGE; //10 | |||
} | |||
// --------------------------------------------------------------------- | |||
// Sensors API | |||
// --------------------------------------------------------------------- | |||
// Initialization method, must be idempotent | |||
void begin() { | |||
if (!_dirty) return; | |||
_init(); | |||
_dirty = !_ready; | |||
} | |||
// Descriptive name of the sensor | |||
String description() { | |||
char buffer[25]; | |||
snprintf(buffer, sizeof(buffer), "ADE7953 @ I2C (0x%02X)", _address); | |||
return String(buffer); | |||
} | |||
// Descriptive name of the slot # index | |||
String slot(unsigned char index) { | |||
return description(); | |||
}; | |||
// Type for slot # index | |||
unsigned char type(unsigned char index) { | |||
if (index == 0) return MAGNITUDE_VOLTAGE; | |||
index = index % ADE7953_TOTAL_DEVICES; | |||
if (index == 0) return MAGNITUDE_ENERGY; | |||
if (index == 1) return MAGNITUDE_CURRENT; | |||
if (index == 2) return MAGNITUDE_POWER_ACTIVE; | |||
return MAGNITUDE_NONE; | |||
} | |||
// Pre-read hook (usually to populate registers with up-to-date data) | |||
void pre() { | |||
uint32_t active_power1 = 0; | |||
uint32_t active_power2 = 0; | |||
uint32_t current_rms = 0; | |||
uint32_t current_rms1 = 0; | |||
uint32_t current_rms2 = 0; | |||
uint32_t voltage_rms = 0; | |||
voltage_rms = read(_address, 0x31C); // Both relays | |||
current_rms1 = read(_address, 0x31B); // Relay 1 | |||
if (current_rms1 < 2000) { // No load threshold (20mA) | |||
current_rms1 = 0; | |||
active_power1 = 0; | |||
} else { | |||
active_power1 = (int32_t)read(_address, 0x313) * -1; // Relay 1 | |||
active_power1 = (active_power1 > 0) ? active_power1 : 0; | |||
} | |||
current_rms2 = read(_address, 0x31A); // Relay 2 | |||
if (current_rms2 < 2000) { // No load threshold (20mA) | |||
current_rms2 = 0; | |||
active_power2 = 0; | |||
} else { | |||
active_power2 = (int32_t)read(_address, 0x312); // Relay 2 | |||
active_power2 = (active_power2 > 0) ? active_power2 : 0; | |||
} | |||
_voltage = (float) voltage_rms / ADE7953_UREF; | |||
storeReading( | |||
ADE7953_ALL_RELAYS, | |||
(float)(current_rms1 + current_rms2) / (ADE7953_IREF * 10), | |||
(float)(active_power1 + active_power2) / (ADE7953_PREF / 10) | |||
); | |||
storeReading( | |||
ADE7953_RELAY_1, | |||
(float) current_rms1 / (ADE7953_IREF * 10), | |||
(float) active_power1 / (ADE7953_PREF / 10) | |||
); | |||
storeReading( | |||
ADE7953_RELAY_2, | |||
(float)current_rms2 / (ADE7953_IREF * 10), | |||
(float)active_power2 / (ADE7953_PREF / 10) | |||
); | |||
} | |||
inline void storeReading(unsigned int relay, float current, float power) { | |||
auto& reading_ref = _readings.at(relay); | |||
reading_ref.current = current; | |||
reading_ref.power = power; | |||
static unsigned long last = 0; | |||
if (last > 0) { | |||
reading_ref.energy += (power * (millis() - last) / 1000); | |||
} | |||
last = millis(); | |||
} | |||
// Current value for slot # index | |||
double value(unsigned char index) { | |||
if (index == 0) return _voltage; | |||
int relay = (index - 1) / ADE7953_TOTAL_DEVICES; | |||
index = index % ADE7953_TOTAL_DEVICES; | |||
if (index == 0) return _energy_offsets[relay] + _readings[relay].energy; | |||
if (index == 1) return _readings[relay].current; | |||
if (index == 2) return _readings[relay].power; | |||
return 0; | |||
} | |||
unsigned int getTotalDevices() { | |||
return ADE7953_TOTAL_DEVICES; | |||
} | |||
void resetEnergy(int relay, double value = 0) { | |||
_energy_offsets[relay] = value; | |||
} | |||
protected: | |||
void _init() { | |||
nice_delay(100); // Need 100mS to init ADE7953 | |||
write(_address, 0x102, 0x0004); // Locking the communication interface (Clear bit COMM_LOCK), Enable HPF | |||
write(_address, 0x0FE, 0x00AD); // Unlock register 0x120 | |||
write(_address, 0x120, 0x0030); // Configure optimum setting | |||
_ready = true; | |||
} | |||
#if 0 | |||
static int reg_size(uint16_t reg) { | |||
int size = 0; | |||
switch ((reg >> 8) & 0x0F) { | |||
case 0x03: | |||
size++; | |||
case 0x02: | |||
size++; | |||
case 0x01: | |||
size++; | |||
case 0x00: | |||
case 0x07: | |||
case 0x08: | |||
size++; | |||
} | |||
return size; | |||
} | |||
#else | |||
// Optimized version of the function above, -80 bytes of code | |||
// Use the known property of register addresses to calculate their size | |||
static const int reg_size(const uint16_t reg) { | |||
const uint8_t mask = ((reg >> 8) & 0b1111); | |||
if (!mask || (mask & 0b1100)) { | |||
return 1; | |||
} else if (mask & 0b0011) { | |||
return mask + 1; | |||
} | |||
return 0; | |||
} | |||
#endif | |||
void write(unsigned char address, uint16_t reg, uint32_t val) { | |||
int size = reg_size(reg); | |||
if (size) { | |||
Wire.beginTransmission(address); | |||
Wire.write((reg >> 8) & 0xFF); | |||
Wire.write(reg & 0xFF); | |||
while (size--) { | |||
Wire.write((val >> (8 * size)) & 0xFF); // Write data, MSB first | |||
} | |||
Wire.endTransmission(); | |||
delayMicroseconds(5); // Bus-free time minimum 4.7us | |||
} | |||
} | |||
static uint32_t read(int address, uint16_t reg) { | |||
uint32_t response = 0; | |||
const int size = reg_size(reg); | |||
if (size) { | |||
Wire.beginTransmission(address); | |||
Wire.write((reg >> 8) & 0xFF); | |||
Wire.write(reg & 0xFF); | |||
Wire.endTransmission(0); | |||
Wire.requestFrom(address, size); | |||
if (size <= Wire.available()) { | |||
for (int i = 0; i < size; i++) { | |||
response = response << 8 | Wire.read(); // receive DATA (MSB first) | |||
} | |||
} | |||
} | |||
return response; | |||
} | |||
std::vector<reading_t> _readings; | |||
float _voltage = 0; | |||
std::vector<double> _energy_offsets; | |||
}; | |||
#endif // SENSOR_SUPPORT && ADE7953_SUPPORT |
@ -0,0 +1,42 @@ | |||
// https://github.com root issuer | |||
// Issuer: C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert High Assurance EV Root CA | |||
// Validity | |||
// Not Before: Oct 22 12:00:00 2013 GMT | |||
// Not After : Oct 22 12:00:00 2028 GMT | |||
// Subject: C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert SHA2 Extended Validation Server CA | |||
#pragma once | |||
#include <pgmspace.h> | |||
const char PROGMEM _ssl_digicert_ev_root_ca[] = R"EOF( | |||
-----BEGIN CERTIFICATE----- | |||
MIIEtjCCA56gAwIBAgIQDHmpRLCMEZUgkmFf4msdgzANBgkqhkiG9w0BAQsFADBs | |||
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 | |||
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j | |||
ZSBFViBSb290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowdTEL | |||
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 | |||
LmRpZ2ljZXJ0LmNvbTE0MDIGA1UEAxMrRGlnaUNlcnQgU0hBMiBFeHRlbmRlZCBW | |||
YWxpZGF0aW9uIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC | |||
ggEBANdTpARR+JmmFkhLZyeqk0nQOe0MsLAAh/FnKIaFjI5j2ryxQDji0/XspQUY | |||
uD0+xZkXMuwYjPrxDKZkIYXLBxA0sFKIKx9om9KxjxKws9LniB8f7zh3VFNfgHk/ | |||
LhqqqB5LKw2rt2O5Nbd9FLxZS99RStKh4gzikIKHaq7q12TWmFXo/a8aUGxUvBHy | |||
/Urynbt/DvTVvo4WiRJV2MBxNO723C3sxIclho3YIeSwTQyJ3DkmF93215SF2AQh | |||
cJ1vb/9cuhnhRctWVyh+HA1BV6q3uCe7seT6Ku8hI3UarS2bhjWMnHe1c63YlC3k | |||
8wyd7sFOYn4XwHGeLN7x+RAoGTMCAwEAAaOCAUkwggFFMBIGA1UdEwEB/wQIMAYB | |||
Af8CAQAwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF | |||
BQcDAjA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp | |||
Z2ljZXJ0LmNvbTBLBgNVHR8ERDBCMECgPqA8hjpodHRwOi8vY3JsNC5kaWdpY2Vy | |||
dC5jb20vRGlnaUNlcnRIaWdoQXNzdXJhbmNlRVZSb290Q0EuY3JsMD0GA1UdIAQ2 | |||
MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5j | |||
b20vQ1BTMB0GA1UdDgQWBBQ901Cl1qCt7vNKYApl0yHU+PjWDzAfBgNVHSMEGDAW | |||
gBSxPsNpA/i/RwHUmCYaCALvY2QrwzANBgkqhkiG9w0BAQsFAAOCAQEAnbbQkIbh | |||
hgLtxaDwNBx0wY12zIYKqPBKikLWP8ipTa18CK3mtlC4ohpNiAexKSHc59rGPCHg | |||
4xFJcKx6HQGkyhE6V6t9VypAdP3THYUYUN9XR3WhfVUgLkc3UHKMf4Ib0mKPLQNa | |||
2sPIoc4sUqIAY+tzunHISScjl2SFnjgOrWNoPLpSgVh5oywM395t6zHyuqB8bPEs | |||
1OG9d4Q3A84ytciagRpKkk47RpqF/oOi+Z6Mo8wNXrM9zwR4jxQUezKcxwCmXMS1 | |||
oVWNWlZopCJwqjyBcdmdqEU79OX2olHdx3ti6G8MdOu42vi/hw15UJGQmxg7kVkn | |||
8TUoE6smftX3eg== | |||
-----END CERTIFICATE----- | |||
)EOF"; |
@ -0,0 +1,94 @@ | |||
// ISRG Root X1 (self-signed) | |||
// from https://letsencrypt.org/certs/isrgrootx1.pem.txt | |||
// Note: LetsEncrypt will only start using this root certificate to sign | |||
// certificates after July 8, 2020. Any certificate issued before this date | |||
// uses the X3 intermediate certificate down below. | |||
// See: https://letsencrypt.org/2019/04/15/transitioning-to-isrg-root.html | |||
// Issuer: C = US, O = Internet Security Research Group, CN = ISRG Root X1 | |||
// Validity | |||
// Not Before: Jun 4 11:04:38 2015 GMT | |||
// Not After : Jun 4 11:04:38 2035 GMT | |||
// Subject: C = US, O = Internet Security Research Group, CN = ISRG Root X1 | |||
#pragma once | |||
#include <pgmspace.h> | |||
const char PROGMEM _ssl_letsencrypt_isrg_x1_ca[] = R"EOF( | |||
-----BEGIN CERTIFICATE----- | |||
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw | |||
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh | |||
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 | |||
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu | |||
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY | |||
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc | |||
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ | |||
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U | |||
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW | |||
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH | |||
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC | |||
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv | |||
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn | |||
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn | |||
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw | |||
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI | |||
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV | |||
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq | |||
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL | |||
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ | |||
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK | |||
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 | |||
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur | |||
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC | |||
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc | |||
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq | |||
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA | |||
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d | |||
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= | |||
-----END CERTIFICATE----- | |||
)EOF"; | |||
// Let’s Encrypt Authority X3 (Signed by ISRG Root X1) | |||
// from https://letsencrypt.org/certs/letsencryptauthorityx3.pem.txt | |||
// Issuer: C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3 | |||
// Validity | |||
// Not Before: Oct 6 15:43:55 2016 GMT | |||
// Not After : Oct 6 15:43:55 2021 GMT | |||
const char PROGMEM _ssl_letsencrypt_isrg_x3_ca[] = R"EOF( | |||
-----BEGIN CERTIFICATE----- | |||
MIIFjTCCA3WgAwIBAgIRANOxciY0IzLc9AUoUSrsnGowDQYJKoZIhvcNAQELBQAw | |||
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh | |||
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTYxMDA2MTU0MzU1 | |||
WhcNMjExMDA2MTU0MzU1WjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg | |||
RW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDMwggEi | |||
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc0wzwWuUuR7dyXTeDs2hjMOrX | |||
NSYZJeG9vjXxcJIvt7hLQQWrqZ41CFjssSrEaIcLo+N15Obzp2JxunmBYB/XkZqf | |||
89B4Z3HIaQ6Vkc/+5pnpYDxIzH7KTXcSJJ1HG1rrueweNwAcnKx7pwXqzkrrvUHl | |||
Npi5y/1tPJZo3yMqQpAMhnRnyH+lmrhSYRQTP2XpgofL2/oOVvaGifOFP5eGr7Dc | |||
Gu9rDZUWfcQroGWymQQ2dYBrrErzG5BJeC+ilk8qICUpBMZ0wNAxzY8xOJUWuqgz | |||
uEPxsR/DMH+ieTETPS02+OP88jNquTkxxa/EjQ0dZBYzqvqEKbbUC8DYfcOTAgMB | |||
AAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBU | |||
BgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIB | |||
FiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBSo | |||
SmpjBH3duubRObemRWXv86jsoTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js | |||
LnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEF | |||
BQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsG | |||
AQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYD | |||
VR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIB | |||
ABnPdSA0LTqmRf/Q1eaM2jLonG4bQdEnqOJQ8nCqxOeTRrToEKtwT++36gTSlBGx | |||
A/5dut82jJQ2jxN8RI8L9QFXrWi4xXnA2EqA10yjHiR6H9cj6MFiOnb5In1eWsRM | |||
UM2v3e9tNsCAgBukPHAg1lQh07rvFKm/Bz9BCjaxorALINUfZ9DD64j2igLIxle2 | |||
DPxW8dI/F2loHMjXZjqG8RkqZUdoxtID5+90FgsGIfkMpqgRS05f4zPbCEHqCXl1 | |||
eO5HyELTgcVlLXXQDgAWnRzut1hFJeczY1tjQQno6f6s+nMydLN26WuU4s3UYvOu | |||
OsUxRlJu7TSRHqDC3lSE5XggVkzdaPkuKGQbGpny+01/47hfXXNB7HntWNZ6N2Vw | |||
p7G6OfY+YQrZwIaQmhrIqJZuigsrbe3W+gdn5ykE9+Ky0VgVUsfxo52mwFYs1JKY | |||
2PGDuWx8M6DlS6qQkvHaRUo0FMd8TsSlbF0/v965qGFKhSDeQoMpYnwcmQilRh/0 | |||
ayLThlHLN81gSkJjVrPI0Y8xCVPB4twb1PFUd2fPM3sA1tJ83sZ5v8vgFv2yofKR | |||
PB0t6JzUA81mSqM3kxl5e+IZwhYAyO0OTg3/fs8HqGTNKd9BqoUwSRBzp06JMg5b | |||
rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt | |||
-----END CERTIFICATE----- | |||
)EOF"; |
@ -0,0 +1,90 @@ | |||
from __future__ import print_function | |||
Import("env") | |||
import os | |||
import sys | |||
TRAVIS = os.environ.get("TRAVIS") | |||
PIO_PLATFORM = env.PioPlatform() | |||
CONFIG = env.GetProjectConfig() | |||
class ExtraScriptError(Exception): | |||
pass | |||
# Most portable way, without depending on platformio internals | |||
def subprocess_libdeps(lib_deps, storage=None, silent=True): | |||
import subprocess | |||
args = [env.subst("$PYTHONEXE"), "-mplatformio", "lib"] | |||
if not storage: | |||
args.append("-g") | |||
else: | |||
args.extend(["-d", storage]) | |||
args.append("install") | |||
if silent: | |||
args.append("-s") | |||
args.extend(lib_deps) | |||
subprocess.check_call(args) | |||
# Avoid spawning pio lib every time, hook into the LibraryManager API (sort-of internal) | |||
def library_manager_libdeps(lib_deps, storage=None): | |||
from platformio.managers.lib import LibraryManager | |||
from platformio.project.helpers import get_project_global_lib_dir | |||
if not storage: | |||
manager = LibraryManager(get_project_global_lib_dir()) | |||
else: | |||
manager = LibraryManager(storage) | |||
for lib in lib_deps: | |||
if manager.get_package_dir(*manager.parse_pkg_uri(lib)): | |||
continue | |||
print("installing: {}".format(lib), file=sys.stderr) | |||
manager.install(lib) | |||
def get_shared_libdeps_dir(section, name): | |||
if not CONFIG.has_option(section, name): | |||
raise ExtraScriptError("{}.{} is required to be set".format(section, name)) | |||
opt = CONFIG.get(section, name) | |||
if not opt in env.GetProjectOption("lib_extra_dirs"): | |||
raise ExtraScriptError( | |||
"lib_extra_dirs must contain {}.{}".format(section, name) | |||
) | |||
return os.path.join(env["PROJECT_DIR"], opt) | |||
def ensure_platform_updated(): | |||
try: | |||
if PIO_PLATFORM.are_outdated_packages(): | |||
print("updating platform packages", file=sys.stderr) | |||
PIO_PLATFORM.update_packages() | |||
except Exception: | |||
print("Warning: no connection, cannot check for outdated packages", file=sys.stderr) | |||
# latest toolchain is still optional with PIO (TODO: recheck after 2.6.0!) | |||
# also updates arduino core git to the latest master commit | |||
if TRAVIS and (env.GetProjectOption("platform") == CONFIG.get("common", "arduino_core_git")): | |||
ensure_platform_updated() | |||
# to speed-up build process, install libraries in either global or local shared storage | |||
if os.environ.get("ESPURNA_PIO_SHARED_LIBRARIES"): | |||
if TRAVIS: | |||
storage = None | |||
print("using global library storage", file=sys.stderr) | |||
else: | |||
storage = get_shared_libdeps_dir("common", "shared_libdeps_dir") | |||
print("using shared library storage: ", storage, file=sys.stderr) | |||
subprocess_libdeps(env.GetProjectOption("lib_deps"), storage) |
@ -1,160 +0,0 @@ | |||
/** | |||
* jQuery Wheel Color Picker | |||
* Base Stylesheet | |||
* | |||
* http://www.jar2.net/projects/jquery-wheelcolorpicker | |||
* | |||
* Copyright © 2011-2016 Fajar Chandra. All rights reserved. | |||
* Released under MIT License. | |||
* http://www.opensource.org/licenses/mit-license.php | |||
* | |||
* Note: Width, height, left, and top properties are handled by the | |||
* plugin. These values might change on the fly. | |||
*/ | |||
.jQWCP-wWidget { | |||
position: absolute; | |||
width: 250px; | |||
height: 180px; | |||
background: #eee; | |||
box-shadow: 1px 1px 4px rgba(0,0,0,.5); | |||
border-radius: 4px; | |||
border: solid 1px #aaa; | |||
padding: 10px; | |||
z-index: 1001; | |||
} | |||
.jQWCP-wWidget.jQWCP-block { | |||
position: relative; | |||
border-color: #aaa; | |||
box-shadow: inset 1px 1px 1px #ccc; | |||
} | |||
.jQWCP-wWheel { | |||
background-repeat: no-repeat; | |||
background-position: center; | |||
background-size: contain; | |||
position: relative; | |||
float: left; | |||
width: 180px; | |||
height: 180px; | |||
-webkit-border-radius: 90px; | |||
-moz-border-radius: 50%; | |||
border-radius: 50%; | |||
border: solid 1px #aaa; | |||
margin: -1px; | |||
margin-right: 10px; | |||
transition: border .15s; | |||
cursor: crosshair; | |||
} | |||
.jQWCP-wWheel:hover { | |||
border-color: #666; | |||
} | |||
.jQWCP-wWheelOverlay { | |||
position: absolute; | |||
top: 0; | |||
left: 0; | |||
width: 100%; | |||
height: 100%; | |||
background: #000; | |||
opacity: 0; | |||
-webkit-border-radius: 90px; | |||
-moz-border-radius: 50%; | |||
border-radius: 50%; | |||
} | |||
.jQWCP-wWheelCursor { | |||
width: 8px; | |||
height: 8px; | |||
position: absolute; | |||
top: 50%; | |||
left: 50%; | |||
margin: -6px -6px; | |||
cursor: crosshair; | |||
border: solid 2px #fff; | |||
box-shadow: 1px 1px 2px #000; | |||
border-radius: 50%; | |||
} | |||
.jQWCP-slider-wrapper, | |||
.jQWCP-wPreview { | |||
position: relative; | |||
width: 20px; | |||
height: 180px; | |||
float: left; | |||
margin-right: 10px; | |||
} | |||
.jQWCP-wWheel:last-child, | |||
.jQWCP-slider-wrapper:last-child, | |||
.jQWCP-wPreview:last-child { | |||
margin-right: 0; | |||
} | |||
.jQWCP-slider, | |||
.jQWCP-wPreviewBox { | |||
position: absolute; | |||
width: 100%; | |||
height: 100%; | |||
left: 0; | |||
top: 0; | |||
box-sizing: border-box; | |||
border: solid 1px #aaa; | |||
margin: -1px; | |||
-moz-border-radius: 4px; | |||
border-radius: 4px; | |||
transition: border .15s; | |||
} | |||
.jQWCP-slider { | |||
cursor: crosshair; | |||
} | |||
.jQWCP-slider-wrapper:hover .jQWCP-slider { | |||
border-color: #666; | |||
} | |||
.jQWCP-scursor { | |||
position: absolute; | |||
left: 0; | |||
top: 0; | |||
right: 0; | |||
height: 6px; | |||
margin: -5px -1px -5px -3px; | |||
cursor: crosshair; | |||
border: solid 2px #fff; | |||
box-shadow: 1px 1px 2px #000; | |||
border-radius: 4px; | |||
} | |||
.jQWCP-wAlphaSlider, | |||
.jQWCP-wPreviewBox { | |||
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEVAQEB/f39eaJUuAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QYRBDgK9dKdMgAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAARSURBVAjXY/jPwIAVYRf9DwB+vw/x6vMT1wAAAABJRU5ErkJggg==') center center; | |||
} | |||
.jQWCP-overlay { | |||
position: fixed; | |||
top: 0; | |||
left: 0; | |||
bottom: 0; | |||
right: 0; | |||
z-index: 1000; | |||
} | |||
/*********************/ | |||
/* Mobile layout */ | |||
.jQWCP-mobile.jQWCP-wWidget { | |||
position: fixed; | |||
bottom: 0; | |||
left: 0 !important; | |||
top: auto !important; | |||
width: 100%; | |||
height: 75%; | |||
max-height: 240px; | |||
box-sizing: border-box; | |||
border-radius: 0; | |||
} |
@ -0,0 +1,161 @@ | |||
/** | |||
* Wheel Color Picker for jQuery | |||
* Base Stylesheet | |||
* | |||
* https://raffer.one/projects/jquery-wheelcolorpicker | |||
* | |||
* Copyright © 2011-2019 Fajar Chandra. All rights reserved. | |||
* Released under MIT License. | |||
* http://www.opensource.org/licenses/mit-license.php | |||
* | |||
* Note: Width, height, left, and top properties are handled by the | |||
* plugin. These values might change on the fly. | |||
*/ | |||
.jQWCP-wWidget { | |||
position: absolute; | |||
width: 250px; | |||
height: 180px; | |||
background: #eee; | |||
box-shadow: 1px 1px 4px rgba(0,0,0,.5); | |||
border-radius: 4px; | |||
border: solid 1px #aaa; | |||
padding: 10px; | |||
z-index: 1001; | |||
touch-action: none; | |||
} | |||
.jQWCP-wWidget.jQWCP-block { | |||
position: relative; | |||
border-color: #aaa; | |||
box-shadow: inset 1px 1px 1px #ccc; | |||
} | |||
.jQWCP-wWheel { | |||
background-repeat: no-repeat; | |||
background-position: center; | |||
background-size: contain; | |||
position: relative; | |||
float: left; | |||
width: 180px; | |||
height: 180px; | |||
-webkit-border-radius: 90px; | |||
-moz-border-radius: 50%; | |||
border-radius: 50%; | |||
border: solid 1px #aaa; | |||
margin: -1px; | |||
margin-right: 10px; | |||
transition: border .15s; | |||
cursor: crosshair; | |||
} | |||
.jQWCP-wWheel:hover { | |||
border-color: #666; | |||
} | |||
.jQWCP-wWheelOverlay { | |||
position: absolute; | |||
top: 0; | |||
left: 0; | |||
width: 100%; | |||
height: 100%; | |||
background: #000; | |||
opacity: 0; | |||
-webkit-border-radius: 90px; | |||
-moz-border-radius: 50%; | |||
border-radius: 50%; | |||
} | |||
.jQWCP-wWheelCursor { | |||
width: 8px; | |||
height: 8px; | |||
position: absolute; | |||
top: 50%; | |||
left: 50%; | |||
margin: -6px -6px; | |||
cursor: crosshair; | |||
border: solid 2px #fff; | |||
box-shadow: 1px 1px 2px #000; | |||
border-radius: 50%; | |||
} | |||
.jQWCP-slider-wrapper, | |||
.jQWCP-wPreview { | |||
position: relative; | |||
width: 20px; | |||
height: 180px; | |||
float: left; | |||
margin-right: 10px; | |||
} | |||
.jQWCP-wWheel:last-child, | |||
.jQWCP-slider-wrapper:last-child, | |||
.jQWCP-wPreview:last-child { | |||
margin-right: 0; | |||
} | |||
.jQWCP-slider, | |||
.jQWCP-wPreviewBox { | |||
position: absolute; | |||
width: 100%; | |||
height: 100%; | |||
left: 0; | |||
top: 0; | |||
box-sizing: border-box; | |||
border: solid 1px #aaa; | |||
margin: -1px; | |||
-moz-border-radius: 4px; | |||
border-radius: 4px; | |||
transition: border .15s; | |||
} | |||
.jQWCP-slider { | |||
cursor: crosshair; | |||
} | |||
.jQWCP-slider-wrapper:hover .jQWCP-slider { | |||
border-color: #666; | |||
} | |||
.jQWCP-scursor { | |||
position: absolute; | |||
left: 0; | |||
top: 0; | |||
right: 0; | |||
height: 6px; | |||
margin: -5px -1px -5px -3px; | |||
cursor: crosshair; | |||
border: solid 2px #fff; | |||
box-shadow: 1px 1px 2px #000; | |||
border-radius: 4px; | |||
} | |||
.jQWCP-wAlphaSlider, | |||
.jQWCP-wPreviewBox { | |||
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEVAQEB/f39eaJUuAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QYRBDgK9dKdMgAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAARSURBVAjXY/jPwIAVYRf9DwB+vw/x6vMT1wAAAABJRU5ErkJggg==') center center; | |||
} | |||
.jQWCP-overlay { | |||
position: fixed; | |||
top: 0; | |||
left: 0; | |||
bottom: 0; | |||
right: 0; | |||
z-index: 1000; | |||
} | |||
/*********************/ | |||
/* Mobile layout */ | |||
.jQWCP-mobile.jQWCP-wWidget { | |||
position: fixed; | |||
bottom: 0; | |||
left: 0 !important; | |||
top: auto !important; | |||
width: 100%; | |||
height: 75%; | |||
max-height: 240px; | |||
box-sizing: border-box; | |||
border-radius: 0; | |||
} |
@ -0,0 +1 @@ | |||
Shared lib_deps storage, see code/extra_script_libdeps.py |
@ -0,0 +1,39 @@ | |||
#version=2.3.0 | |||
menu.float_support=scanf and printf float support | |||
generic.menu.FlashSize.1M1S=1M (1 EEPROM Sector, no SPIFFS) | |||
generic.menu.FlashSize.1M1S.build.flash_size=1M | |||
generic.menu.FlashSize.1M1S.build.flash_size_bytes=0x100000 | |||
generic.menu.FlashSize.1M1S.build.flash_ld=eagle.flash.1m0m1s.ld | |||
generic.menu.FlashSize.1M1S.build.spiffs_pagesize=256 | |||
generic.menu.FlashSize.1M1S.upload.maximum_size=1023984 | |||
generic.menu.FlashSize.1M1S.build.rfcal_addr=0xFC000 | |||
generic.menu.FlashSize.2M4S=2M (4 EEPROM Sectors, 1M SPIFFS) | |||
generic.menu.FlashSize.2M4S.build.flash_size=2M | |||
generic.menu.FlashSize.2M4S.build.flash_size_bytes=0x200000 | |||
generic.menu.FlashSize.2M4S.build.flash_ld=eagle.flash.2m1m4s.ld | |||
generic.menu.FlashSize.2M4S.build.spiffs_pagesize=256 | |||
generic.menu.FlashSize.2M4S.upload.maximum_size=1044464 | |||
generic.menu.FlashSize.2M4S.build.rfcal_addr=0x1FC000 | |||
generic.menu.FlashSize.4M1M4S=4M (4 EEPROM Sectors, 1M SPIFFS) | |||
generic.menu.FlashSize.4M1M4S.build.flash_size=4M | |||
generic.menu.FlashSize.4M1M4S.build.flash_size_bytes=0x400000 | |||
generic.menu.FlashSize.4M1M4S.build.flash_ld=eagle.flash.4m1m4s.ld | |||
generic.menu.FlashSize.4M1M4S.build.spiffs_pagesize=256 | |||
generic.menu.FlashSize.4M1M4S.upload.maximum_size=1044464 | |||
generic.menu.FlashSize.4M1M4S.build.rfcal_addr=0x3FC000 | |||
generic.menu.FlashSize.4M3M4S=4M (4 EEPROM Sectors, 3M SPIFFS) | |||
generic.menu.FlashSize.4M3M4S.build.flash_size=4M | |||
generic.menu.FlashSize.4M3M4S.build.flash_size_bytes=0x400000 | |||
generic.menu.FlashSize.4M3M4S.build.flash_ld=eagle.flash.4m3m4s.ld | |||
generic.menu.FlashSize.4M3M4S.build.spiffs_pagesize=256 | |||
generic.menu.FlashSize.4M3M4S.upload.maximum_size=1044464 | |||
generic.menu.FlashSize.4M3M4S.build.rfcal_addr=0x3FC000 | |||
generic.menu.float_support.disabled=Disabled (Recommended) | |||
generic.menu.float_support.disabled.build.float= | |||
generic.menu.float_support.enabled=Enabled | |||
generic.menu.float_support.enabled.build.float=-u _printf_float -u _scanf_float | |||
generic.compiler.cpp.extra_flags=-DNO_GLOBAL_EEPROM -DMQTT_MAX_PACKET_SIZE=1024 |
@ -0,0 +1,24 @@ | |||
# boards.local.txt for ESPurna | |||
Additional flash layouts to support multiple EEPROM sectors | |||
### Installation | |||
Place boards.local.txt into Arduino hardware directory, in the same directory as the boards.txt file. Depending on platform and ESP8266 installation method, it is one of: | |||
- Linux (boards manager): `~/.arduino15/packages/esp8266/hardware/esp8266/<version>` | |||
- Linux (git): `~/Arduino/hardware/esp8266com/esp8266/` | |||
- Windows (boards manager): `%LOCALAPPDATA%\Arduino15\packages\esp8266\hardware\esp8266\<version>` | |||
- Windows (git): `~\Documents\Arduino\hardware\esp8266com\esp8266\` | |||
- macOS (boards manager): `~/Library/Arduino15/packages/esp2866/hardware/esp8266/<version>` | |||
- macOS (git): `<application-directory>/Arduino.app/Contents/Java/hardware/esp8266com/esp8266` | |||
Use `2.3.0/boards.local.txt` for Core version 2.3.0 | |||
Use `latest/boards.local.txt` for all the others | |||
### Arduino documentation | |||
https://arduino-esp8266.readthedocs.io/en/latest/installing.html | |||
https://www.arduino.cc/en/Hacking/Preferences | |||
https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5-3rd-party-Hardware-specification#boardslocaltxt |
@ -0,0 +1,39 @@ | |||
#version=latest | |||
menu.float_support=scanf and printf float support | |||
generic.menu.eesz.1M1S=1M (1 EEPROM Sector, no SPIFFS) | |||
generic.menu.eesz.1M1S.build.flash_size=1M | |||
generic.menu.eesz.1M1S.build.flash_size_bytes=0x100000 | |||
generic.menu.eesz.1M1S.build.flash_ld=eagle.flash.1m0m1s.ld | |||
generic.menu.eesz.1M1S.build.spiffs_pagesize=256 | |||
generic.menu.eesz.1M1S.upload.maximum_size=1023984 | |||
generic.menu.eesz.1M1S.build.rfcal_addr=0xFC000 | |||
generic.menu.eesz.2M4S=2M (4 EEPROM Sectors, 1M SPIFFS) | |||
generic.menu.eesz.2M4S.build.flash_size=2M | |||
generic.menu.eesz.2M4S.build.flash_size_bytes=0x200000 | |||
generic.menu.eesz.2M4S.build.flash_ld=eagle.flash.2m1m4s.ld | |||
generic.menu.eesz.2M4S.build.spiffs_pagesize=256 | |||
generic.menu.eesz.2M4S.upload.maximum_size=1044464 | |||
generic.menu.eesz.2M4S.build.rfcal_addr=0x1FC000 | |||
generic.menu.eesz.4M1M4S=4M (4 EEPROM Sectors, 1M SPIFFS) | |||
generic.menu.eesz.4M1M4S.build.flash_size=4M | |||
generic.menu.eesz.4M1M4S.build.flash_size_bytes=0x400000 | |||
generic.menu.eesz.4M1M4S.build.flash_ld=eagle.flash.4m1m4s.ld | |||
generic.menu.eesz.4M1M4S.build.spiffs_pagesize=256 | |||
generic.menu.eesz.4M1M4S.upload.maximum_size=1044464 | |||
generic.menu.eesz.4M1M4S.build.rfcal_addr=0x3FC000 | |||
generic.menu.eesz.4M3M4S=4M (4 EEPROM Sectors, 3M SPIFFS) | |||
generic.menu.eesz.4M3M4S.build.flash_size=4M | |||
generic.menu.eesz.4M3M4S.build.flash_size_bytes=0x400000 | |||
generic.menu.eesz.4M3M4S.build.flash_ld=eagle.flash.4m3m4s.ld | |||
generic.menu.eesz.4M3M4S.build.spiffs_pagesize=256 | |||
generic.menu.eesz.4M3M4S.upload.maximum_size=1044464 | |||
generic.menu.eesz.4M3M4S.build.rfcal_addr=0x3FC000 | |||
generic.menu.float_support.disabled=Disabled (Recommended) | |||
generic.menu.float_support.disabled.build.float= | |||
generic.menu.float_support.enabled=Enabled | |||
generic.menu.float_support.enabled.build.float=-u _printf_float -u _scanf_float | |||
generic.compiler.cpp.extra_flags=-DNO_GLOBAL_EEPROM -DMQTT_MAX_PACKET_SIZE=1024 |
@ -0,0 +1,187 @@ | |||
#!/usr/bin/env python | |||
# adapted boards.txt.py from esp8266/Arduino | |||
# - single board definition, ldscripts | |||
# - portable boards.local.txt for 2.3.0 and up | |||
import os | |||
import argparse | |||
import sys | |||
import collections | |||
# TODO: drop after platform.io supports python 3 | |||
if sys.version < (3, 2): | |||
import string | |||
_format = string.Formatter().vformat | |||
def format_map(tmpl, f_map): | |||
return _format(tmpl, None, f_map) | |||
else: | |||
def format_map(tmpl, f_map): | |||
return tmpl.format_map(f_map) | |||
class VersionedSubstitution(collections.MutableMapping): | |||
def __init__(self, substitutions, targets, *args, **kwargs): | |||
self._targets = targets | |||
self._version = None | |||
self._store = substitutions.copy() | |||
self.update(dict(*args, **kwargs)) | |||
def set_version(self, version): | |||
self._version = version | |||
def __getitem__(self, key): | |||
if self._version in self._targets: | |||
return self._store[key] | |||
return key | |||
def __setitem__(self, key, value): | |||
self._store[key] = value | |||
def __delitem__(self, key): | |||
del self._store[key] | |||
def __iter__(self): | |||
return iter(self._store) | |||
def __len__(self): | |||
return len(self._store) | |||
BOARDS_LOCAL = { | |||
"global": collections.OrderedDict( | |||
[("menu.float_support", "scanf and printf float support")] | |||
), | |||
"flash_size": collections.OrderedDict( | |||
[ | |||
(".menu.{eesz}.1M1S", "1M (1 EEPROM Sector, no SPIFFS)"), | |||
(".menu.{eesz}.1M1S.build.flash_size", "1M"), | |||
(".menu.{eesz}.1M1S.build.flash_size_bytes", "0x100000"), | |||
(".menu.{eesz}.1M1S.build.flash_ld", "eagle.flash.1m0m1s.ld"), | |||
(".menu.{eesz}.1M1S.build.spiffs_pagesize", "256"), | |||
(".menu.{eesz}.1M1S.upload.maximum_size", "1023984"), | |||
(".menu.{eesz}.1M1S.build.rfcal_addr", "0xFC000"), | |||
(".menu.{eesz}.2M4S", "2M (4 EEPROM Sectors, 1M SPIFFS)"), | |||
(".menu.{eesz}.2M4S.build.flash_size", "2M"), | |||
(".menu.{eesz}.2M4S.build.flash_size_bytes", "0x200000"), | |||
(".menu.{eesz}.2M4S.build.flash_ld", "eagle.flash.2m1m4s.ld"), | |||
(".menu.{eesz}.2M4S.build.spiffs_pagesize", "256"), | |||
(".menu.{eesz}.2M4S.upload.maximum_size", "1044464"), | |||
(".menu.{eesz}.2M4S.build.rfcal_addr", "0x1FC000"), | |||
(".menu.{eesz}.4M1M4S", "4M (4 EEPROM Sectors, 1M SPIFFS)"), | |||
(".menu.{eesz}.4M1M4S.build.flash_size", "4M"), | |||
(".menu.{eesz}.4M1M4S.build.flash_size_bytes", "0x400000"), | |||
(".menu.{eesz}.4M1M4S.build.flash_ld", "eagle.flash.4m1m4s.ld"), | |||
(".menu.{eesz}.4M1M4S.build.spiffs_pagesize", "256"), | |||
(".menu.{eesz}.4M1M4S.upload.maximum_size", "1044464"), | |||
(".menu.{eesz}.4M1M4S.build.rfcal_addr", "0x3FC000"), | |||
(".menu.{eesz}.4M3M4S", "4M (4 EEPROM Sectors, 3M SPIFFS)"), | |||
(".menu.{eesz}.4M3M4S.build.flash_size", "4M"), | |||
(".menu.{eesz}.4M3M4S.build.flash_size_bytes", "0x400000"), | |||
(".menu.{eesz}.4M3M4S.build.flash_ld", "eagle.flash.4m3m4s.ld"), | |||
(".menu.{eesz}.4M3M4S.build.spiffs_pagesize", "256"), | |||
(".menu.{eesz}.4M3M4S.upload.maximum_size", "1044464"), | |||
(".menu.{eesz}.4M3M4S.build.rfcal_addr", "0x3FC000"), | |||
] | |||
), | |||
"float_support": collections.OrderedDict( | |||
[ | |||
(".menu.float_support.disabled", "Disabled (Recommended)"), | |||
(".menu.float_support.disabled.build.float", ""), | |||
(".menu.float_support.enabled", "Enabled"), | |||
( | |||
".menu.float_support.enabled.build.float", | |||
"-u _printf_float -u _scanf_float", | |||
), | |||
] | |||
), | |||
} | |||
BOARD = "generic" | |||
MENUS = ["flash_size", "float_support"] | |||
CORE_VERSIONS = ["2.3.0", "latest"] | |||
EXTRA_FLAGS = [ | |||
(".compiler.cpp.extra_flags", "-DNO_GLOBAL_EEPROM -DMQTT_MAX_PACKET_SIZE=1024") | |||
] | |||
SUBSTITUTIONS = VersionedSubstitution( | |||
dict(eesz="FlashSize", wipe="FlashErase", baud="UploadSpeed", vt="VTable"), | |||
["2.3.0"], | |||
) | |||
def generate_boards_txt(args, sub=SUBSTITUTIONS): | |||
versions = args.versions | |||
if args.version: | |||
versions = [args.version] | |||
for version in versions: | |||
sub.set_version(version) | |||
result = ["#version={}\n\n".format(version)] | |||
result.extend("{}={}\n".format(k, v) for k, v in BOARDS_LOCAL["global"].items()) | |||
result.append("\n") | |||
# print("{} unused:".format(version, set(BOARDS_LOCAL.keys()) - set(MENUS[version]))) | |||
# continue | |||
for menu in MENUS: | |||
section = [] | |||
for k, v in BOARDS_LOCAL[menu].items(): | |||
k = format_map(k, sub) | |||
section.append(BOARD + "=".join([k, v])) | |||
result.append("\n".join(section)) | |||
result.append("\n\n") | |||
if EXTRA_FLAGS: | |||
result.extend(("{}{}={}".format(BOARD, k, v)) for k, v in EXTRA_FLAGS) | |||
f_path = os.path.join(args.directory, version, "boards.local.txt") | |||
f_dir, _ = os.path.split(f_path) | |||
if not os.path.exists(f_dir): | |||
os.makedirs(f_dir) | |||
with open(f_path, "w") as f: | |||
for part in result: | |||
f.write(part) | |||
f.write("\n") | |||
def print_versions(args): | |||
for version in CORE_VERSIONS: | |||
print("- {}".format(version)) | |||
if __name__ == "__main__": | |||
parser = argparse.ArgumentParser() | |||
parser.add_argument( | |||
"-d", | |||
"--directory", | |||
default=os.path.join( | |||
os.path.dirname(os.path.realpath(__file__)), "arduino_ide" | |||
), | |||
) | |||
subparsers = parser.add_subparsers(title="commands") | |||
parser_versions = subparsers.add_parser("versions", help="list supported versions") | |||
parser_versions.set_defaults(command=print_versions) | |||
parser_generate = subparsers.add_parser("generate", help="") | |||
parser_generate.add_argument("version", nargs="?") | |||
parser_generate.set_defaults(command=generate_boards_txt, versions=CORE_VERSIONS) | |||
args = parser.parse_args() | |||
args.command(args) |
@ -0,0 +1,8 @@ | |||
# ESP8266 linker scripts with additional EEPROM sectors | |||
### Installation | |||
Depending on ESP8266 version, use files from either `Pre 2.5.0` or `latest` | |||
Reference [../arduino_ide/README.md](../arduino_ide/README.md) about ESP8266 package location | |||
Copy all \*.ld files into the `<esp8266-package>/tools/sdk/ld` directory |
@ -0,0 +1,30 @@ | |||
/* | |||
sketch: 999KB | |||
fs: 0KB | |||
eeprom: 4KB | |||
*/ | |||
MEMORY | |||
{ | |||
dport0_0_seg : org = 0x3FF00000, len = 0x10 | |||
dram0_0_seg : org = 0x3FFE8000, len = 0x14000 | |||
iram1_0_seg : org = 0x40100000, len = 0x8000 | |||
irom0_0_seg : org = 0x40201010, len = 0xf9ff0 | |||
} | |||
/* | |||
Provide both _SPIFFS_ and _FS_ to be compatible with 2.3.0...2.6.0+ and | |||
any library that is using old _SPIFFS_... | |||
*/ | |||
PROVIDE ( _SPIFFS_start = 0x402fb000 ); | |||
PROVIDE ( _SPIFFS_end = 0x402fb000 ); | |||
PROVIDE ( _SPIFFS_page = 0x0 ); | |||
PROVIDE ( _SPIFFS_block = 0x0 ); | |||
PROVIDE ( _FS_start = _SPIFFS_start ); | |||
PROVIDE ( _FS_end = _SPIFFS_end ); | |||
PROVIDE ( _FS_page = _SPIFFS_page ); | |||
PROVIDE ( _FS_block = _SPIFFS_block ); | |||
INCLUDE "local.eagle.app.v6.common.ld" |
@ -0,0 +1,30 @@ | |||
/* | |||
sketch: 995KB | |||
fs: 0KB | |||
eeprom: 8KB | |||
*/ | |||
MEMORY | |||
{ | |||
dport0_0_seg : org = 0x3FF00000, len = 0x10 | |||
dram0_0_seg : org = 0x3FFE8000, len = 0x14000 | |||
iram1_0_seg : org = 0x40100000, len = 0x8000 | |||
irom0_0_seg : org = 0x40201010, len = 0xf8ff0 | |||
} | |||
/* | |||
Provide both _SPIFFS_ and _FS_ to be compatible with 2.3.0...2.6.0+ and | |||
any library that is using old _SPIFFS_... | |||
*/ | |||
PROVIDE ( _SPIFFS_start = 0x402fa000 ); | |||
PROVIDE ( _SPIFFS_end = 0x402fa000 ); | |||
PROVIDE ( _SPIFFS_page = 0x0 ); | |||
PROVIDE ( _SPIFFS_block = 0x0 ); | |||
PROVIDE ( _FS_start = _SPIFFS_start ); | |||
PROVIDE ( _FS_end = _SPIFFS_end ); | |||
PROVIDE ( _FS_page = _SPIFFS_page ); | |||
PROVIDE ( _FS_block = _SPIFFS_block ); | |||
INCLUDE "local.eagle.app.v6.common.ld" |
@ -0,0 +1,30 @@ | |||
/* | |||
sketch: 1019KB | |||
fs: 992KB | |||
eeprom: 16KB | |||
*/ | |||
MEMORY | |||
{ | |||
dport0_0_seg : org = 0x3FF00000, len = 0x10 | |||
dram0_0_seg : org = 0x3FFE8000, len = 0x14000 | |||
iram1_0_seg : org = 0x40100000, len = 0x8000 | |||
irom0_0_seg : org = 0x40201010, len = 0xfeff0 | |||
} | |||
/* | |||
Provide both _SPIFFS_ and _FS_ to be compatible with 2.3.0...2.6.0+ and | |||
any library that is using old _SPIFFS_... | |||
*/ | |||
PROVIDE ( _SPIFFS_start = 0x40300000 ); | |||
PROVIDE ( _SPIFFS_end = 0x403f8000 ); | |||
PROVIDE ( _SPIFFS_page = 0x100 ); | |||
PROVIDE ( _SPIFFS_block = 0x2000 ); | |||
PROVIDE ( _FS_start = _SPIFFS_start ); | |||
PROVIDE ( _FS_end = _SPIFFS_end ); | |||
PROVIDE ( _FS_page = _SPIFFS_page ); | |||
PROVIDE ( _FS_block = _SPIFFS_block ); | |||
INCLUDE "local.eagle.app.v6.common.ld" |