Merge branch 'development' of https://github.com/arendst/Sonoff-Tasmota into development

This commit is contained in:
Stephan Hadinger 2019-04-18 21:55:32 +02:00
commit 99a4556bab
37 changed files with 489 additions and 190 deletions

8
lib/Joba_Tsl2561-2.0.10/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
.pioenvs
.piolibdeps
.clang_complete
.gcc-flags.json
examples/*/platformio.ini
examples/*/lib
examples/*/.gitignore
examples/*/.travis.yml

View File

@ -0,0 +1,4 @@
.pioenvs
.piolibdeps
.clang_complete
.gcc-flags.json

View File

@ -0,0 +1,55 @@
# Continuous Integration (CI) is the practice, in software
# engineering, of merging all developer working copies with a shared mainline
# several times a day < http://docs.platformio.org/page/ci/index.html >
#
# Documentation:
#
# * Travis CI Embedded Builds with PlatformIO
# < https://docs.travis-ci.com/user/integration/platformio/ >
#
# * PlatformIO integration with Travis CI
# < http://docs.platformio.org/page/ci/travis.html >
#
# * User Guide for `platformio ci` command
# < http://docs.platformio.org/page/userguide/cmd_ci.html >
#
#
# Please choice one of the following templates (proposed below) and uncomment
# it (remove "# " before each line) or use own configuration according to the
# Travis CI documentation (see above).
#
#
# Template #1: General project. Test it using existing `platformio.ini`.
#
# language: python
# python:
# - "2.7"
#
# install:
# - pip install -U platformio
#
# script:
# - platformio run
#
# Template #2: The project is intended to by used as a library with examples
#
# language: python
# python:
# - "2.7"
#
# env:
# - PLATFORMIO_CI_SRC=path/to/test/file.c
# - PLATFORMIO_CI_SRC=examples/file.ino
# - PLATFORMIO_CI_SRC=path/to/test/directory
#
# install:
# - pip install -U platformio
#
# script:
# - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N

View File

@ -1,4 +1,4 @@
This is a library for the TSL2561 digital luminosity sensors from Ams (Taos). This is an Arduino library for the TSL2561 digital luminosity sensors from Ams (Taos).
Design goals: Design goals:
* It is modularized so you can use only what you need if space/ram is constrained. * It is modularized so you can use only what you need if space/ram is constrained.
@ -18,3 +18,5 @@ The library has 3 classes:
Tsl2561 All register access as described in the datasheet, except for interrupts Tsl2561 All register access as described in the datasheet, except for interrupts
Tsl2561Util Convenience functions like lux calculation or automatic gain Tsl2561Util Convenience functions like lux calculation or automatic gain
Tsl2561Int TODO, Interrupt related stuff (not needed if int pin unconnected) Tsl2561Int TODO, Interrupt related stuff (not needed if int pin unconnected)
Tested with boards Nano, ESP8266 and ESP32

View File

@ -21,7 +21,7 @@ This file is part of the Joba_Tsl2561 Library.
#include <Tsl2561Util.h> #include <Tsl2561Util.h>
// to mimic Serial.printf() of esp8266 core for other platforms // to mimic Serial.printf() of esp cores for other platforms
char *format( const char *fmt, ... ) { char *format( const char *fmt, ... ) {
static char buf[128]; static char buf[128];
va_list arg; va_list arg;
@ -37,7 +37,7 @@ uint8_t id;
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
Wire.begin(); Wire.begin(TSL2561_SDA, TSL2561_SCL);
while( !Tsl.begin() ) while( !Tsl.begin() )
; // wait until chip detected or wdt reset ; // wait until chip detected or wdt reset
Serial.println("\nStarting Tsl2561Util autogain loop"); Serial.println("\nStarting Tsl2561Util autogain loop");

View File

@ -21,7 +21,7 @@ This file is part of the Joba_Tsl2561 Library.
#include <Tsl2561.h> #include <Tsl2561.h>
// to mimic Serial.printf() of esp8266 core for other platforms // to mimic Serial.printf() of esp cores for other platforms
char *format( const char *fmt, ... ) { char *format( const char *fmt, ... ) {
static char buf[128]; static char buf[128];
va_list arg; va_list arg;
@ -36,7 +36,7 @@ Tsl2561 Tsl(Wire);
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
Wire.begin(); Wire.begin(TSL2561_SDA, TSL2561_SCL);
Serial.println("\nStarting Tsl2561 simple loop"); Serial.println("\nStarting Tsl2561 simple loop");
} }
@ -60,7 +60,7 @@ void loop() {
Tsl.off(); Tsl.off();
} }
else { else {
Serial.println("No Tsl2561 found. Check wiring."); Serial.print(format("No Tsl2561 found. Check wiring: SCL=%u, SDA=%u\n", TSL2561_SCL, TSL2561_SDA));
} }
delay(5000); delay(5000);

View File

@ -22,7 +22,7 @@ This file is part of the Joba_Tsl2561 Library.
#include <Tsl2561.h> #include <Tsl2561.h>
// to mimic Serial.printf() of esp8266 core for other platforms // to mimic Serial.printf() of esp cores for other platforms
char *format( const char *fmt, ... ) { char *format( const char *fmt, ... ) {
static char buf[128]; static char buf[128];
va_list arg; va_list arg;
@ -158,7 +158,7 @@ void test( Tsl2561 &tsl ) {
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
Wire.begin(); Wire.begin(TSL2561_SDA, TSL2561_SCL);
Serial.println("\nStarting Tsl2561 testing loop"); Serial.println("\nStarting Tsl2561 testing loop");
} }

View File

@ -21,7 +21,7 @@ This file is part of the Joba_Tsl2561 Library.
#include <Tsl2561Util.h> #include <Tsl2561Util.h>
// to mimic Serial.printf() of esp8266 core for other platforms // to mimic Serial.printf() of esp cores for other platforms
char *format( const char *fmt, ... ) { char *format( const char *fmt, ... ) {
static char buf[128]; static char buf[128];
va_list arg; va_list arg;
@ -37,7 +37,7 @@ Tsl2561 Tsl(Wire);
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
Wire.begin(); Wire.begin(TSL2561_SDA, TSL2561_SCL);
Serial.println("\nStarting Tsl2561Util loop"); Serial.println("\nStarting Tsl2561Util loop");
} }
@ -91,7 +91,7 @@ void loop() {
} }
if( !found ) { if( !found ) {
Serial.println("No Tsl2561 found. Check wiring."); Serial.print(format("No Tsl2561 found. Check wiring: SCL=%u, SDA=%u\n", TSL2561_SCL, TSL2561_SDA));
} }
delay(5000); delay(5000);

View File

@ -18,9 +18,13 @@
[platformio] [platformio]
src_dir = .
lib_dir = ../..
; uncomment one, if you want to build only one ; uncomment one, if you want to build only one
; env_default = nodemcuv2 ; env_default = nodemcuv2
; env_default = nano328 ; env_default = nano328
; env_default = espressif32
[env:nodemcuv2] [env:nodemcuv2]
@ -28,12 +32,12 @@
; ------------ ; ------------
; GND <-> GND ; GND <-> GND
; VCC <-> 3V ; VCC <-> 3V
; SCL <-> D1 ; SCL <-> D1 (other pin can be defined below)
; SDA <-> D2 ; SDA <-> D2 (other pin can be defined below)
platform = espressif8266 platform = espressif8266
board = nodemcuv2 board = nodemcuv2
framework = arduino framework = arduino
build_flags = -Wall build_flags = -Wall -DTSL2561_SCL=D1 -DTSL2561_SDA=D2
monitor_speed = 115200 monitor_speed = 115200
@ -53,6 +57,25 @@ upload_resetmethod = nodemcu
platform = atmelavr platform = atmelavr
board = nanoatmega328 board = nanoatmega328
framework = arduino framework = arduino
build_flags = -Wall build_flags = -Wall -DTSL2561_SCL=A5 -DTSL2561_SDA=A4
monitor_speed = 115200 monitor_speed = 115200
;upload_port = /dev/ttyUSB[1-9]
[env:espressif32]
; TSL <-> ESP32
; ------------
; GND <-> GND
; VCC <-> 3V
; SCL <-> IO22 (other pin can be defined below)
; SDA <-> IO21 (other pin can be defined below)
platform = espressif32
board = mhetesp32minikit
framework = arduino
build_flags = -Wall -DTSL2561_SCL=22 -DTSL2561_SDA=21
monitor_speed = 115200
upload_speed = 921600
;upload_port = /dev/ttyUSB[1-9]

View File

@ -1,8 +1,8 @@
{ {
"name": "Joba_Tsl2561", "name": "Joba_Tsl2561",
"version": "2.0.7", "version": "2.0.10",
"keywords": "twowire, i2c, bus, sensor, luminosity, illuminance, lux", "keywords": "twowire, i2c, bus, sensor, luminosity, illuminance, lux",
"description": "Arduino Library for ams (taos) luminance chip Tsl2561 with autogain", "description": "Arduino library for ams (taos) luminance chip Tsl2561 with autogain. Tested with Nano, Esp8266 and Esp32.",
"repository": "repository":
{ {
"type": "git", "type": "git",

View File

@ -1,9 +1,9 @@
name=Joba Tsl2561 Library name=Joba Tsl2561 Library
version=2.0.7 version=2.0.10
author=joba-1 author=joba-1
maintainer=joba-1 <joban123.psn@gmail.com> maintainer=joba-1 <joban123.psn@gmail.com>
sentence=IoT library for using the Tsl2561 luminosity sensor sentence=IoT library for using the Tsl2561 luminosity sensor
paragraph=Luminosity measurement in lux with autogain paragraph=Luminosity measurement in lux with autogain. Tested with Nano, Esp8266 and Esp32.
category=Sensors category=Sensors
url=https://github.com/joba-1/Joba_Tsl2561 url=https://github.com/joba-1/Joba_Tsl2561
architectures=* architectures=*

View File

@ -18,20 +18,19 @@
[platformio] [platformio]
src_dir = .
lib_dir = ../..
; uncomment one, if you want to build only one ; uncomment one, if you want to build only one
; env_default = nodemcuv2 ; env_default = nodemcuv2
; env_default = nano328 ; env_default = nano328
; env_default = espressif32
[env:nodemcuv2] [env:nodemcuv2]
; TSL <-> ESP8266 ; TSL <-> ESP8266
; ------------ ; ------------
; GND <-> GND ; GND <-> GND
; VCC <-> 3V ; VCC <-> 3V
; SCL <-> D1 ; SCL <-> D1 (other pin can be defined below)
; SDA <-> D2 ; SDA <-> D2 (other pin can be defined below)
platform = espressif8266 platform = espressif8266
board = nodemcuv2 board = nodemcuv2
framework = arduino framework = arduino
@ -58,4 +57,21 @@ framework = arduino
build_flags = -Wall build_flags = -Wall
monitor_speed = 115200 monitor_speed = 115200
[env:espressif32]
; TSL <-> ESP32
; ------------
; GND <-> GND
; VCC <-> 3V
; SCL <-> IO22 (other pin can be defined below)
; SDA <-> IO21 (other pin can be defined below)
platform = espressif32
board = mhetesp32minikit
framework = arduino
build_flags = -Wall -DTSL2561_SCL=22 -DTSL2561_SDA=21
monitor_speed = 115200
upload_speed = 921600
;upload_port = /dev/ttyUSB[1-9] ;upload_port = /dev/ttyUSB[1-9]

View File

@ -23,6 +23,14 @@ This file is part of the Joba_Tsl2561 Library.
#include <Arduino.h> #include <Arduino.h>
#include <Wire.h> #include <Wire.h>
// To be able to override pin definitions in build flags (used in examples)
#ifndef TSL2561_SDA
#define TSL2561_SDA SDA
#endif
#ifndef TSL2561_SCL
#define TSL2561_SCL SCL
#endif
class Tsl2561 { class Tsl2561 {
public: public:

View File

@ -22,9 +22,10 @@ This file is part of the Joba_Tsl2561 Library.
namespace Tsl2561Util { namespace Tsl2561Util {
// Tsl2561Util::normalizedLuminosity returncode false can mean: // Tsl2561Util::normalizedLuminosity returncode false can mean:
// - saturation: full and/or ir have value ~0 (aka -1) // - saturation: full and/or ir have value ~0 (aka -1) and not shortest exposure
// - manual exposure time: full and ir are corrected only for gain // - manual exposure time: full and ir are corrected only for gain
// If true, full and ir have values as if exposure was 402 and gain 16. // If true, full and ir have values as if exposure was 402 and gain 16
// or ~0 if saturated even at shortest exposure
bool normalizedLuminosity( bool gain, Tsl2561::exposure_t exposure, uint32_t &full, uint32_t &ir ) { bool normalizedLuminosity( bool gain, Tsl2561::exposure_t exposure, uint32_t &full, uint32_t &ir ) {
uint16_t scaledFull = (uint16_t)full; uint16_t scaledFull = (uint16_t)full;
@ -39,8 +40,8 @@ bool normalizedLuminosity( bool gain, Tsl2561::exposure_t exposure, uint32_t &fu
switch( exposure ) { switch( exposure ) {
case Tsl2561::EXP_14: case Tsl2561::EXP_14:
full = (scaledFull >= 5047/4*3) ? 0xffffffff : ((full + 5) * 322) / 11; full = (scaledFull == 5047) ? 0xffffffff : ((full + 5) * 322) / 11;
ir = (scaledIr >= 5047/4*3) ? 0xffffffff : ((ir + 5) * 322) / 11; ir = (scaledIr == 5047) ? 0xffffffff : ((ir + 5) * 322) / 11;
break; break;
case Tsl2561::EXP_101: case Tsl2561::EXP_101:
full = (scaledFull >= 37177/4*3) ? 0xffffffff : ((full + 40) * 322) / 81; full = (scaledFull >= 37177/4*3) ? 0xffffffff : ((full + 40) * 322) / 81;
@ -54,7 +55,7 @@ bool normalizedLuminosity( bool gain, Tsl2561::exposure_t exposure, uint32_t &fu
return false; return false;
} }
return full != 0xffffffff && ir != 0xffffffff; return exposure == Tsl2561::EXP_14 || (full != 0xffffffff && ir != 0xffffffff);
} }
return false; return false;
@ -93,6 +94,8 @@ bool autoGain( Tsl2561 &tsl, bool &gain, Tsl2561::exposure_t &exposure, uint16_t
{ true, Tsl2561::EXP_402 } // max { true, Tsl2561::EXP_402 } // max
}; };
// Serial.printf("autoGain start: gain=%u, expo=%u\n", gain, exposure);
// get current sensitivity // get current sensitivity
if( !tsl.getSensitivity(gain, exposure) ) { if( !tsl.getSensitivity(gain, exposure) ) {
return false; // I2C error return false; // I2C error
@ -110,9 +113,10 @@ bool autoGain( Tsl2561 &tsl, bool &gain, Tsl2561::exposure_t &exposure, uint16_t
return false; // should not happen... return false; // should not happen...
} }
// in a loop wait for next sample, get values and adjust sensitivity if needed // sometimes sensor reports high brightness although it is darker.
uint8_t retryOnSaturated = 10; uint8_t retryOnSaturated = 10;
// in a loop wait for next sample, get values and adjust sensitivity if needed
while( true ) { while( true ) {
waitNext(exposure); waitNext(exposure);
@ -122,11 +126,14 @@ bool autoGain( Tsl2561 &tsl, bool &gain, Tsl2561::exposure_t &exposure, uint16_t
uint16_t limit = getLimit(exposure); uint16_t limit = getLimit(exposure);
if( full >= 1000 && full <= limit ) { if( full >= 1000 && full <= limit ) {
return true; // new value within limits // Serial.printf("autoGain normal full=%u, limits=1000-%u, curr=%u\n", full, limit, curr);
return true; // new value within limits of good accuracy
} }
// adjust sensitivity, if possible
if( (full < 1000 && ++curr < sizeof(sensitivity)/sizeof(sensitivity[0])) if( (full < 1000 && ++curr < sizeof(sensitivity)/sizeof(sensitivity[0]))
|| (full > limit && curr-- > 0) ) { || (full > limit && curr-- > 0) ) {
// Serial.printf("autoGain adjust full=%u, limits=1000-%u, curr=%u\n", full, limit, curr);
if( !tsl.setSensitivity(sensitivity[curr].gain, sensitivity[curr].exposure) ) { if( !tsl.setSensitivity(sensitivity[curr].gain, sensitivity[curr].exposure) ) {
return false; // I2C error return false; // I2C error
} }
@ -134,7 +141,10 @@ bool autoGain( Tsl2561 &tsl, bool &gain, Tsl2561::exposure_t &exposure, uint16_t
exposure = sensitivity[curr].exposure; exposure = sensitivity[curr].exposure;
} }
else { else {
if( ++curr > 0 && retryOnSaturated-- == 0 ) { // sensitivity already is at minimum or maximum
if( ++curr > 0 || retryOnSaturated-- == 0 ) {
// Serial.printf("autoGain limit full=%u, ir=%u, limits=1000-%u, curr=%u, retry=%u\n", full, ir, limit, curr, retryOnSaturated);
// dark, or repeatedly confirmed high brightness
return true; // saturated, but best we can do return true; // saturated, but best we can do
} }
} }
@ -176,6 +186,11 @@ bool milliLux( uint32_t full, uint32_t ir, uint32_t &mLux, bool csType, uint8_t
return true; return true;
} }
if( full == 0xffffffff || ir == 0xffffffff ) {
mLux = 99999999; // indicates saturation at shortest exposure
return true;
}
uint32_t milliRatio = ir * 1000 / full; uint32_t milliRatio = ir * 1000 / full;
if( csType ) { if( csType ) {

View File

@ -100,7 +100,7 @@ TasmotaSerial::TasmotaSerial(int receive_pin, int transmit_pin, int hardware_fal
m_buffer = (uint8_t*)malloc(TM_SERIAL_BUFFER_SIZE); m_buffer = (uint8_t*)malloc(TM_SERIAL_BUFFER_SIZE);
if (m_buffer == NULL) return; if (m_buffer == NULL) return;
// Use getCycleCount() loop to get as exact timing as possible // Use getCycleCount() loop to get as exact timing as possible
m_bit_time = F_CPU / TM_SERIAL_BAUDRATE; m_bit_time = ESP.getCpuFreqMHz() * 1000000 / TM_SERIAL_BAUDRATE;
pinMode(m_rx_pin, INPUT); pinMode(m_rx_pin, INPUT);
tms_obj_list[m_rx_pin] = this; tms_obj_list[m_rx_pin] = this;
attachInterrupt(m_rx_pin, ISRList[m_rx_pin], FALLING); attachInterrupt(m_rx_pin, ISRList[m_rx_pin], FALLING);
@ -145,8 +145,8 @@ bool TasmotaSerial::begin(long speed, int stop_bits) {
} }
} else { } else {
// Use getCycleCount() loop to get as exact timing as possible // Use getCycleCount() loop to get as exact timing as possible
m_bit_time = F_CPU / speed; m_bit_time = ESP.getCpuFreqMHz() * 1000000 / speed;
m_high_speed = (speed > 9600); m_high_speed = (speed >= 9600);
} }
return m_valid; return m_valid;
} }

View File

@ -1,4 +1,10 @@
/* 6.5.0.8 20190413 /* 6.5.0.9 20190418
* Add command SetOption63 0/1 to disable relay state feedback scan at restart (#5594, #5663)
* Fix TasmotaSerial at 9600 bps solving DFPlayer comms (#5528)
* Fix Shelly 2.5 overtemp
*
* 6.5.0.8 20190413
* Add Tuya Dimmer 10 second heartbeat serial packet required by some Tuya dimmer secondary MCUs
* Fix use of SerialDelimiter value 128 (#5634) * Fix use of SerialDelimiter value 128 (#5634)
* Fix lost syslog connection regression from 6.5.0.4 * Fix lost syslog connection regression from 6.5.0.4
* Add Shelly 2.5 Energy Monitoring (#5592) * Add Shelly 2.5 Energy Monitoring (#5592)

View File

@ -76,7 +76,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu
uint32_t sleep_normal : 1; // bit 10 (v6.3.0.15) - SetOption60 - Enable normal sleep instead of dynamic sleep uint32_t sleep_normal : 1; // bit 10 (v6.3.0.15) - SetOption60 - Enable normal sleep instead of dynamic sleep
uint32_t button_switch_force_local : 1;// bit 11 (v6.3.0.16) - SetOption61 - Force local operation when button/switch topic is set uint32_t button_switch_force_local : 1;// bit 11 (v6.3.0.16) - SetOption61 - Force local operation when button/switch topic is set
uint32_t no_hold_retain : 1; // bit 12 (v6.4.1.19) - SetOption62 - Don't use retain flag on HOLD messages uint32_t no_hold_retain : 1; // bit 12 (v6.4.1.19) - SetOption62 - Don't use retain flag on HOLD messages
uint32_t spare13 : 1; uint32_t no_power_feedback : 1; // bit 13 (v6.5.0.9) - SetOption63 - Don't scan relay power state at restart
uint32_t spare14 : 1; uint32_t spare14 : 1;
uint32_t spare15 : 1; uint32_t spare15 : 1;
uint32_t spare16 : 1; uint32_t spare16 : 1;

View File

@ -2693,8 +2693,10 @@ void setup(void)
// Issue #526 and #909 // Issue #526 and #909
for (uint8_t i = 0; i < devices_present; i++) { for (uint8_t i = 0; i < devices_present; i++) {
if ((i < MAX_RELAYS) && (pin[GPIO_REL1 +i] < 99)) { if (!Settings.flag3.no_power_feedback) { // #5594 and #5663
bitWrite(power, i, digitalRead(pin[GPIO_REL1 +i]) ^ bitRead(rel_inverted, i)); if ((i < MAX_RELAYS) && (pin[GPIO_REL1 +i] < 99)) {
bitWrite(power, i, digitalRead(pin[GPIO_REL1 +i]) ^ bitRead(rel_inverted, i));
}
} }
if ((i < MAX_PULSETIMERS) && (bitRead(power, i) || (POWER_ALL_OFF_PULSETIME_ON == Settings.poweronstate))) { if ((i < MAX_PULSETIMERS) && (bitRead(power, i) || (POWER_ALL_OFF_PULSETIME_ON == Settings.poweronstate))) {
SetPulseTimer(i, Settings.pulse_timer[i]); SetPulseTimer(i, Settings.pulse_timer[i]);

View File

@ -20,6 +20,6 @@
#ifndef _SONOFF_VERSION_H_ #ifndef _SONOFF_VERSION_H_
#define _SONOFF_VERSION_H_ #define _SONOFF_VERSION_H_
const uint32_t VERSION = 0x06050008; const uint32_t VERSION = 0x06050009;
#endif // _SONOFF_VERSION_H_ #endif // _SONOFF_VERSION_H_

View File

@ -584,6 +584,16 @@ float ConvertTemp(float c)
return result; return result;
} }
float ConvertTempToCelsius(float c)
{
float result = c;
if (!isnan(c) && Settings.flag.temperature_conversion) {
result = (c - 32) / 1.8; // Celsius
}
return result;
}
char TempUnit(void) char TempUnit(void)
{ {
return (Settings.flag.temperature_conversion) ? 'F' : 'C'; return (Settings.flag.temperature_conversion) ? 'F' : 'C';

View File

@ -245,7 +245,7 @@ const char HTTP_HEAD_STYLE1[] PROGMEM =
"input[type=checkbox],input[type=radio]{width:1em;margin-right:6px;vertical-align:-1px;}" "input[type=checkbox],input[type=radio]{width:1em;margin-right:6px;vertical-align:-1px;}"
"select{width:100%%;background:#%06x;color:#%06x;}" // COLOR_INPUT, COLOR_INPUT_TEXT "select{width:100%%;background:#%06x;color:#%06x;}" // COLOR_INPUT, COLOR_INPUT_TEXT
"textarea{resize:none;width:98%%;height:318px;padding:5px;overflow:auto;background:#%06x;color:#%06x;}" // COLOR_CONSOLE, COLOR_CONSOLE_TEXT "textarea{resize:none;width:98%%;height:318px;padding:5px;overflow:auto;background:#%06x;color:#%06x;}" // COLOR_CONSOLE, COLOR_CONSOLE_TEXT
"body{text-align:center;font-family:verdana;background:#%06x;}" // COLOR_BACKGROUND "body{text-align:center;font-family:verdana,sans-serif;background:#%06x;}" // COLOR_BACKGROUND
"td{padding:0px;}"; "td{padding:0px;}";
const char HTTP_HEAD_STYLE2[] PROGMEM = const char HTTP_HEAD_STYLE2[] PROGMEM =
"button{border:0;border-radius:0.3rem;background:#%06x;color:#%06x;line-height:2.4rem;font-size:1.2rem;width:100%%;-webkit-transition-duration:0.4s;transition-duration:0.4s;cursor:pointer;}" // COLOR_BUTTON, COLOR_BUTTON_TEXT "button{border:0;border-radius:0.3rem;background:#%06x;color:#%06x;line-height:2.4rem;font-size:1.2rem;width:100%%;-webkit-transition-duration:0.4s;transition-duration:0.4s;cursor:pointer;}" // COLOR_BUTTON, COLOR_BUTTON_TEXT

View File

@ -52,6 +52,7 @@ uint8_t tuya_cmd_status = 0; // Current status of serial-read
uint8_t tuya_cmd_checksum = 0; // Checksum of tuya command uint8_t tuya_cmd_checksum = 0; // Checksum of tuya command
uint8_t tuya_data_len = 0; // Data lenght of command uint8_t tuya_data_len = 0; // Data lenght of command
int8_t tuya_wifi_state = -2; // Keep MCU wifi-status in sync with WifiState() int8_t tuya_wifi_state = -2; // Keep MCU wifi-status in sync with WifiState()
uint8_t tuya_heartbeat_timer = 0; // 10 second heartbeat timer for tuya module
char *tuya_buffer = nullptr; // Serial receive buffer char *tuya_buffer = nullptr; // Serial receive buffer
int tuya_byte_counter = 0; // Index in serial receive buffer int tuya_byte_counter = 0; // Index in serial receive buffer
@ -294,6 +295,7 @@ void TuyaInit(void)
TuyaSendCmd(TUYA_CMD_MCU_CONF); TuyaSendCmd(TUYA_CMD_MCU_CONF);
} }
} }
tuya_heartbeat_timer = 0; // init heartbeat timer when dimmer init is done
} }
void TuyaSerialInput(void) void TuyaSerialInput(void)
@ -410,6 +412,11 @@ bool Xdrv16(uint8_t function)
break; break;
case FUNC_EVERY_SECOND: case FUNC_EVERY_SECOND:
if(TuyaSerial && tuya_wifi_state!=WifiState()) { TuyaSetWifiLed(); } if(TuyaSerial && tuya_wifi_state!=WifiState()) { TuyaSetWifiLed(); }
tuya_heartbeat_timer++;
if (tuya_heartbeat_timer > 10) {
tuya_heartbeat_timer = 0;
TuyaSendCmd(TUYA_CMD_HEARTBEAT);
}
break; break;
case FUNC_SET_CHANNELS: case FUNC_SET_CHANNELS:
result = TuyaSetChannels(); result = TuyaSetChannels();

View File

@ -162,9 +162,11 @@ void Ade7953EnergyEverySecond()
void Ade7953EverySecond() void Ade7953EverySecond()
{ {
if (power && (global_temperature > ADE7953_OVERTEMP)) { // Device overtemp, turn off relays #ifndef USE_ADC_VCC
if (power && (ConvertTempToCelsius(AdcTemperature()) > ADE7953_OVERTEMP)) { // Device overtemp, turn off relays
SetAllPower(POWER_ALL_OFF, SRC_OVERTEMP); SetAllPower(POWER_ALL_OFF, SRC_OVERTEMP);
} }
#endif // USE_ADC_VCC
} }
void Ade7953DrvInit(void) void Ade7953DrvInit(void)
@ -190,7 +192,7 @@ bool Ade7953Command(void)
{ {
bool serviced = true; bool serviced = true;
double value = CharToDouble(XdrvMailbox.data); uint32_t value = (uint32_t)(CharToDouble(XdrvMailbox.data) * 100); // 1.23 = 123
if (CMND_POWERCAL == energy_command_code) { if (CMND_POWERCAL == energy_command_code) {
if (1 == XdrvMailbox.payload) { XdrvMailbox.payload = ADE7953_PREF; } if (1 == XdrvMailbox.payload) { XdrvMailbox.payload = ADE7953_PREF; }
@ -206,17 +208,23 @@ bool Ade7953Command(void)
} }
else if (CMND_POWERSET == energy_command_code) { else if (CMND_POWERSET == energy_command_code) {
if (XdrvMailbox.data_len && ade7953_active_power) { if (XdrvMailbox.data_len && ade7953_active_power) {
Settings.energy_power_calibration = (uint32_t)((double)ade7953_active_power / (value / 10)); // W if ((value > 100) && (value < 200000)) { // Between 1W and 2000W
Settings.energy_power_calibration = (ade7953_active_power * 1000) / value; // 0.00 W
}
} }
} }
else if (CMND_VOLTAGESET == energy_command_code) { else if (CMND_VOLTAGESET == energy_command_code) {
if (XdrvMailbox.data_len && ade7953_voltage_rms) { if (XdrvMailbox.data_len && ade7953_voltage_rms) {
Settings.energy_voltage_calibration = (uint32_t)((double)ade7953_voltage_rms / value); // V if ((value > 10000) && (value < 26000)) { // Between 100V and 260V
Settings.energy_voltage_calibration = (ade7953_voltage_rms * 100) / value; // 0.00 V
}
} }
} }
else if (CMND_CURRENTSET == energy_command_code) { else if (CMND_CURRENTSET == energy_command_code) {
if (XdrvMailbox.data_len && ade7953_current_rms) { if (XdrvMailbox.data_len && ade7953_current_rms) {
Settings.energy_current_calibration = (uint32_t)((double)ade7953_current_rms / (value * 10)); // A if ((value > 2000) && (value < 1000000)) { // Between 20mA and 10A
Settings.energy_current_calibration = ((ade7953_current_rms * 100) / value) * 100; // 0.00 mA
}
} }
} }
else serviced = false; // Unknown command else serviced = false; // Unknown command

View File

@ -36,6 +36,7 @@
#define ANALOG_B 3350.0 // Thermistor Beta Coefficient #define ANALOG_B 3350.0 // Thermistor Beta Coefficient
uint16_t adc_last_value = 0; uint16_t adc_last_value = 0;
float adc_temp = 0;
uint16_t AdcRead(uint8_t factor) uint16_t AdcRead(uint8_t factor)
{ {
@ -69,6 +70,22 @@ void AdcEvery250ms(void)
} }
#endif // USE_RULES #endif // USE_RULES
void AdcEverySecond(void)
{
if (my_module_flag.adc0_temp) {
int adc = AdcRead(2);
// Steinhart-Hart equation for thermistor as temperature sensor
double Rt = (adc * ANALOG_R21) / (1024.0 * ANALOG_V33 - (double)adc);
double T = ANALOG_B / (ANALOG_B/ANALOG_T0 + log(Rt/ANALOG_R0));
adc_temp = ConvertTemp(TO_CELSIUS(T));
}
}
float AdcTemperature(void)
{
return adc_temp;
}
void AdcShow(bool json) void AdcShow(bool json)
{ {
if (my_module_flag.adc0) { if (my_module_flag.adc0) {
@ -83,14 +100,8 @@ void AdcShow(bool json)
} }
} }
if (my_module_flag.adc0_temp) { if (my_module_flag.adc0_temp) {
int adc = AdcRead(2);
// Steinhart-Hart equation for thermistor as temperature sensor
double Rt = (adc * ANALOG_R21) / (1024.0 * ANALOG_V33 - (double)adc);
double T = ANALOG_B / (ANALOG_B/ANALOG_T0 + log(Rt/ANALOG_R0));
double temp = ConvertTemp(TO_CELSIUS(T));
char temperature[33]; char temperature[33];
dtostrfd(temp, Settings.flag2.temperature_resolution, temperature); dtostrfd(adc_temp, Settings.flag2.temperature_resolution, temperature);
if (json) { if (json) {
ResponseAppend_P(JSON_SNS_TEMP, "ANALOG", temperature); ResponseAppend_P(JSON_SNS_TEMP, "ANALOG", temperature);
@ -127,6 +138,9 @@ bool Xsns02(uint8_t function)
AdcEvery250ms(); AdcEvery250ms();
break; break;
#endif // USE_RULES #endif // USE_RULES
case FUNC_EVERY_SECOND:
AdcEverySecond();
break;
case FUNC_JSON_APPEND: case FUNC_JSON_APPEND:
AdcShow(1); AdcShow(1);
break; break;

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
VER = '2.1.0023' VER = '2.1.0024'
""" """
decode-config.py - Backup/Restore Sonoff-Tasmota configuration data decode-config.py - Backup/Restore Sonoff-Tasmota configuration data
@ -433,7 +433,7 @@ Setting_5_10_0 = {
'pwm_control': ('<L', (0x010,1,15), (None, None, ('Main', '"SetOption15 {}".format($)')) ), 'pwm_control': ('<L', (0x010,1,15), (None, None, ('Main', '"SetOption15 {}".format($)')) ),
'ws_clock_reverse': ('<L', (0x010,1,16), (None, None, ('SetOption', '"SetOption16 {}".format($)')) ), 'ws_clock_reverse': ('<L', (0x010,1,16), (None, None, ('SetOption', '"SetOption16 {}".format($)')) ),
'decimal_text': ('<L', (0x010,1,17), (None, None, ('SetOption', '"SetOption17 {}".format($)')) ), 'decimal_text': ('<L', (0x010,1,17), (None, None, ('SetOption', '"SetOption17 {}".format($)')) ),
}, 0x010, (None, None, ('*', None)), (None, False) ), }, 0x010, (None, None, ('*', None)), (None, None) ),
'save_data': ('<h', 0x014, (None, '0 <= $ <= 3600', ('Management', '"SaveData {}".format($)')) ), 'save_data': ('<h', 0x014, (None, '0 <= $ <= 3600', ('Management', '"SaveData {}".format($)')) ),
'timezone': ('b', 0x016, (None, '-13 <= $ <= 13 or $==99', ('Management', '"Timezone {}".format($)')) ), 'timezone': ('b', 0x016, (None, '-13 <= $ <= 13 or $==99', ('Management', '"Timezone {}".format($)')) ),
'ota_url': ('101s',0x017, (None, None, ('Main', '"OtaUrl {}".format($)')) ), 'ota_url': ('101s',0x017, (None, None, ('Main', '"OtaUrl {}".format($)')) ),
@ -469,7 +469,7 @@ Setting_5_10_0 = {
'power6': ('<L', (0x2E8,1,5), (None, None, ('Main', '"Power6 {}".format($)')) ), 'power6': ('<L', (0x2E8,1,5), (None, None, ('Main', '"Power6 {}".format($)')) ),
'power7': ('<L', (0x2E8,1,6), (None, None, ('Main', '"Power7 {}".format($)')) ), 'power7': ('<L', (0x2E8,1,6), (None, None, ('Main', '"Power7 {}".format($)')) ),
'power8': ('<L', (0x2E8,1,7), (None, None, ('Main', '"Power8 {}".format($)')) ), 'power8': ('<L', (0x2E8,1,7), (None, None, ('Main', '"Power8 {}".format($)')) ),
}, 0x2E8, (None, None, ('Main', None)), (None, False) ), }, 0x2E8, (None, None, ('Main', None)), (None, None) ),
'pwm_value': ('<H', 0x2EC, ([5], '0 <= $ <= 1023', ('Management', '"Pwm{} {}".format(#,$)')) ), 'pwm_value': ('<H', 0x2EC, ([5], '0 <= $ <= 1023', ('Management', '"Pwm{} {}".format(#,$)')) ),
'altitude': ('<h', 0x2F6, (None, '-30000 <= $ <= 30000', ('Sensor', '"Altitude {}".format($)')) ), 'altitude': ('<h', 0x2F6, (None, '-30000 <= $ <= 30000', ('Sensor', '"Altitude {}".format($)')) ),
'tele_period': ('<H', 0x2F8, (None, '0 <= $ <= 1 or 10 <= $ <= 3600',('MQTT', '"TelePeriod {}".format($)')) ), 'tele_period': ('<H', 0x2F8, (None, '0 <= $ <= 1 or 10 <= $ <= 3600',('MQTT', '"TelePeriod {}".format($)')) ),
@ -483,8 +483,8 @@ Setting_5_10_0 = {
'energy_power_calibration': ('<L', 0x364, (None, None, ('Pow', '"PowerSet {}".format($)')) ), 'energy_power_calibration': ('<L', 0x364, (None, None, ('Pow', '"PowerSet {}".format($)')) ),
'energy_voltage_calibration': ('<L', 0x368, (None, None, ('Pow', '"VoltageSet {}".format($)')) ), 'energy_voltage_calibration': ('<L', 0x368, (None, None, ('Pow', '"VoltageSet {}".format($)')) ),
'energy_current_calibration': ('<L', 0x36C, (None, None, ('Pow', '"CurrentSet {}".format($)')) ), 'energy_current_calibration': ('<L', 0x36C, (None, None, ('Pow', '"CurrentSet {}".format($)')) ),
'energy_kWhtoday': ('<L', 0x370, (None, '0 <= $ <= 42500', ('Pow', '"EnergyReset1 {}".format($)')) ), 'energy_kWhtoday': ('<L', 0x370, (None, '0 <= $ <= 4250000', ('Pow', '"EnergyReset1 {}".format(int(round(float($)/100)))')) ),
'energy_kWhyesterday': ('<L', 0x374, (None, '0 <= $ <= 42500', ('Pow', '"EnergyReset2 {}".format($)')) ), 'energy_kWhyesterday': ('<L', 0x374, (None, '0 <= $ <= 4250000', ('Pow', '"EnergyReset2 {}".format(int(round(float($)/100)))')) ),
'energy_kWhdoy': ('<H', 0x378, (None, None, ('Pow', None)) ), 'energy_kWhdoy': ('<H', 0x378, (None, None, ('Pow', None)) ),
'energy_min_power': ('<H', 0x37A, (None, None, ('Pow', '"PowerLow {}".format($)')) ), 'energy_min_power': ('<H', 0x37A, (None, None, ('Pow', '"PowerLow {}".format($)')) ),
'energy_max_power': ('<H', 0x37C, (None, None, ('Pow', '"PowerHigh {}".format($)')) ), 'energy_max_power': ('<H', 0x37C, (None, None, ('Pow', '"PowerHigh {}".format($)')) ),
@ -529,9 +529,9 @@ Setting_5_10_0 = {
'switchmode': ('B', 0x4CA, ([4], '0 <= $ <= 7', ('Main', '"SwitchMode{} {}".format(#,$)')) ), 'switchmode': ('B', 0x4CA, ([4], '0 <= $ <= 7', ('Main', '"SwitchMode{} {}".format(#,$)')) ),
'ntp_server': ('33s', 0x4CE, ([3], None, ('Wifi', '"NtpServer{} {}".format(#,$)')) ), 'ntp_server': ('33s', 0x4CE, ([3], None, ('Wifi', '"NtpServer{} {}".format(#,$)')) ),
'ina219_mode': ('B', 0x531, (None, '0 <= $ <= 7', ('Sensor', '"Sensor13 {}".format($)')) ), 'ina219_mode': ('B', 0x531, (None, '0 <= $ <= 7', ('Sensor', '"Sensor13 {}".format($)')) ),
'pulse_timer': ('<H', 0x532, ([8], '0 <= $ <= 64900', ('Main', '"PulseTime{} {}".format(#,$)')), ("float($)/10 if 1 <= $ <= 111 else $-100 if $ != 0 else 0", "int($*10) if 0.1 <= $ < 12 else $+100 if $ != 0 else 0") ), 'pulse_timer': ('<H', 0x532, ([8], '0 <= $ <= 64900', ('Main', '"PulseTime{} {}".format(#,$)')) ),
'ip_address': ('<L', 0x544, ([4], None, ('Wifi', '"IPAddress{} {}".format(#,$)')), ("socket.inet_ntoa(struct.pack('<L', $))", "struct.unpack('<L', socket.inet_aton($))[0]")), 'ip_address': ('<L', 0x544, ([4], None, ('Wifi', '"IPAddress{} {}".format(#,$)')), ("socket.inet_ntoa(struct.pack('<L', $))", "struct.unpack('<L', socket.inet_aton($))[0]")),
'energy_kWhtotal': ('<L', 0x554, (None, None, ('Pow', None)) ), 'energy_kWhtotal': ('<L', 0x554, (None, '0 <= $ <= 4250000000', ('Pow', '"EnergyReset3 {}".format(int(round(float($)/100)))')) ),
'mqtt_fulltopic': ('100s',0x558, (None, None, ('MQTT', '"FullTopic {}".format($)')) ), 'mqtt_fulltopic': ('100s',0x558, (None, None, ('MQTT', '"FullTopic {}".format($)')) ),
'flag2': ({ 'flag2': ({
'current_resolution': ('<L', (0x5BC,2,15), (None, '0 <= $ <= 3', ('Pow', '"AmpRes {}".format($)')) ), 'current_resolution': ('<L', (0x5BC,2,15), (None, '0 <= $ <= 3', ('Pow', '"AmpRes {}".format($)')) ),
@ -542,7 +542,7 @@ Setting_5_10_0 = {
'pressure_resolution': ('<L', (0x5BC,2,26), (None, '0 <= $ <= 3', ('Sensor', '"PressRes {}".format($)')) ), 'pressure_resolution': ('<L', (0x5BC,2,26), (None, '0 <= $ <= 3', ('Sensor', '"PressRes {}".format($)')) ),
'humidity_resolution': ('<L', (0x5BC,2,28), (None, '0 <= $ <= 3', ('Sensor', '"HumRes {}".format($)')) ), 'humidity_resolution': ('<L', (0x5BC,2,28), (None, '0 <= $ <= 3', ('Sensor', '"HumRes {}".format($)')) ),
'temperature_resolution': ('<L', (0x5BC,2,30), (None, '0 <= $ <= 3', ('Sensor', '"TempRes {}".format($)')) ), 'temperature_resolution': ('<L', (0x5BC,2,30), (None, '0 <= $ <= 3', ('Sensor', '"TempRes {}".format($)')) ),
}, 0x5BC, (None, None, ('*', None)), (None, False) ), }, 0x5BC, (None, None, ('*', None)), (None, None) ),
'pulse_counter': ('<L', 0x5C0, ([4], None, ('Sensor', '"Counter{} {}".format(#,$)')) ), 'pulse_counter': ('<L', 0x5C0, ([4], None, ('Sensor', '"Counter{} {}".format(#,$)')) ),
'pulse_counter_type': ('<H', 0x5D0, (None, None, ('Sensor', '"CounterType {}".format($)')) ), 'pulse_counter_type': ('<H', 0x5D0, (None, None, ('Sensor', '"CounterType {}".format($)')) ),
'pulse_counter_type': ({ 'pulse_counter_type': ({
@ -550,7 +550,7 @@ Setting_5_10_0 = {
'pulse_counter_type2': ('<H', (0x5D0,1,1), (None, None, ('Sensor', '"CounterType2 {}".format($)')) ), 'pulse_counter_type2': ('<H', (0x5D0,1,1), (None, None, ('Sensor', '"CounterType2 {}".format($)')) ),
'pulse_counter_type3': ('<H', (0x5D0,1,2), (None, None, ('Sensor', '"CounterType3 {}".format($)')) ), 'pulse_counter_type3': ('<H', (0x5D0,1,2), (None, None, ('Sensor', '"CounterType3 {}".format($)')) ),
'pulse_counter_type4': ('<H', (0x5D0,1,3), (None, None, ('Sensor', '"CounterType4 {}".format($)')) ), 'pulse_counter_type4': ('<H', (0x5D0,1,3), (None, None, ('Sensor', '"CounterType4 {}".format($)')) ),
}, 0x5D0, (None, None, ('Sensor', None)), (None, False) ), }, 0x5D0, (None, None, ('Sensor', None)), (None, None) ),
'pulse_counter_debounce': ('<H', 0x5D2, (None, '0 <= $ <= 3200', ('Sensor', '"CounterDebounce {}".format($)')) ), 'pulse_counter_debounce': ('<H', 0x5D2, (None, '0 <= $ <= 3200', ('Sensor', '"CounterDebounce {}".format($)')) ),
'rf_code': ('B', 0x5D4, ([17,9],None, ('SonoffRF', None)), '"0x{:02x}".format($)'), 'rf_code': ('B', 0x5D4, ([17,9],None, ('SonoffRF', None)), '"0x{:02x}".format($)'),
} }
@ -595,7 +595,7 @@ Setting_5_13_1.update ({
'knx_GA_registered': ('B', 0x4A5, (None, None, ('KNX', None)) ), 'knx_GA_registered': ('B', 0x4A5, (None, None, ('KNX', None)) ),
'knx_CB_registered': ('B', 0x4A8, (None, None, ('KNX', None)) ), 'knx_CB_registered': ('B', 0x4A8, (None, None, ('KNX', None)) ),
'timer': ({ 'timer': ({
'value': ('<L', 0x670, (None, None, ('Timers', '"Timer{} {{\\\"Arm\\\":{arm},\\\"Mode\\\":{mode},\\\"Time\\\":\\\"{tsign}{time}\\\",\\\"Window\\\":{window},\\\"Days\\\":\\\"{days}\\\",\\\"Repeat\\\":{repeat},\\\"Output\\\":{device},\\\"Action\\\":{power}}}".format(#, arm=bitsRead($,31),mode=bitsRead($,29,2),tsign="-" if bitsRead($,29,2)>0 and bitsRead($,0,11)>(12*60) else "",time=time.strftime("%H:%M",time.gmtime((bitsRead($,0,11) if bitsRead($,29,2)==0 else bitsRead($,0,11) if bitsRead($,0,11)<=(12*60) else bitsRead($,0,11)-(12*60))*60)),window=bitsRead($,11,4),repeat=bitsRead($,15),days="{:07b}".format(bitsRead($,16,7))[::-1],device=bitsRead($,23,4)+1,power=bitsRead($,27,2) )')), ('"0x{:08x}".format($)', False) ), '_': ('<L', 0x670, (None, None, ('Timers', '"Timer{} {{\\\"Arm\\\":{arm},\\\"Mode\\\":{mode},\\\"Time\\\":\\\"{tsign}{time}\\\",\\\"Window\\\":{window},\\\"Days\\\":\\\"{days}\\\",\\\"Repeat\\\":{repeat},\\\"Output\\\":{device},\\\"Action\\\":{power}}}".format(#, arm=bitsRead($,31),mode=bitsRead($,29,2),tsign="-" if bitsRead($,29,2)>0 and bitsRead($,0,11)>(12*60) else "",time=time.strftime("%H:%M",time.gmtime((bitsRead($,0,11) if bitsRead($,29,2)==0 else bitsRead($,0,11) if bitsRead($,0,11)<=(12*60) else bitsRead($,0,11)-(12*60))*60)),window=bitsRead($,11,4),repeat=bitsRead($,15),days="{:07b}".format(bitsRead($,16,7))[::-1],device=bitsRead($,23,4)+1,power=bitsRead($,27,2) )')), ('"0x{:08x}".format($)', False) ),
'time': ('<L', (0x670,11, 0),(None, '0 <= $ < 1440', ('Timers', None)) ), 'time': ('<L', (0x670,11, 0),(None, '0 <= $ < 1440', ('Timers', None)) ),
'window': ('<L', (0x670, 4,11),(None, None, ('Timers', None)) ), 'window': ('<L', (0x670, 4,11),(None, None, ('Timers', None)) ),
'repeat': ('<L', (0x670, 1,15),(None, None, ('Timers', None)) ), 'repeat': ('<L', (0x670, 1,15),(None, None, ('Timers', None)) ),
@ -627,7 +627,7 @@ Setting_5_14_0.update ({
'month': ('<H', (0x2E2,4, 4), (None, '1 <= $ <= 12', ('Management', None)) ), 'month': ('<H', (0x2E2,4, 4), (None, '1 <= $ <= 12', ('Management', None)) ),
'dow': ('<H', (0x2E2,3, 8), (None, '1 <= $ <= 7', ('Management', None)) ), 'dow': ('<H', (0x2E2,3, 8), (None, '1 <= $ <= 7', ('Management', None)) ),
'hour': ('<H', (0x2E2,5,11), (None, '0 <= $ <= 23', ('Management', None)) ), 'hour': ('<H', (0x2E2,5,11), (None, '0 <= $ <= 23', ('Management', None)) ),
}, 0x2E2, ([2], None, ('Management', None)), (None, False) ), }, 0x2E2, ([2], None, ('Management', None)), (None, None) ),
'param': ('B', 0x2FC, ([18], None, ('SetOption', '"SetOption{} {}".format(#+31,$)')) ), 'param': ('B', 0x2FC, ([18], None, ('SetOption', '"SetOption{} {}".format(#+31,$)')) ),
'toffset': ('<h', 0x30E, ([2], None, ('Management', '"{cmnd} {hemis},{week},{month},{dow},{hour},{toffset}".format(cmnd="TimeSTD" if idx==1 else "TimeDST", hemis=@["tflag"][#-1]["hemis"], week=@["tflag"][#-1]["week"], month=@["tflag"][#-1]["month"], dow=@["tflag"][#-1]["dow"], hour=@["tflag"][#-1]["hour"], toffset=value)')) ), 'toffset': ('<h', 0x30E, ([2], None, ('Management', '"{cmnd} {hemis},{week},{month},{dow},{hour},{toffset}".format(cmnd="TimeSTD" if idx==1 else "TimeDST", hemis=@["tflag"][#-1]["hemis"], week=@["tflag"][#-1]["week"], month=@["tflag"][#-1]["month"], dow=@["tflag"][#-1]["dow"], hour=@["tflag"][#-1]["hour"], toffset=value)')) ),
}) })
@ -642,12 +642,12 @@ Setting_6_0_0.update({
'rule1': ('B', (0x49F,1,0), (None, None, ('Management', '"Rule1 {}".format($)')) ), 'rule1': ('B', (0x49F,1,0), (None, None, ('Management', '"Rule1 {}".format($)')) ),
'rule2': ('B', (0x49F,1,1), (None, None, ('Management', '"Rule2 {}".format($)')) ), 'rule2': ('B', (0x49F,1,1), (None, None, ('Management', '"Rule2 {}".format($)')) ),
'rule3': ('B', (0x49F,1,2), (None, None, ('Management', '"Rule3 {}".format($)')) ), 'rule3': ('B', (0x49F,1,2), (None, None, ('Management', '"Rule3 {}".format($)')) ),
}, 0x49F, (None, None, ('Management', None)), (None, False) ), }, 0x49F, (None, None, ('Management', None)), (None, None) ),
'rule_once': ({ 'rule_once': ({
'rule1': ('B', (0x4A0,1,0), (None, None, ('Management', '"Rule1 {}".format($+4)')) ), 'rule1': ('B', (0x4A0,1,0), (None, None, ('Management', '"Rule1 {}".format($+4)')) ),
'rule2': ('B', (0x4A0,1,1), (None, None, ('Management', '"Rule2 {}".format($+4)')) ), 'rule2': ('B', (0x4A0,1,1), (None, None, ('Management', '"Rule2 {}".format($+4)')) ),
'rule3': ('B', (0x4A0,1,2), (None, None, ('Management', '"Rule3 {}".format($+4)')) ), 'rule3': ('B', (0x4A0,1,2), (None, None, ('Management', '"Rule3 {}".format($+4)')) ),
}, 0x4A0, (None, None, ('Management', None)), (None, False) ), }, 0x4A0, (None, None, ('Management', None)), (None, None) ),
'mems': ('10s', 0x7CE, ([5], None, ('Management', '"Mem{} {}".format(#,"\\"" if len($)==0 else $)')) ), 'mems': ('10s', 0x7CE, ([5], None, ('Management', '"Mem{} {}".format(#,"\\"" if len($)==0 else $)')) ),
'rules': ('512s',0x800, ([3], None, ('Management', '"Rule{} {}".format(#,"\\"" if len($)==0 else $)')) ), 'rules': ('512s',0x800, ([3], None, ('Management', '"Rule{} {}".format(#,"\\"" if len($)==0 else $)')) ),
}) })
@ -660,14 +660,14 @@ Setting_6_1_1.update ({
'flag3': ('<L', 0x3A0, (None, None, ('System', None)), '"0x{:08x}".format($)' ), 'flag3': ('<L', 0x3A0, (None, None, ('System', None)), '"0x{:08x}".format($)' ),
'switchmode': ('B', 0x3A4, ([8], '0 <= $ <= 7', ('Main', '"SwitchMode{} {}".format(#,$)')) ), 'switchmode': ('B', 0x3A4, ([8], '0 <= $ <= 7', ('Main', '"SwitchMode{} {}".format(#,$)')) ),
'mcp230xx_config': ({ 'mcp230xx_config': ({
'value': ('<L', 0x6F6, (None, None, ('MCP230xx', '"Sensor29 {pin},{pinmode},{pullup},{intmode}".format(pin=#-1, pinmode=@["mcp230xx_config"][#-1]["pinmode"], pullup=@["mcp230xx_config"][#-1]["pullup"], intmode=@["mcp230xx_config"][#-1]["int_report_mode"])')), ('"0x{:08x}".format($)', False) ), '_': ('<L', 0x6F6, (None, None, ('MCP230xx', '"Sensor29 {pin},{pinmode},{pullup},{intmode}".format(pin=#-1, pinmode=@["mcp230xx_config"][#-1]["pinmode"], pullup=@["mcp230xx_config"][#-1]["pullup"], intmode=@["mcp230xx_config"][#-1]["int_report_mode"])')), ('"0x{:08x}".format($)', False) ),
'pinmode': ('<L', (0x6F6,3, 0), (None, '0 <= $ <= 5', ('MCP230xx', None)) ), 'pinmode': ('<L', (0x6F6,3, 0), (None, '0 <= $ <= 5', ('MCP230xx', None)) ),
'pullup': ('<L', (0x6F6,1, 3), (None, None, ('MCP230xx', None)) ), 'pullup': ('<L', (0x6F6,1, 3), (None, None, ('MCP230xx', None)) ),
'saved_state': ('<L', (0x6F6,1, 4), (None, None, ('MCP230xx', None)) ), 'saved_state': ('<L', (0x6F6,1, 4), (None, None, ('MCP230xx', None)) ),
'int_report_mode': ('<L', (0x6F6,2, 5), (None, None, ('MCP230xx', None)) ), 'int_report_mode': ('<L', (0x6F6,2, 5), (None, None, ('MCP230xx', None)) ),
'int_report_defer': ('<L', (0x6F6,4, 7), (None, None, ('MCP230xx', None)) ), 'int_report_defer': ('<L', (0x6F6,4, 7), (None, None, ('MCP230xx', None)) ),
'int_count_en': ('<L', (0x6F6,1,11), (None, None, ('MCP230xx', None)) ), 'int_count_en': ('<L', (0x6F6,1,11), (None, None, ('MCP230xx', None)) ),
}, 0x6F6, ([16], None, ('MCP230xx', None)), (None, False) ), }, 0x6F6, ([16], None, ('MCP230xx', None)), (None, None) ),
}) })
Setting_6_1_1['flag'][0].update ({ Setting_6_1_1['flag'][0].update ({
'rf_receive_decimal': ('<L', (0x010,1,28), (None, None, ('SetOption' , '"SetOption28 {}".format($)')) ), 'rf_receive_decimal': ('<L', (0x010,1,28), (None, None, ('SetOption' , '"SetOption28 {}".format($)')) ),
@ -687,7 +687,7 @@ Setting_6_2_1.update ({
'flag3': ({ 'flag3': ({
'timers_enable': ('<L', (0x3A0,1, 0), (None, None, ('Timers', '"Timers {}".format($)')) ), 'timers_enable': ('<L', (0x3A0,1, 0), (None, None, ('Timers', '"Timers {}".format($)')) ),
'user_esp8285_enable': ('<L', (0x3A0,1,31), (None, None, ('System', None)) ), 'user_esp8285_enable': ('<L', (0x3A0,1,31), (None, None, ('System', None)) ),
}, 0x3A0, (None, None, ('*', None)), (None, False) ), }, 0x3A0, (None, None, ('*', None)), (None, None) ),
'button_debounce': ('<H', 0x542, (None, '40 <= $ <= 1000', ('Main', '"ButtonDebounce {}".format($)')) ), 'button_debounce': ('<H', 0x542, (None, '40 <= $ <= 1000', ('Main', '"ButtonDebounce {}".format($)')) ),
'switch_debounce': ('<H', 0x66E, (None, '40 <= $ <= 1000', ('Main', '"SwitchDebounce {}".format($)')) ), 'switch_debounce': ('<H', 0x66E, (None, '40 <= $ <= 1000', ('Main', '"SwitchDebounce {}".format($)')) ),
'mcp230xx_int_prio': ('B', 0x716, (None, None, ('MCP230xx', None)) ), 'mcp230xx_int_prio': ('B', 0x716, (None, None, ('MCP230xx', None)) ),
@ -834,12 +834,15 @@ Setting_6_4_1_11['flag3'][0].pop('split_interlock',None)
Setting_6_4_1_11.update ({ Setting_6_4_1_11.update ({
'interlock': ('B', 0x4CA, ([4], None, ('Main', None)), '"0x{:02x}".format($)' ), 'interlock': ('B', 0x4CA, ([4], None, ('Main', None)), '"0x{:02x}".format($)' ),
}) })
Setting_6_4_1_11['flag'][0].update ({
'interlock': ('<L', (0x010,1,14), (None, None, ('Main', '"Interlock {}".format($)')) ),
})
# ====================================================================== # ======================================================================
Setting_6_4_1_13 = copy.deepcopy(Setting_6_4_1_11) Setting_6_4_1_13 = copy.deepcopy(Setting_6_4_1_11)
Setting_6_4_1_13.update ({ Setting_6_4_1_13.update ({
'SensorBits1': ({ 'SensorBits1': ({
'mhz19b_abc_disable': ('B', (0x717,1, 7), (None, None, ('Sensor', '"Sensor15 {}".format($)')) ), 'mhz19b_abc_disable': ('B', (0x717,1, 7), (None, None, ('Sensor', '"Sensor15 {}".format($)')) ),
}, 0x717, (None, None, ('*', None)), (None, False) ), }, 0x717, (None, None, ('*', None)), (None, None) ),
}) })
# ====================================================================== # ======================================================================
Setting_6_4_1_16 = copy.deepcopy(Setting_6_4_1_13) Setting_6_4_1_16 = copy.deepcopy(Setting_6_4_1_13)
@ -861,7 +864,7 @@ Setting_6_4_1_16.update({
'gpio15': ('B', 0x73A, (None, None, ('Management', None)) ), 'gpio15': ('B', 0x73A, (None, None, ('Management', None)) ),
'gpio16': ('B', 0x73B, (None, None, ('Management', None)) ), 'gpio16': ('B', 0x73B, (None, None, ('Management', None)) ),
'flag': ({ 'flag': ({
'value': ('B', 0x73C , (None, None, ('Management', '"Template {{\\\"FLAG\\\":{}}}".format($)')) ), '_': ('B', 0x73C , (None, None, ('Management', '"Template {{\\\"FLAG\\\":{}}}".format($)')),(None, False) ),
'adc0': ('B', (0x73C,1,0), (None, None, ('Management', None)) ), 'adc0': ('B', (0x73C,1,0), (None, None, ('Management', None)) ),
'pullup': ('B', (0x73C,1,1), (None, None, ('Management', None)) ), 'pullup': ('B', (0x73C,1,1), (None, None, ('Management', None)) ),
}, 0x73C, (None, None, ('Management', None)) }, 0x73C, (None, None, ('Management', None))
@ -880,10 +883,22 @@ Setting_6_4_1_18['flag3'][0].update ({
# ====================================================================== # ======================================================================
Setting_6_5_0_3 = copy.deepcopy(Setting_6_4_1_18) Setting_6_5_0_3 = copy.deepcopy(Setting_6_4_1_18)
Setting_6_5_0_3.update({ Setting_6_5_0_3.update({
'novasds_period': ('B', 0x73D, (None, '0 <= $ <= 255', ('Sensor', '"Sensor20 {}".format($)')) ), 'novasds_period': ('B', 0x73D, (None, '1 <= $ <= 255', ('Sensor', '"Sensor20 {}".format($)')) ),
})
# ======================================================================
Setting_6_5_0_6 = copy.deepcopy(Setting_6_5_0_3)
Setting_6_5_0_6.update({
'web_color': ('3B', 0x73E, ([18], None, ('Wifi', '"WebColor{} {}{:06x}".format(#,chr(35),int($,0))')), '"0x{:06x}".format($)' ),
})
# ======================================================================
Setting_6_5_0_7 = copy.deepcopy(Setting_6_5_0_6)
Setting_6_5_0_7.update({
'ledmask': ('<H', 0x7BC, (None, None, ('Main', '"LedMask {}".format($)')), '"0x{:04x}".format($)' ),
}) })
# ====================================================================== # ======================================================================
Settings = [ Settings = [
(0x6050007, 0xe00, Setting_6_5_0_7),
(0x6050006, 0xe00, Setting_6_5_0_6),
(0x6050003, 0xe00, Setting_6_5_0_3), (0x6050003, 0xe00, Setting_6_5_0_3),
(0x6040112, 0xe00, Setting_6_4_1_18), (0x6040112, 0xe00, Setting_6_4_1_18),
(0x6040111, 0xe00, Setting_6_4_1_17), (0x6040111, 0xe00, Setting_6_4_1_17),
@ -1110,11 +1125,11 @@ def GetGroupList(setting):
for name in setting: for name in setting:
dev = setting[name] dev = setting[name]
format, group = GetFieldDef(dev, fields="format, group") format_, group = GetFieldDef(dev, fields="format_, group")
if group is not None and len(group) > 0: if group is not None and len(group) > 0:
groups.add(group) groups.add(group)
if isinstance(format, dict): if isinstance(format_, dict):
subgroups = GetGroupList(format) subgroups = GetGroupList(format_)
if subgroups is not None and len(subgroups) > 0: if subgroups is not None and len(subgroups) > 0:
for group in subgroups: for group in subgroups:
groups.add(group) groups.add(group)
@ -1478,8 +1493,11 @@ def PushTasmotaConfig(encode_cfg, host, port, username=DEFAULTS['source']['usern
# post data # post data
c = pycurl.Curl() c = pycurl.Curl()
header = HTTPHeader() header = HTTPHeader()
from StringIO import StringIO
buffer_ = io.BytesIO()
c.setopt(c.HEADERFUNCTION, header.store) c.setopt(c.HEADERFUNCTION, header.store)
c.setopt(c.WRITEFUNCTION, lambda x: None) c.setopt(c.WRITEFUNCTION, lambda x: None)
c.setopt(c.WRITEDATA, buffer_)
c.setopt(c.POST, 1) c.setopt(c.POST, 1)
c.setopt(c.URL, MakeUrl(host, port, 'u2')) c.setopt(c.URL, MakeUrl(host, port, 'u2'))
if username is not None and password is not None: if username is not None and password is not None:
@ -1506,6 +1524,7 @@ def PushTasmotaConfig(encode_cfg, host, port, username=DEFAULTS['source']['usern
responsecode = c.getinfo(c.RESPONSE_CODE) responsecode = c.getinfo(c.RESPONSE_CODE)
except Exception, e: except Exception, e:
return e[0], e[1] return e[0], e[1]
c.close() c.close()
if responsecode >= 400: if responsecode >= 400:
@ -1513,6 +1532,21 @@ def PushTasmotaConfig(encode_cfg, host, port, username=DEFAULTS['source']['usern
elif header.contenttype() != 'text/html': elif header.contenttype() != 'text/html':
return ExitCode.UPLOAD_CONFIG_ERROR, "Device did not response properly, may be Tasmota webserver admin mode is disabled (WebServer 2)" return ExitCode.UPLOAD_CONFIG_ERROR, "Device did not response properly, may be Tasmota webserver admin mode is disabled (WebServer 2)"
body = buffer_.getvalue()
findUpload = body.find("Upload")
if findUpload < 0:
return ExitCode.UPLOAD_CONFIG_ERROR, "Device did not response properly with upload result page"
body = body[findUpload:]
findSuccessful = body.find("Successful")
if findSuccessful < 0:
errmatch = re.search("<font\s*color='[#0-9a-fA-F]+'>(\S*)</font></b><br/><br/>(.*)<br/>", body)
reason = "Unknown error"
if errmatch and len(errmatch.groups()) > 1:
reason = errmatch.group(2)
return ExitCode.UPLOAD_CONFIG_ERROR, reason
return 0, 'OK' return 0, 'OK'
@ -1556,7 +1590,7 @@ def GetSettingsCrc(dobj):
return crc & 0xffff return crc & 0xffff
def GetFieldDef(fielddef, fields="format, addrdef, baseaddr, bits, bitshift, datadef, arraydef, validate, cmd, group, tasmotacmnd, converter, readconverter, writeconverter"): def GetFieldDef(fielddef, fields="format_, addrdef, baseaddr, bits, bitshift, datadef, arraydef, validate, cmd, group, tasmotacmnd, converter, readconverter, writeconverter"):
""" """
Get field definition items Get field definition items
@ -1570,7 +1604,7 @@ def GetFieldDef(fielddef, fields="format, addrdef, baseaddr, bits, bitshift, dat
@return: @return:
set of values defined in <fields> set of values defined in <fields>
""" """
format = addrdef = baseaddr = datadef = arraydef = validate = cmd = group = tasmotacmnd = converter = readconverter = writeconverter = None format_ = addrdef = baseaddr = datadef = arraydef = validate = cmd = group = tasmotacmnd = converter = readconverter = writeconverter = None
bits = bitshift = 0 bits = bitshift = 0
# calling with nothing is wrong # calling with nothing is wrong
@ -1581,20 +1615,20 @@ def GetFieldDef(fielddef, fields="format, addrdef, baseaddr, bits, bitshift, dat
# get top level items # get top level items
if len(fielddef) == 3: if len(fielddef) == 3:
# converter not present # converter not present
format, addrdef, datadef = fielddef format_, addrdef, datadef = fielddef
elif len(fielddef) == 4: elif len(fielddef) == 4:
# converter present # converter present
format, addrdef, datadef, converter = fielddef format_, addrdef, datadef, converter = fielddef
else: else:
print >> sys.stderr, 'wrong <fielddef> {} length ({}) in setting'.format(fielddef, len(fielddef)) print >> sys.stderr, 'wrong <fielddef> {} length ({}) in setting'.format(fielddef, len(fielddef))
raise SyntaxError('<fielddef> error') raise SyntaxError('<fielddef> error')
# ignore calls with 'root' setting # ignore calls with 'root' setting
if isinstance(format, dict) and baseaddr is None and datadef is None: if isinstance(format_, dict) and baseaddr is None and datadef is None:
return eval(fields) return eval(fields)
if not isinstance(format, (unicode,str,dict)): if not isinstance(format_, (unicode,str,dict)):
print >> sys.stderr, 'wrong <format> {} type {} in <fielddef> {}'.format(format, type(format), fielddef) print >> sys.stderr, 'wrong <format> {} type {} in <fielddef> {}'.format(format_, type(format_), fielddef)
raise SyntaxError('<fielddef> error') raise SyntaxError('<fielddef> error')
# extract addrdef items # extract addrdef items
@ -1604,16 +1638,16 @@ def GetFieldDef(fielddef, fields="format, addrdef, baseaddr, bits, bitshift, dat
# baseaddr bit definition # baseaddr bit definition
baseaddr, bits, bitshift = baseaddr baseaddr, bits, bitshift = baseaddr
if not isinstance(bits, int): if not isinstance(bits, int):
print >> sys.stderr, '<bits> must be a integer in <fielddef> {}'.format(bits, fielddef) print >> sys.stderr, '<bits> must be defined as integer in <fielddef> {}'.format(bits, fielddef)
raise SyntaxError('<fielddef> error') raise SyntaxError('<fielddef> error')
if not isinstance(bitshift, int): if not isinstance(bitshift, int):
print >> sys.stderr, '<bitshift> must be a integer in <fielddef> {}'.format(bitshift, fielddef) print >> sys.stderr, '<bitshift> must be defined as integer in <fielddef> {}'.format(bitshift, fielddef)
raise SyntaxError('<fielddef> error') raise SyntaxError('<fielddef> error')
else: else:
print >> sys.stderr, 'wrong <addrdef> {} length ({}) in <fielddef> {}'.format(addrdef, len(addrdef), fielddef) print >> sys.stderr, 'wrong <addrdef> {} length ({}) in <fielddef> {}'.format(addrdef, len(addrdef), fielddef)
raise SyntaxError('<fielddef> error') raise SyntaxError('<fielddef> error')
if not isinstance(baseaddr, int): if not isinstance(baseaddr, int):
print >> sys.stderr, '<baseaddr> must be a integer in <fielddef> {}'.format(baseaddr, fielddef) print >> sys.stderr, '<baseaddr> must be defined as integer in <fielddef> {}'.format(baseaddr, fielddef)
raise SyntaxError('<fielddef> error') raise SyntaxError('<fielddef> error')
# extract datadef items # extract datadef items
@ -1742,11 +1776,7 @@ def CmndConverter(valuemapping, value, idx, fielddef):
evalstr = tasmotacmnd.replace('$','value').replace('#','idx').replace('@','valuemapping') evalstr = tasmotacmnd.replace('$','value').replace('#','idx').replace('@','valuemapping')
else: else:
evalstr = tasmotacmnd.replace('$','value').replace('@','valuemapping') evalstr = tasmotacmnd.replace('$','value').replace('@','valuemapping')
# ~ try:
result = eval(evalstr) result = eval(evalstr)
# ~ except:
# ~ print evalstr
# ~ print value
elif callable(tasmotacmnd): # use as format function elif callable(tasmotacmnd): # use as format function
if idx is not None: if idx is not None:
@ -1790,6 +1820,46 @@ def ValidateValue(value, fielddef):
return valid return valid
def GetFormatCount(format_):
"""
Get format prefix count
@param format_:
format specifier
@return:
prefix count or 1 if not specified
"""
if isinstance(format_, str):
match = re.search("\s*(\d+)", format_)
if match:
return int(match.group(0))
return 1
def GetFormatType(format_):
"""
Get format type and bitsize without prefix
@param format_:
format specifier
@return:
(format_, 0) or (format without prefix, bitsize)
"""
formattype = format_
bitsize = 0
if isinstance(format_, str):
match = re.search("\s*(\D+)", format_)
if match:
formattype = match.group(0)
bitsize = struct.calcsize(formattype) * 8
return formattype, bitsize
def GetFieldMinMax(fielddef): def GetFieldMinMax(fielddef):
""" """
Get minimum, maximum of field based on field format definition Get minimum, maximum of field based on field format definition
@ -1800,33 +1870,33 @@ def GetFieldMinMax(fielddef):
@return: @return:
min, max min, max
""" """
minmax = {'c': (0, 1), minmax = {'c': (0, 0xff),
'?': (0, 1), '?': (0, 1),
'b': (~0x7f, 0x7f), 'b': (~0x7f, 0x7f),
'B': (0, 0xff), 'B': (0, 0xff),
'h': (~0x7fff, 0x7fff), 'h': (~0x7fff, 0x7fff),
'H': (0, 0xffff), 'H': (0, 0xffff),
'i': (~0x7fffffff, 0x7fffffff), 'i': (~0x7fffffff, 0x7fffffff),
'I': (0, 0xffffffff), 'I': (0, 0xffffffff),
'l': (~0x7fffffff, 0x7fffffff), 'l': (~0x7fffffff, 0x7fffffff),
'L': (0, 0xffffffff), 'L': (0, 0xffffffff),
'q': (~0x7fffffffffffffff, 0x7fffffffffffffff), 'q': (~0x7fffffffffffffff, 0x7fffffffffffffff),
'Q': (0, 0x7fffffffffffffff), 'Q': (0, 0x7fffffffffffffff),
'f': (sys.float_info.min, sys.float_info.max), 'f': (sys.float_info.min, sys.float_info.max),
'd': (sys.float_info.min, sys.float_info.max), 'd': (sys.float_info.min, sys.float_info.max),
} }
format = GetFieldDef(fielddef, fields='format') format_ = GetFieldDef(fielddef, fields='format_')
_min = 0 min_ = 0
_max = 0 max_ = 0
if format[-1:] in minmax: if format_[-1:] in minmax:
_min, _max = minmax[format[-1:]] min_, max_ = minmax[format_[-1:]]
elif format[-1:] in ['s','p']: max_ *= GetFormatCount(format_)
elif format_[-1:] in ['s','p']:
# s and p may have a prefix as length # s and p may have a prefix as length
match = re.search("\s*(\d+)", format) max_ = GetFormatCount(format_)
if match:
_max=int(match.group(0)) return min_,max_
return _min,_max
def GetFieldLength(fielddef): def GetFieldLength(fielddef):
@ -1841,7 +1911,7 @@ def GetFieldLength(fielddef):
""" """
length=0 length=0
format, addrdef, arraydef = GetFieldDef(fielddef, fields='format, addrdef, arraydef') format_, addrdef, arraydef = GetFieldDef(fielddef, fields='format_, addrdef, arraydef')
# <arraydef> contains a integer list # <arraydef> contains a integer list
if isinstance(arraydef, list) and len(arraydef) > 0: if isinstance(arraydef, list) and len(arraydef) > 0:
@ -1850,15 +1920,15 @@ def GetFieldLength(fielddef):
for i in range(0, arraydef[0]): for i in range(0, arraydef[0]):
subfielddef = GetSubfieldDef(fielddef) subfielddef = GetSubfieldDef(fielddef)
if len(arraydef) > 1: if len(arraydef) > 1:
length += GetFieldLength( (format, addrdef, subfielddef) ) length += GetFieldLength( (format_, addrdef, subfielddef) )
# single array # single array
else: else:
length += GetFieldLength( (format, addrdef, None) ) length += GetFieldLength( (format_, addrdef, None) )
elif isinstance(format, dict): elif isinstance(format_, dict):
# -> iterate through format # -> iterate through format
addr = None addr = None
setting = format setting = format_
for name in setting: for name in setting:
baseaddr, bits, bitshift = GetFieldDef(setting[name], fields='baseaddr, bits, bitshift') baseaddr, bits, bitshift = GetFieldDef(setting[name], fields='baseaddr, bits, bitshift')
_len = GetFieldLength(setting[name]) _len = GetFieldLength(setting[name])
@ -1867,20 +1937,8 @@ def GetFieldLength(fielddef):
length += _len length += _len
# a simple value # a simple value
elif isinstance(format, str): elif isinstance(format_, str):
if format[-1:] in ['b','B','c','?']: length = struct.calcsize(format_)
length=1
elif format[-1:] in ['h','H']:
length=2
elif format[-1:] in ['i','I','l','L','f']:
length=4
elif format[-1:] in ['q','Q','d']:
length=8
elif format[-1:] in ['s','p']:
# s and p may have a prefix as length
match = re.search("\s*(\d+)", format)
if match:
length=int(match.group(0))
return length return length
@ -1896,7 +1954,7 @@ def GetSubfieldDef(fielddef):
subfield definition subfield definition
""" """
format, addrdef, datadef, arraydef, validate, cmd, converter = GetFieldDef(fielddef, fields='format, addrdef, datadef, arraydef, validate, cmd, converter') format_, addrdef, datadef, arraydef, validate, cmd, converter = GetFieldDef(fielddef, fields='format_, addrdef, datadef, arraydef, validate, cmd, converter')
# create new arraydef # create new arraydef
if len(arraydef) > 1: if len(arraydef) > 1:
@ -1916,9 +1974,9 @@ def GetSubfieldDef(fielddef):
# set new field def # set new field def
subfielddef = None subfielddef = None
if converter is not None: if converter is not None:
subfielddef = (format, addrdef, datadef, converter) subfielddef = (format_, addrdef, datadef, converter)
else: else:
subfielddef = (format, addrdef, datadef) subfielddef = (format_, addrdef, datadef)
return subfielddef return subfielddef
@ -1941,6 +1999,79 @@ def IsFilterGroup(group):
return True return True
def GetFieldValue(fielddef, dobj, addr):
"""
Get single field value from definition
@param fielddef:
see Settings desc
@param dobj:
decrypted binary config data
@param addr
addr within dobj
@return:
value read from dobj
"""
format_, bits, bitshift = GetFieldDef(fielddef, fields='format_, bits, bitshift')
value_ = 0
unpackedvalue = struct.unpack_from(format_, dobj, addr)
singletype, bitsize = GetFormatType(format_)
if not format_[-1:].lower() in ['s','p']:
for val in unpackedvalue:
value_ <<= bitsize
value_ = value_ + val
value_ = bitsRead(value_, bitshift, bits)
else:
value_ = unpackedvalue[0]
s = str(value_).split('\0')[0] # use left string until \0
value_ = unicode(s, errors='ignore') # remove character > 127
return value_
def SetFieldValue(fielddef, dobj, addr, value):
"""
Set single field value from definition
@param fielddef:
see Settings desc
@param dobj:
decrypted binary config data
@param addr
addr within dobj
@param value
new value
@return:
new decrypted binary config data
"""
format_, bits, bitshift = GetFieldDef(fielddef, fields='format_, bits, bitshift')
formatcnt = GetFormatCount(format_)
singletype, bitsize = GetFormatType(format_)
if args.debug >= 2:
print >> sys.stderr, "SetFieldValue(): fielddef {}, addr 0x{:04x} value {} formatcnt {} singletype {} bitsize {} ".format(fielddef,addr,value,formatcnt,singletype,bitsize)
if not format_[-1:].lower() in ['s','p']:
addr += (bitsize / 8) * formatcnt
for _ in range(0, formatcnt):
addr -= (bitsize / 8)
val = value & ((2**bitsize) - 1)
if args.debug >= 3:
print >> sys.stderr, "SetFieldValue(): Single type - fielddef {}, addr 0x{:04x} value {} singletype {} bitsize {}".format(fielddef,addr,val,singletype,bitsize)
struct.pack_into(singletype, dobj, addr, val)
value >>= bitsize
else:
if args.debug >= 3:
print >> sys.stderr, "SetFieldValue(): String type - fielddef {}, addr 0x{:04x} value {} format_ {}".format(fielddef,addr,value,format_)
struct.pack_into(format_, dobj, addr, value)
return dobj
def GetField(dobj, fieldname, fielddef, raw=False, addroffset=0): def GetField(dobj, fieldname, fielddef, raw=False, addroffset=0):
""" """
Get field value from definition Get field value from definition
@ -1966,7 +2097,7 @@ def GetField(dobj, fieldname, fielddef, raw=False, addroffset=0):
valuemapping = None valuemapping = None
# get field definition # get field definition
format, baseaddr, bits, bitshift, arraydef, group, tasmotacmnd = GetFieldDef(fielddef, fields='format, baseaddr, bits, bitshift, arraydef, group, tasmotacmnd') format_, baseaddr, bits, bitshift, arraydef, group, tasmotacmnd = GetFieldDef(fielddef, fields='format_, baseaddr, bits, bitshift, arraydef, group, tasmotacmnd')
# filter groups # filter groups
if not IsFilterGroup(group): if not IsFilterGroup(group):
@ -1985,36 +2116,24 @@ def GetField(dobj, fieldname, fielddef, raw=False, addroffset=0):
offset += length offset += length
# <format> contains a dict # <format> contains a dict
elif isinstance(format, dict): elif isinstance(format_, dict):
mapping_value = {} mapping_value = {}
# -> iterate through format # -> iterate through format
for name in format: for name in format_:
value = None value = None
value = GetField(dobj, name, format[name], raw=raw, addroffset=addroffset) value = GetField(dobj, name, format_[name], raw=raw, addroffset=addroffset)
if value is not None: if value is not None:
mapping_value[name] = value mapping_value[name] = value
# copy complete returned mapping # copy complete returned mapping
valuemapping = copy.deepcopy(mapping_value) valuemapping = copy.deepcopy(mapping_value)
# a simple value # a simple value
elif isinstance(format, (str, bool, int, float, long)): elif isinstance(format_, (str, bool, int, float, long)):
if GetFieldLength(fielddef) != 0: if GetFieldLength(fielddef) != 0:
valuemapping = struct.unpack_from(format, dobj, baseaddr+addroffset)[0] valuemapping = ReadWriteConverter(GetFieldValue(fielddef, dobj, baseaddr+addroffset), fielddef, read=True, raw=raw)
if not format[-1:].lower() in ['s','p']:
valuemapping = bitsRead(valuemapping, bitshift, bits)
# additional processing for strings
if format[-1:].lower() in ['s','p']:
# use left string until \0
s = str(valuemapping).split('\0')[0]
# remove character > 127
valuemapping = unicode(s, errors='ignore')
valuemapping = ReadWriteConverter(valuemapping, fielddef, read=True, raw=raw)
else: else:
exit(ExitCode.INTERNAL_ERROR, "Wrong mapping format definition: '{}'".format(format), typ=LogType.WARNING, doexit=not args.ignorewarning, line=inspect.getlineno(inspect.currentframe())) exit(ExitCode.INTERNAL_ERROR, "Wrong mapping format definition: '{}'".format(format_), typ=LogType.WARNING, doexit=not args.ignorewarning, line=inspect.getlineno(inspect.currentframe()))
return valuemapping return valuemapping
@ -2039,7 +2158,7 @@ def SetField(dobj, fieldname, fielddef, restore, addroffset=0, filename=""):
@return: @return:
new decrypted binary config data new decrypted binary config data
""" """
format, baseaddr, bits, bitshift, arraydef, group, writeconverter = GetFieldDef(fielddef, fields='format, baseaddr, bits, bitshift, arraydef, group, writeconverter') format_, baseaddr, bits, bitshift, arraydef, group, writeconverter = GetFieldDef(fielddef, fields='format_, baseaddr, bits, bitshift, arraydef, group, writeconverter')
# cast unicode # cast unicode
fieldname = str(fieldname) fieldname = str(fieldname)
@ -2049,8 +2168,8 @@ def SetField(dobj, fieldname, fielddef, restore, addroffset=0, filename=""):
# do not write readonly values # do not write readonly values
if writeconverter is False: if writeconverter is False:
if args.debug: if args.debug >= 2:
print >> sys.stderr, "SetField(): Readonly '{}' using '{}'/{}{} @{} skipped".format(fieldname, format, arraydef, bits, hex(baseaddr+addroffset)) print >> sys.stderr, "SetField(): Readonly '{}' using '{}'/{}{} @{} skipped".format(fieldname, format_, arraydef, bits, hex(baseaddr+addroffset))
return dobj return dobj
# <arraydef> contains a list # <arraydef> contains a list
@ -2069,23 +2188,23 @@ def SetField(dobj, fieldname, fielddef, restore, addroffset=0, filename=""):
offset += length offset += length
# <format> contains a dict # <format> contains a dict
elif isinstance(format, dict): elif isinstance(format_, dict):
for name in format: # -> iterate through format for name in format_: # -> iterate through format
if name in restore: if name in restore:
dobj = SetField(dobj, name, format[name], restore[name], addroffset=addroffset, filename=filename) dobj = SetField(dobj, name, format_[name], restore[name], addroffset=addroffset, filename=filename)
# a simple value # a simple value
elif isinstance(format, (str, bool, int, float, long)): elif isinstance(format_, (str, bool, int, float, long)):
valid = True valid = True
err = "" err = ""
errformat = "" errformat = ""
_min, _max = GetFieldMinMax(fielddef) min_, max_ = GetFieldMinMax(fielddef)
value = _value = None value = _value = None
skip = False skip = False
# simple char value # simple char value
if format[-1:] in ['c']: if format_[-1:] in ['c']:
try: try:
value = ReadWriteConverter(restore.encode(STR_ENCODING)[0], fielddef, read=False) value = ReadWriteConverter(restore.encode(STR_ENCODING)[0], fielddef, read=False)
except Exception, e: except Exception, e:
@ -2093,7 +2212,7 @@ def SetField(dobj, fieldname, fielddef, restore, addroffset=0, filename=""):
valid = False valid = False
# bool # bool
elif format[-1:] in ['?']: elif format_[-1:] in ['?']:
try: try:
value = ReadWriteConverter(bool(restore), fielddef, read=False) value = ReadWriteConverter(bool(restore), fielddef, read=False)
except Exception, e: except Exception, e:
@ -2101,7 +2220,7 @@ def SetField(dobj, fieldname, fielddef, restore, addroffset=0, filename=""):
valid = False valid = False
# integer # integer
elif format[-1:] in ['b','B','h','H','i','I','l','L','q','Q','P']: elif format_[-1:] in ['b','B','h','H','i','I','l','L','q','Q','P']:
value = ReadWriteConverter(restore, fielddef, read=False) value = ReadWriteConverter(restore, fielddef, read=False)
if isinstance(value, (str, unicode)): if isinstance(value, (str, unicode)):
value = int(value, 0) value = int(value, 0)
@ -2110,16 +2229,17 @@ def SetField(dobj, fieldname, fielddef, restore, addroffset=0, filename=""):
# bits # bits
if bits != 0: if bits != 0:
bitvalue = value bitvalue = value
value = struct.unpack_from(format, dobj, baseaddr+addroffset)[0] value = struct.unpack_from(format_, dobj, baseaddr+addroffset)[0]
# validate restore value # validate restore value
valid = ValidateValue(bitvalue, fielddef) valid = ValidateValue(bitvalue, fielddef)
if not valid: if not valid:
err = "valid bit range exceeding" err = "valid bit range exceeding"
value = bitvalue
else: else:
mask = (1<<bits)-1 mask = (1<<bits)-1
if bitvalue > mask: if bitvalue > mask:
_min = 0 min_ = 0
_max = mask max_ = mask
_value = bitvalue _value = bitvalue
valid = False valid = False
else: else:
@ -2142,19 +2262,19 @@ def SetField(dobj, fieldname, fielddef, restore, addroffset=0, filename=""):
_value = value _value = value
# float # float
elif format[-1:] in ['f','d']: elif format_[-1:] in ['f','d']:
try: try:
value = ReadWriteConverter(float(restore), fielddef, read=False) value = ReadWriteConverter(float(restore), fielddef, read=False)
except: except:
valid = False valid = False
# string # string
elif format[-1:] in ['s','p']: elif format_[-1:] in ['s','p']:
value = ReadWriteConverter(restore.encode(STR_ENCODING), fielddef, read=False) value = ReadWriteConverter(restore.encode(STR_ENCODING), fielddef, read=False)
err = "string length exceeding" err = "string length exceeding"
if value is not None: if value is not None:
_max -= 1 max_ -= 1
valid = _min <= len(value) <= _max valid = min_ <= len(value) <= max_
else: else:
skip = True skip = True
valid = True valid = True
@ -2165,7 +2285,7 @@ def SetField(dobj, fieldname, fielddef, restore, addroffset=0, filename=""):
if valid is None and not skip: if valid is None and not skip:
# validate against object type size # validate against object type size
valid = _min <= value <= _max valid = min_ <= value <= max_
if not valid: if not valid:
err = "type range exceeding" err = "type range exceeding"
errformat = " [{smin},{smax}]" errformat = " [{smin},{smax}]"
@ -2179,21 +2299,22 @@ def SetField(dobj, fieldname, fielddef, restore, addroffset=0, filename=""):
if valid: if valid:
if not skip: if not skip:
if args.debug: if args.debug >= 2:
if bits: sbits = " {} bits shift {}".format(bits, bitshift) if bits else ""
sbits=" {} bits shift {}".format(bits, bitshift) strvalue = "{} [{}]".format(_value, hex(value)) if isinstance(_value, int) else _value
else: print >> sys.stderr, "SetField(): Set '{}' using '{}'/{}{} @{} to {}".format(fieldname, format_, arraydef, sbits, hex(baseaddr+addroffset), strvalue)
sbits = "" if fieldname != 'cfg_crc' and fieldname != '_':
print >> sys.stderr, "SetField(): Set '{}' using '{}'/{}{} @{} to {}".format(fieldname, format, arraydef, sbits, hex(baseaddr+addroffset), _value) prevvalue = GetFieldValue(fielddef, dobj, baseaddr+addroffset)
if fieldname != 'cfg_crc': dobj = SetFieldValue(fielddef, dobj, baseaddr+addroffset, value)
prevvalue = struct.unpack_from(format, dobj, baseaddr+addroffset)[0] curvalue = GetFieldValue(fielddef, dobj, baseaddr+addroffset)
struct.pack_into(format, dobj, baseaddr+addroffset, value)
curvalue = struct.unpack_from(format, dobj, baseaddr+addroffset)[0]
if prevvalue != curvalue and args.verbose: if prevvalue != curvalue and args.verbose:
message("Value for '{}' changed from {} to {}".format(fieldname, prevvalue, curvalue), typ=LogType.INFO) message("Value for '{}' changed from {} to {}".format(fieldname, prevvalue, curvalue), typ=LogType.INFO)
else:
if args.debug >= 2:
print >> sys.stderr, "SetField(): Special field '{}' using '{}'/{}{} @{} skipped".format(fieldname, format_, arraydef, bits, hex(baseaddr+addroffset))
else: else:
sformat = "file '{sfile}' - {{'{sname}': {svalue}}} ({serror})"+errformat sformat = "file '{sfile}' - {{'{sname}': {svalue}}} ({serror})"+errformat
exit(ExitCode.RESTORE_DATA_ERROR, sformat.format(sfile=filename, sname=fieldname, serror=err, svalue=_value, smin=_min, smax=_max), typ=LogType.WARNING, doexit=not args.ignorewarning) exit(ExitCode.RESTORE_DATA_ERROR, sformat.format(sfile=filename, sname=fieldname, serror=err, svalue=_value, smin=min_, smax=max_), typ=LogType.WARNING, doexit=not args.ignorewarning)
return dobj return dobj
@ -2220,7 +2341,7 @@ def SetCmnd(cmnds, fieldname, fielddef, valuemapping, mappedvalue, addroffset=0,
@return: @return:
new Tasmota command mapping new Tasmota command mapping
""" """
format, baseaddr, bits, bitshift, arraydef, group, tasmotacmnd, writeconverter = GetFieldDef(fielddef, fields='format, baseaddr, bits, bitshift, arraydef, group, tasmotacmnd, writeconverter') format_, baseaddr, bits, bitshift, arraydef, group, tasmotacmnd, writeconverter = GetFieldDef(fielddef, fields='format_, baseaddr, bits, bitshift, arraydef, group, tasmotacmnd, writeconverter')
# cast unicode # cast unicode
fieldname = str(fieldname) fieldname = str(fieldname)
@ -2245,13 +2366,13 @@ def SetCmnd(cmnds, fieldname, fielddef, valuemapping, mappedvalue, addroffset=0,
offset += length offset += length
# <format> contains a dict # <format> contains a dict
elif isinstance(format, dict): elif isinstance(format_, dict):
for name in format: # -> iterate through format for name in format_: # -> iterate through format
if name in mappedvalue: if name in mappedvalue:
cmnds = SetCmnd(cmnds, name, format[name], valuemapping, mappedvalue[name], addroffset=addroffset, idx=idx) cmnds = SetCmnd(cmnds, name, format_[name], valuemapping, mappedvalue[name], addroffset=addroffset, idx=idx)
# a simple value # a simple value
elif isinstance(format, (str, bool, int, float, long)): elif isinstance(format_, (str, bool, int, float, long)):
cmnd = CmndConverter(valuemapping, mappedvalue, idx, fielddef) cmnd = CmndConverter(valuemapping, mappedvalue, idx, fielddef)
if group is not None and cmnd is not None: if group is not None and cmnd is not None:
@ -2804,7 +2925,7 @@ def ParseArgs():
info = parser.add_argument_group('Info','Extra information') info = parser.add_argument_group('Info','Extra information')
info.add_argument('-D', '--debug', info.add_argument('-D', '--debug',
dest='debug', dest='debug',
action='store_true', action='count',
help=configargparse.SUPPRESS) help=configargparse.SUPPRESS)
info.add_argument('-h', '--help', info.add_argument('-h', '--help',
dest='shorthelp', dest='shorthelp',
@ -2823,7 +2944,7 @@ def ParseArgs():
args = parser.parse_args() args = parser.parse_args()
if args.debug: if args.debug >= 1:
print >> sys.stderr, parser.format_values() print >> sys.stderr, parser.format_values()
print >> sys.stderr, "Settings:" print >> sys.stderr, "Settings:"
for k in args.__dict__: for k in args.__dict__: