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:
* 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
Tsl2561Util Convenience functions like lux calculation or automatic gain
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>
// 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, ... ) {
static char buf[128];
va_list arg;
@ -37,7 +37,7 @@ uint8_t id;
void setup() {
Serial.begin(115200);
Wire.begin();
Wire.begin(TSL2561_SDA, TSL2561_SCL);
while( !Tsl.begin() )
; // wait until chip detected or wdt reset
Serial.println("\nStarting Tsl2561Util autogain loop");

View File

@ -21,7 +21,7 @@ This file is part of the Joba_Tsl2561 Library.
#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, ... ) {
static char buf[128];
va_list arg;
@ -36,7 +36,7 @@ Tsl2561 Tsl(Wire);
void setup() {
Serial.begin(115200);
Wire.begin();
Wire.begin(TSL2561_SDA, TSL2561_SCL);
Serial.println("\nStarting Tsl2561 simple loop");
}
@ -60,7 +60,7 @@ void loop() {
Tsl.off();
}
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);

View File

@ -22,7 +22,7 @@ This file is part of the Joba_Tsl2561 Library.
#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, ... ) {
static char buf[128];
va_list arg;
@ -158,7 +158,7 @@ void test( Tsl2561 &tsl ) {
void setup() {
Serial.begin(115200);
Wire.begin();
Wire.begin(TSL2561_SDA, TSL2561_SCL);
Serial.println("\nStarting Tsl2561 testing loop");
}

View File

@ -21,7 +21,7 @@ This file is part of the Joba_Tsl2561 Library.
#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, ... ) {
static char buf[128];
va_list arg;
@ -37,7 +37,7 @@ Tsl2561 Tsl(Wire);
void setup() {
Serial.begin(115200);
Wire.begin();
Wire.begin(TSL2561_SDA, TSL2561_SCL);
Serial.println("\nStarting Tsl2561Util loop");
}
@ -91,7 +91,7 @@ void loop() {
}
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);

View File

@ -18,9 +18,13 @@
[platformio]
src_dir = .
lib_dir = ../..
; uncomment one, if you want to build only one
; env_default = nodemcuv2
; env_default = nano328
; env_default = espressif32
[env:nodemcuv2]
@ -28,12 +32,12 @@
; ------------
; GND <-> GND
; VCC <-> 3V
; SCL <-> D1
; SDA <-> D2
; SCL <-> D1 (other pin can be defined below)
; SDA <-> D2 (other pin can be defined below)
platform = espressif8266
board = nodemcuv2
framework = arduino
build_flags = -Wall
build_flags = -Wall -DTSL2561_SCL=D1 -DTSL2561_SDA=D2
monitor_speed = 115200
@ -53,6 +57,25 @@ upload_resetmethod = nodemcu
platform = atmelavr
board = nanoatmega328
framework = arduino
build_flags = -Wall
build_flags = -Wall -DTSL2561_SCL=A5 -DTSL2561_SDA=A4
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",
"version": "2.0.7",
"version": "2.0.10",
"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":
{
"type": "git",

View File

@ -1,9 +1,9 @@
name=Joba Tsl2561 Library
version=2.0.7
version=2.0.10
author=joba-1
maintainer=joba-1 <joban123.psn@gmail.com>
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
url=https://github.com/joba-1/Joba_Tsl2561
architectures=*

View File

@ -18,20 +18,19 @@
[platformio]
src_dir = .
lib_dir = ../..
; uncomment one, if you want to build only one
; env_default = nodemcuv2
; env_default = nano328
; env_default = espressif32
[env:nodemcuv2]
; TSL <-> ESP8266
; ------------
; GND <-> GND
; VCC <-> 3V
; SCL <-> D1
; SDA <-> D2
; SCL <-> D1 (other pin can be defined below)
; SDA <-> D2 (other pin can be defined below)
platform = espressif8266
board = nodemcuv2
framework = arduino
@ -58,4 +57,21 @@ framework = arduino
build_flags = -Wall
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]

View File

@ -23,6 +23,14 @@ This file is part of the Joba_Tsl2561 Library.
#include <Arduino.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 {
public:

View File

@ -22,9 +22,10 @@ This file is part of the Joba_Tsl2561 Library.
namespace Tsl2561Util {
// 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
// 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 ) {
uint16_t scaledFull = (uint16_t)full;
@ -39,8 +40,8 @@ bool normalizedLuminosity( bool gain, Tsl2561::exposure_t exposure, uint32_t &fu
switch( exposure ) {
case Tsl2561::EXP_14:
full = (scaledFull >= 5047/4*3) ? 0xffffffff : ((full + 5) * 322) / 11;
ir = (scaledIr >= 5047/4*3) ? 0xffffffff : ((ir + 5) * 322) / 11;
full = (scaledFull == 5047) ? 0xffffffff : ((full + 5) * 322) / 11;
ir = (scaledIr == 5047) ? 0xffffffff : ((ir + 5) * 322) / 11;
break;
case Tsl2561::EXP_101:
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 full != 0xffffffff && ir != 0xffffffff;
return exposure == Tsl2561::EXP_14 || (full != 0xffffffff && ir != 0xffffffff);
}
return false;
@ -93,6 +94,8 @@ bool autoGain( Tsl2561 &tsl, bool &gain, Tsl2561::exposure_t &exposure, uint16_t
{ true, Tsl2561::EXP_402 } // max
};
// Serial.printf("autoGain start: gain=%u, expo=%u\n", gain, exposure);
// get current sensitivity
if( !tsl.getSensitivity(gain, exposure) ) {
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...
}
// 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;
// in a loop wait for next sample, get values and adjust sensitivity if needed
while( true ) {
waitNext(exposure);
@ -122,11 +126,14 @@ bool autoGain( Tsl2561 &tsl, bool &gain, Tsl2561::exposure_t &exposure, uint16_t
uint16_t limit = getLimit(exposure);
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]))
|| (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) ) {
return false; // I2C error
}
@ -134,7 +141,10 @@ bool autoGain( Tsl2561 &tsl, bool &gain, Tsl2561::exposure_t &exposure, uint16_t
exposure = sensitivity[curr].exposure;
}
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
}
}
@ -176,6 +186,11 @@ bool milliLux( uint32_t full, uint32_t ir, uint32_t &mLux, bool csType, uint8_t
return true;
}
if( full == 0xffffffff || ir == 0xffffffff ) {
mLux = 99999999; // indicates saturation at shortest exposure
return true;
}
uint32_t milliRatio = ir * 1000 / full;
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);
if (m_buffer == NULL) return;
// 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);
tms_obj_list[m_rx_pin] = this;
attachInterrupt(m_rx_pin, ISRList[m_rx_pin], FALLING);
@ -145,8 +145,8 @@ bool TasmotaSerial::begin(long speed, int stop_bits) {
}
} else {
// Use getCycleCount() loop to get as exact timing as possible
m_bit_time = F_CPU / speed;
m_high_speed = (speed > 9600);
m_bit_time = ESP.getCpuFreqMHz() * 1000000 / speed;
m_high_speed = (speed >= 9600);
}
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 lost syslog connection regression from 6.5.0.4
* 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 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 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 spare15 : 1;
uint32_t spare16 : 1;

View File

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

View File

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

View File

@ -584,6 +584,16 @@ float ConvertTemp(float c)
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)
{
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;}"
"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
"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;}";
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

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_data_len = 0; // Data lenght of command
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
int tuya_byte_counter = 0; // Index in serial receive buffer
@ -294,6 +295,7 @@ void TuyaInit(void)
TuyaSendCmd(TUYA_CMD_MCU_CONF);
}
}
tuya_heartbeat_timer = 0; // init heartbeat timer when dimmer init is done
}
void TuyaSerialInput(void)
@ -410,6 +412,11 @@ bool Xdrv16(uint8_t function)
break;
case FUNC_EVERY_SECOND:
if(TuyaSerial && tuya_wifi_state!=WifiState()) { TuyaSetWifiLed(); }
tuya_heartbeat_timer++;
if (tuya_heartbeat_timer > 10) {
tuya_heartbeat_timer = 0;
TuyaSendCmd(TUYA_CMD_HEARTBEAT);
}
break;
case FUNC_SET_CHANNELS:
result = TuyaSetChannels();

View File

@ -162,9 +162,11 @@ void Ade7953EnergyEverySecond()
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);
}
#endif // USE_ADC_VCC
}
void Ade7953DrvInit(void)
@ -190,7 +192,7 @@ bool Ade7953Command(void)
{
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 (1 == XdrvMailbox.payload) { XdrvMailbox.payload = ADE7953_PREF; }
@ -206,17 +208,23 @@ bool Ade7953Command(void)
}
else if (CMND_POWERSET == energy_command_code) {
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) {
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) {
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

View File

@ -36,6 +36,7 @@
#define ANALOG_B 3350.0 // Thermistor Beta Coefficient
uint16_t adc_last_value = 0;
float adc_temp = 0;
uint16_t AdcRead(uint8_t factor)
{
@ -69,6 +70,22 @@ void AdcEvery250ms(void)
}
#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)
{
if (my_module_flag.adc0) {
@ -83,14 +100,8 @@ void AdcShow(bool json)
}
}
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];
dtostrfd(temp, Settings.flag2.temperature_resolution, temperature);
dtostrfd(adc_temp, Settings.flag2.temperature_resolution, temperature);
if (json) {
ResponseAppend_P(JSON_SNS_TEMP, "ANALOG", temperature);
@ -127,6 +138,9 @@ bool Xsns02(uint8_t function)
AdcEvery250ms();
break;
#endif // USE_RULES
case FUNC_EVERY_SECOND:
AdcEverySecond();
break;
case FUNC_JSON_APPEND:
AdcShow(1);
break;

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
VER = '2.1.0023'
VER = '2.1.0024'
"""
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($)')) ),
'ws_clock_reverse': ('<L', (0x010,1,16), (None, None, ('SetOption', '"SetOption16 {}".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($)')) ),
'timezone': ('b', 0x016, (None, '-13 <= $ <= 13 or $==99', ('Management', '"Timezone {}".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($)')) ),
'power7': ('<L', (0x2E8,1,6), (None, None, ('Main', '"Power7 {}".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(#,$)')) ),
'altitude': ('<h', 0x2F6, (None, '-30000 <= $ <= 30000', ('Sensor', '"Altitude {}".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_voltage_calibration': ('<L', 0x368, (None, None, ('Pow', '"VoltageSet {}".format($)')) ),
'energy_current_calibration': ('<L', 0x36C, (None, None, ('Pow', '"CurrentSet {}".format($)')) ),
'energy_kWhtoday': ('<L', 0x370, (None, '0 <= $ <= 42500', ('Pow', '"EnergyReset1 {}".format($)')) ),
'energy_kWhyesterday': ('<L', 0x374, (None, '0 <= $ <= 42500', ('Pow', '"EnergyReset2 {}".format($)')) ),
'energy_kWhtoday': ('<L', 0x370, (None, '0 <= $ <= 4250000', ('Pow', '"EnergyReset1 {}".format(int(round(float($)/100)))')) ),
'energy_kWhyesterday': ('<L', 0x374, (None, '0 <= $ <= 4250000', ('Pow', '"EnergyReset2 {}".format(int(round(float($)/100)))')) ),
'energy_kWhdoy': ('<H', 0x378, (None, None, ('Pow', None)) ),
'energy_min_power': ('<H', 0x37A, (None, None, ('Pow', '"PowerLow {}".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(#,$)')) ),
'ntp_server': ('33s', 0x4CE, ([3], None, ('Wifi', '"NtpServer{} {}".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]")),
'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($)')) ),
'flag2': ({
'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($)')) ),
'humidity_resolution': ('<L', (0x5BC,2,28), (None, '0 <= $ <= 3', ('Sensor', '"HumRes {}".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_type': ('<H', 0x5D0, (None, None, ('Sensor', '"CounterType {}".format($)')) ),
'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_type3': ('<H', (0x5D0,1,2), (None, None, ('Sensor', '"CounterType3 {}".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($)')) ),
'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_CB_registered': ('B', 0x4A8, (None, None, ('KNX', None)) ),
'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)) ),
'window': ('<L', (0x670, 4,11),(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)) ),
'dow': ('<H', (0x2E2,3, 8), (None, '1 <= $ <= 7', ('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,$)')) ),
'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($)')) ),
'rule2': ('B', (0x49F,1,1), (None, None, ('Management', '"Rule2 {}".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': ({
'rule1': ('B', (0x4A0,1,0), (None, None, ('Management', '"Rule1 {}".format($+4)')) ),
'rule2': ('B', (0x4A0,1,1), (None, None, ('Management', '"Rule2 {}".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 $)')) ),
'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($)' ),
'switchmode': ('B', 0x3A4, ([8], '0 <= $ <= 7', ('Main', '"SwitchMode{} {}".format(#,$)')) ),
'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)) ),
'pullup': ('<L', (0x6F6,1, 3), (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_defer': ('<L', (0x6F6,4, 7), (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 ({
'rf_receive_decimal': ('<L', (0x010,1,28), (None, None, ('SetOption' , '"SetOption28 {}".format($)')) ),
@ -687,7 +687,7 @@ Setting_6_2_1.update ({
'flag3': ({
'timers_enable': ('<L', (0x3A0,1, 0), (None, None, ('Timers', '"Timers {}".format($)')) ),
'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($)')) ),
'switch_debounce': ('<H', 0x66E, (None, '40 <= $ <= 1000', ('Main', '"SwitchDebounce {}".format($)')) ),
'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 ({
'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.update ({
'SensorBits1': ({
'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)
@ -861,7 +864,7 @@ Setting_6_4_1_16.update({
'gpio15': ('B', 0x73A, (None, None, ('Management', None)) ),
'gpio16': ('B', 0x73B, (None, None, ('Management', None)) ),
'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)) ),
'pullup': ('B', (0x73C,1,1), (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.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 = [
(0x6050007, 0xe00, Setting_6_5_0_7),
(0x6050006, 0xe00, Setting_6_5_0_6),
(0x6050003, 0xe00, Setting_6_5_0_3),
(0x6040112, 0xe00, Setting_6_4_1_18),
(0x6040111, 0xe00, Setting_6_4_1_17),
@ -1110,11 +1125,11 @@ def GetGroupList(setting):
for name in setting:
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:
groups.add(group)
if isinstance(format, dict):
subgroups = GetGroupList(format)
if isinstance(format_, dict):
subgroups = GetGroupList(format_)
if subgroups is not None and len(subgroups) > 0:
for group in subgroups:
groups.add(group)
@ -1478,8 +1493,11 @@ def PushTasmotaConfig(encode_cfg, host, port, username=DEFAULTS['source']['usern
# post data
c = pycurl.Curl()
header = HTTPHeader()
from StringIO import StringIO
buffer_ = io.BytesIO()
c.setopt(c.HEADERFUNCTION, header.store)
c.setopt(c.WRITEFUNCTION, lambda x: None)
c.setopt(c.WRITEDATA, buffer_)
c.setopt(c.POST, 1)
c.setopt(c.URL, MakeUrl(host, port, 'u2'))
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)
except Exception, e:
return e[0], e[1]
c.close()
if responsecode >= 400:
@ -1513,6 +1532,21 @@ def PushTasmotaConfig(encode_cfg, host, port, username=DEFAULTS['source']['usern
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)"
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'
@ -1556,7 +1590,7 @@ def GetSettingsCrc(dobj):
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
@ -1570,7 +1604,7 @@ def GetFieldDef(fielddef, fields="format, addrdef, baseaddr, bits, bitshift, dat
@return:
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
# calling with nothing is wrong
@ -1581,20 +1615,20 @@ def GetFieldDef(fielddef, fields="format, addrdef, baseaddr, bits, bitshift, dat
# get top level items
if len(fielddef) == 3:
# converter not present
format, addrdef, datadef = fielddef
format_, addrdef, datadef = fielddef
elif len(fielddef) == 4:
# converter present
format, addrdef, datadef, converter = fielddef
format_, addrdef, datadef, converter = fielddef
else:
print >> sys.stderr, 'wrong <fielddef> {} length ({}) in setting'.format(fielddef, len(fielddef))
raise SyntaxError('<fielddef> error')
# 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)
if not isinstance(format, (unicode,str,dict)):
print >> sys.stderr, 'wrong <format> {} type {} in <fielddef> {}'.format(format, type(format), fielddef)
if not isinstance(format_, (unicode,str,dict)):
print >> sys.stderr, 'wrong <format> {} type {} in <fielddef> {}'.format(format_, type(format_), fielddef)
raise SyntaxError('<fielddef> error')
# extract addrdef items
@ -1604,16 +1638,16 @@ def GetFieldDef(fielddef, fields="format, addrdef, baseaddr, bits, bitshift, dat
# baseaddr bit definition
baseaddr, bits, bitshift = baseaddr
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')
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')
else:
print >> sys.stderr, 'wrong <addrdef> {} length ({}) in <fielddef> {}'.format(addrdef, len(addrdef), fielddef)
raise SyntaxError('<fielddef> error')
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')
# extract datadef items
@ -1742,11 +1776,7 @@ def CmndConverter(valuemapping, value, idx, fielddef):
evalstr = tasmotacmnd.replace('$','value').replace('#','idx').replace('@','valuemapping')
else:
evalstr = tasmotacmnd.replace('$','value').replace('@','valuemapping')
# ~ try:
result = eval(evalstr)
# ~ except:
# ~ print evalstr
# ~ print value
elif callable(tasmotacmnd): # use as format function
if idx is not None:
@ -1790,6 +1820,46 @@ def ValidateValue(value, fielddef):
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):
"""
Get minimum, maximum of field based on field format definition
@ -1800,33 +1870,33 @@ def GetFieldMinMax(fielddef):
@return:
min, max
"""
minmax = {'c': (0, 1),
'?': (0, 1),
'b': (~0x7f, 0x7f),
'B': (0, 0xff),
'h': (~0x7fff, 0x7fff),
'H': (0, 0xffff),
'i': (~0x7fffffff, 0x7fffffff),
'I': (0, 0xffffffff),
'l': (~0x7fffffff, 0x7fffffff),
'L': (0, 0xffffffff),
minmax = {'c': (0, 0xff),
'?': (0, 1),
'b': (~0x7f, 0x7f),
'B': (0, 0xff),
'h': (~0x7fff, 0x7fff),
'H': (0, 0xffff),
'i': (~0x7fffffff, 0x7fffffff),
'I': (0, 0xffffffff),
'l': (~0x7fffffff, 0x7fffffff),
'L': (0, 0xffffffff),
'q': (~0x7fffffffffffffff, 0x7fffffffffffffff),
'Q': (0, 0x7fffffffffffffff),
'f': (sys.float_info.min, sys.float_info.max),
'd': (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),
}
format = GetFieldDef(fielddef, fields='format')
_min = 0
_max = 0
if format[-1:] in minmax:
_min, _max = minmax[format[-1:]]
elif format[-1:] in ['s','p']:
format_ = GetFieldDef(fielddef, fields='format_')
min_ = 0
max_ = 0
if format_[-1:] in minmax:
min_, max_ = minmax[format_[-1:]]
max_ *= GetFormatCount(format_)
elif format_[-1:] in ['s','p']:
# s and p may have a prefix as length
match = re.search("\s*(\d+)", format)
if match:
_max=int(match.group(0))
return _min,_max
max_ = GetFormatCount(format_)
return min_,max_
def GetFieldLength(fielddef):
@ -1841,7 +1911,7 @@ def GetFieldLength(fielddef):
"""
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
if isinstance(arraydef, list) and len(arraydef) > 0:
@ -1850,15 +1920,15 @@ def GetFieldLength(fielddef):
for i in range(0, arraydef[0]):
subfielddef = GetSubfieldDef(fielddef)
if len(arraydef) > 1:
length += GetFieldLength( (format, addrdef, subfielddef) )
length += GetFieldLength( (format_, addrdef, subfielddef) )
# single array
else:
length += GetFieldLength( (format, addrdef, None) )
length += GetFieldLength( (format_, addrdef, None) )
elif isinstance(format, dict):
elif isinstance(format_, dict):
# -> iterate through format
addr = None
setting = format
setting = format_
for name in setting:
baseaddr, bits, bitshift = GetFieldDef(setting[name], fields='baseaddr, bits, bitshift')
_len = GetFieldLength(setting[name])
@ -1867,20 +1937,8 @@ def GetFieldLength(fielddef):
length += _len
# a simple value
elif isinstance(format, str):
if format[-1:] in ['b','B','c','?']:
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))
elif isinstance(format_, str):
length = struct.calcsize(format_)
return length
@ -1896,7 +1954,7 @@ def GetSubfieldDef(fielddef):
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
if len(arraydef) > 1:
@ -1916,9 +1974,9 @@ def GetSubfieldDef(fielddef):
# set new field def
subfielddef = None
if converter is not None:
subfielddef = (format, addrdef, datadef, converter)
subfielddef = (format_, addrdef, datadef, converter)
else:
subfielddef = (format, addrdef, datadef)
subfielddef = (format_, addrdef, datadef)
return subfielddef
@ -1941,6 +1999,79 @@ def IsFilterGroup(group):
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):
"""
Get field value from definition
@ -1966,7 +2097,7 @@ def GetField(dobj, fieldname, fielddef, raw=False, addroffset=0):
valuemapping = None
# 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
if not IsFilterGroup(group):
@ -1985,36 +2116,24 @@ def GetField(dobj, fieldname, fielddef, raw=False, addroffset=0):
offset += length
# <format> contains a dict
elif isinstance(format, dict):
elif isinstance(format_, dict):
mapping_value = {}
# -> iterate through format
for name in format:
for name in format_:
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:
mapping_value[name] = value
# copy complete returned mapping
valuemapping = copy.deepcopy(mapping_value)
# a simple value
elif isinstance(format, (str, bool, int, float, long)):
elif isinstance(format_, (str, bool, int, float, long)):
if GetFieldLength(fielddef) != 0:
valuemapping = struct.unpack_from(format, dobj, baseaddr+addroffset)[0]
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)
valuemapping = ReadWriteConverter(GetFieldValue(fielddef, dobj, baseaddr+addroffset), fielddef, read=True, raw=raw)
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
@ -2039,7 +2158,7 @@ def SetField(dobj, fieldname, fielddef, restore, addroffset=0, filename=""):
@return:
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
fieldname = str(fieldname)
@ -2049,8 +2168,8 @@ def SetField(dobj, fieldname, fielddef, restore, addroffset=0, filename=""):
# do not write readonly values
if writeconverter is False:
if args.debug:
print >> sys.stderr, "SetField(): Readonly '{}' using '{}'/{}{} @{} skipped".format(fieldname, format, arraydef, bits, hex(baseaddr+addroffset))
if args.debug >= 2:
print >> sys.stderr, "SetField(): Readonly '{}' using '{}'/{}{} @{} skipped".format(fieldname, format_, arraydef, bits, hex(baseaddr+addroffset))
return dobj
# <arraydef> contains a list
@ -2069,23 +2188,23 @@ def SetField(dobj, fieldname, fielddef, restore, addroffset=0, filename=""):
offset += length
# <format> contains a dict
elif isinstance(format, dict):
for name in format: # -> iterate through format
elif isinstance(format_, dict):
for name in format_: # -> iterate through format
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
elif isinstance(format, (str, bool, int, float, long)):
elif isinstance(format_, (str, bool, int, float, long)):
valid = True
err = ""
errformat = ""
_min, _max = GetFieldMinMax(fielddef)
min_, max_ = GetFieldMinMax(fielddef)
value = _value = None
skip = False
# simple char value
if format[-1:] in ['c']:
if format_[-1:] in ['c']:
try:
value = ReadWriteConverter(restore.encode(STR_ENCODING)[0], fielddef, read=False)
except Exception, e:
@ -2093,7 +2212,7 @@ def SetField(dobj, fieldname, fielddef, restore, addroffset=0, filename=""):
valid = False
# bool
elif format[-1:] in ['?']:
elif format_[-1:] in ['?']:
try:
value = ReadWriteConverter(bool(restore), fielddef, read=False)
except Exception, e:
@ -2101,7 +2220,7 @@ def SetField(dobj, fieldname, fielddef, restore, addroffset=0, filename=""):
valid = False
# 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)
if isinstance(value, (str, unicode)):
value = int(value, 0)
@ -2110,16 +2229,17 @@ def SetField(dobj, fieldname, fielddef, restore, addroffset=0, filename=""):
# bits
if bits != 0:
bitvalue = value
value = struct.unpack_from(format, dobj, baseaddr+addroffset)[0]
value = struct.unpack_from(format_, dobj, baseaddr+addroffset)[0]
# validate restore value
valid = ValidateValue(bitvalue, fielddef)
if not valid:
err = "valid bit range exceeding"
value = bitvalue
else:
mask = (1<<bits)-1
if bitvalue > mask:
_min = 0
_max = mask
min_ = 0
max_ = mask
_value = bitvalue
valid = False
else:
@ -2142,19 +2262,19 @@ def SetField(dobj, fieldname, fielddef, restore, addroffset=0, filename=""):
_value = value
# float
elif format[-1:] in ['f','d']:
elif format_[-1:] in ['f','d']:
try:
value = ReadWriteConverter(float(restore), fielddef, read=False)
except:
valid = False
# string
elif format[-1:] in ['s','p']:
elif format_[-1:] in ['s','p']:
value = ReadWriteConverter(restore.encode(STR_ENCODING), fielddef, read=False)
err = "string length exceeding"
if value is not None:
_max -= 1
valid = _min <= len(value) <= _max
max_ -= 1
valid = min_ <= len(value) <= max_
else:
skip = True
valid = True
@ -2165,7 +2285,7 @@ def SetField(dobj, fieldname, fielddef, restore, addroffset=0, filename=""):
if valid is None and not skip:
# validate against object type size
valid = _min <= value <= _max
valid = min_ <= value <= max_
if not valid:
err = "type range exceeding"
errformat = " [{smin},{smax}]"
@ -2179,21 +2299,22 @@ def SetField(dobj, fieldname, fielddef, restore, addroffset=0, filename=""):
if valid:
if not skip:
if args.debug:
if bits:
sbits=" {} bits shift {}".format(bits, bitshift)
else:
sbits = ""
print >> sys.stderr, "SetField(): Set '{}' using '{}'/{}{} @{} to {}".format(fieldname, format, arraydef, sbits, hex(baseaddr+addroffset), _value)
if fieldname != 'cfg_crc':
prevvalue = struct.unpack_from(format, dobj, baseaddr+addroffset)[0]
struct.pack_into(format, dobj, baseaddr+addroffset, value)
curvalue = struct.unpack_from(format, dobj, baseaddr+addroffset)[0]
if args.debug >= 2:
sbits = " {} bits shift {}".format(bits, bitshift) if bits else ""
strvalue = "{} [{}]".format(_value, hex(value)) if isinstance(_value, int) else _value
print >> sys.stderr, "SetField(): Set '{}' using '{}'/{}{} @{} to {}".format(fieldname, format_, arraydef, sbits, hex(baseaddr+addroffset), strvalue)
if fieldname != 'cfg_crc' and fieldname != '_':
prevvalue = GetFieldValue(fielddef, dobj, baseaddr+addroffset)
dobj = SetFieldValue(fielddef, dobj, baseaddr+addroffset, value)
curvalue = GetFieldValue(fielddef, dobj, baseaddr+addroffset)
if prevvalue != curvalue and args.verbose:
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:
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
@ -2220,7 +2341,7 @@ def SetCmnd(cmnds, fieldname, fielddef, valuemapping, mappedvalue, addroffset=0,
@return:
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
fieldname = str(fieldname)
@ -2245,13 +2366,13 @@ def SetCmnd(cmnds, fieldname, fielddef, valuemapping, mappedvalue, addroffset=0,
offset += length
# <format> contains a dict
elif isinstance(format, dict):
for name in format: # -> iterate through format
elif isinstance(format_, dict):
for name in format_: # -> iterate through format
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
elif isinstance(format, (str, bool, int, float, long)):
elif isinstance(format_, (str, bool, int, float, long)):
cmnd = CmndConverter(valuemapping, mappedvalue, idx, fielddef)
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.add_argument('-D', '--debug',
dest='debug',
action='store_true',
action='count',
help=configargparse.SUPPRESS)
info.add_argument('-h', '--help',
dest='shorthelp',
@ -2823,7 +2944,7 @@ def ParseArgs():
args = parser.parse_args()
if args.debug:
if args.debug >= 1:
print >> sys.stderr, parser.format_values()
print >> sys.stderr, "Settings:"
for k in args.__dict__: