mirror of https://github.com/arendst/Tasmota.git
Merge pull request #4304 from curzon01/development
add v6.3.0.4 config changes
This commit is contained in:
commit
c7d920f1df
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
VER = '2.0.0004'
|
VER = '2.0.0005'
|
||||||
|
|
||||||
"""
|
"""
|
||||||
decode-config.py - Backup/Restore Sonoff-Tasmota configuration data
|
decode-config.py - Backup/Restore Sonoff-Tasmota configuration data
|
||||||
|
@ -155,6 +155,7 @@ def ModuleImportError(module):
|
||||||
sys.exit(ExitCode.MODULE_NOT_FOUND)
|
sys.exit(ExitCode.MODULE_NOT_FOUND)
|
||||||
try:
|
try:
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import copy
|
||||||
import struct
|
import struct
|
||||||
import socket
|
import socket
|
||||||
import re
|
import re
|
||||||
|
@ -445,8 +446,8 @@ Setting_5_10_0 = {
|
||||||
'pulse_counter_debounce': ('<H', 0x5D2, None),
|
'pulse_counter_debounce': ('<H', 0x5D2, None),
|
||||||
'rf_code': ('B', 0x5D4, [17,9], '"0x{:02x}".format($)'),
|
'rf_code': ('B', 0x5D4, [17,9], '"0x{:02x}".format($)'),
|
||||||
}
|
}
|
||||||
|
# ========
|
||||||
Setting_5_11_0 = Setting_5_10_0
|
Setting_5_11_0 = copy.deepcopy(Setting_5_10_0)
|
||||||
Setting_5_11_0.update({
|
Setting_5_11_0.update({
|
||||||
'display_model': ('B', 0x2D2, None),
|
'display_model': ('B', 0x2D2, None),
|
||||||
'display_mode': ('B', 0x2D3, None),
|
'display_mode': ('B', 0x2D3, None),
|
||||||
|
@ -461,15 +462,15 @@ Setting_5_11_0['flag'][0].update ({
|
||||||
'light_signal': ('<L', (0x010, 1, 18), None),
|
'light_signal': ('<L', (0x010, 1, 18), None),
|
||||||
})
|
})
|
||||||
Setting_5_11_0.pop('mqtt_fingerprinth',None)
|
Setting_5_11_0.pop('mqtt_fingerprinth',None)
|
||||||
|
# ========
|
||||||
Setting_5_12_0 = Setting_5_11_0
|
Setting_5_12_0 = copy.deepcopy(Setting_5_11_0)
|
||||||
Setting_5_12_0['flag'][0].update ({
|
Setting_5_12_0['flag'][0].update ({
|
||||||
'hass_discovery': ('<L', (0x010, 1, 19), None),
|
'hass_discovery': ('<L', (0x010, 1, 19), None),
|
||||||
'not_power_linked': ('<L', (0x010, 1, 20), None),
|
'not_power_linked': ('<L', (0x010, 1, 20), None),
|
||||||
'no_power_on_check': ('<L', (0x010, 1, 21), None),
|
'no_power_on_check': ('<L', (0x010, 1, 21), None),
|
||||||
})
|
})
|
||||||
|
# ========
|
||||||
Setting_5_13_1 = Setting_5_12_0
|
Setting_5_13_1 = copy.deepcopy(Setting_5_12_0)
|
||||||
Setting_5_13_1.update({
|
Setting_5_13_1.update({
|
||||||
'baudrate': ('B', 0x09D, None, ('$ * 1200','$ / 1200') ),
|
'baudrate': ('B', 0x09D, None, ('$ * 1200','$ / 1200') ),
|
||||||
'mqtt_fingerprint': ('20s', 0x1AD, [2]),
|
'mqtt_fingerprint': ('20s', 0x1AD, [2]),
|
||||||
|
@ -505,8 +506,8 @@ Setting_5_13_1['flag'][0].update ({
|
||||||
'rules_once': ('<L', (0x010, 1, 24), None),
|
'rules_once': ('<L', (0x010, 1, 24), None),
|
||||||
'knx_enabled': ('<L', (0x010, 1, 25), None),
|
'knx_enabled': ('<L', (0x010, 1, 25), None),
|
||||||
})
|
})
|
||||||
|
# ========
|
||||||
Setting_5_14_0 = Setting_5_13_1
|
Setting_5_14_0 = copy.deepcopy(Setting_5_13_1)
|
||||||
Setting_5_14_0.update({
|
Setting_5_14_0.update({
|
||||||
'tflag': ({
|
'tflag': ({
|
||||||
'raw': ('<H', 0x2E2, None, ('"0x{:04x}".format($)', None)),
|
'raw': ('<H', 0x2E2, None, ('"0x{:04x}".format($)', None)),
|
||||||
|
@ -523,8 +524,8 @@ Setting_5_14_0['flag'][0].update ({
|
||||||
'device_index_enable': ('<L', (0x010, 1, 26), None),
|
'device_index_enable': ('<L', (0x010, 1, 26), None),
|
||||||
})
|
})
|
||||||
Setting_5_14_0['flag'][0].pop ('rules_once',None)
|
Setting_5_14_0['flag'][0].pop ('rules_once',None)
|
||||||
|
# ========
|
||||||
Setting_6_0_0 = Setting_5_14_0
|
Setting_6_0_0 = copy.deepcopy(Setting_5_14_0)
|
||||||
Setting_6_0_0.update({
|
Setting_6_0_0.update({
|
||||||
'cfg_holder': ('<H', 0x000, None),
|
'cfg_holder': ('<H', 0x000, None),
|
||||||
'cfg_size': ('<H', 0x002, None, (None, None)),
|
'cfg_size': ('<H', 0x002, None, (None, None)),
|
||||||
|
@ -548,8 +549,8 @@ Setting_6_0_0.update({
|
||||||
Setting_6_0_0['flag'][0].update ({
|
Setting_6_0_0['flag'][0].update ({
|
||||||
'knx_enable_enhancement': ('<L', (0x010, 1, 27), None),
|
'knx_enable_enhancement': ('<L', (0x010, 1, 27), None),
|
||||||
})
|
})
|
||||||
|
# ========
|
||||||
Setting_6_1_1 = Setting_6_0_0
|
Setting_6_1_1 = copy.deepcopy(Setting_6_0_0)
|
||||||
Setting_6_1_1.update({
|
Setting_6_1_1.update({
|
||||||
'flag3': ('<L', 0x3A0, None, '"0x{:08x}".format($)'),
|
'flag3': ('<L', 0x3A0, None, '"0x{:08x}".format($)'),
|
||||||
'switchmode': ('B', 0x3A4, [8]),
|
'switchmode': ('B', 0x3A4, [8]),
|
||||||
|
@ -568,8 +569,8 @@ Setting_6_1_1['flag'][0].update ({
|
||||||
'ir_receive_decimal': ('<L', (0x010, 1, 29), None),
|
'ir_receive_decimal': ('<L', (0x010, 1, 29), None),
|
||||||
'hass_light': ('<L', (0x010, 1, 30), None),
|
'hass_light': ('<L', (0x010, 1, 30), None),
|
||||||
})
|
})
|
||||||
|
# ========
|
||||||
Setting_6_2_1 = Setting_6_1_1
|
Setting_6_2_1 = copy.deepcopy(Setting_6_1_1)
|
||||||
Setting_6_2_1.update({
|
Setting_6_2_1.update({
|
||||||
'rule_stop': ({
|
'rule_stop': ({
|
||||||
'raw': ('B', 0x1A7, None, (None, None)),
|
'raw': ('B', 0x1A7, None, (None, None)),
|
||||||
|
@ -597,31 +598,31 @@ Setting_6_2_1['flag'][0].update ({
|
||||||
Setting_6_2_1['flag2'][0].update ({
|
Setting_6_2_1['flag2'][0].update ({
|
||||||
'axis_resolution': ('<L', (0x5BC, 2, 13), None),
|
'axis_resolution': ('<L', (0x5BC, 2, 13), None),
|
||||||
})
|
})
|
||||||
|
# ========
|
||||||
Setting_6_2_1_2 = Setting_6_2_1
|
Setting_6_2_1_2 = copy.deepcopy(Setting_6_2_1)
|
||||||
Setting_6_2_1_2['flag3'][0].update ({
|
Setting_6_2_1_2['flag3'][0].update ({
|
||||||
'user_esp8285_enable': ('<L', (0x3A0, 1, 1), None),
|
'user_esp8285_enable': ('<L', (0x3A0, 1, 1), None),
|
||||||
})
|
})
|
||||||
|
# ========
|
||||||
Setting_6_2_1_3 = Setting_6_2_1_2
|
Setting_6_2_1_3 = copy.deepcopy(Setting_6_2_1_2)
|
||||||
Setting_6_2_1_3['flag2'][0].update ({
|
Setting_6_2_1_3['flag2'][0].update ({
|
||||||
'frequency_resolution': ('<L', (0x5BC, 2, 11), None),
|
'frequency_resolution': ('<L', (0x5BC, 2, 11), None),
|
||||||
})
|
})
|
||||||
Setting_6_2_1_3['flag3'][0].update ({
|
Setting_6_2_1_3['flag3'][0].update ({
|
||||||
'time_append_timezone': ('<L', (0x3A0, 1, 2), None),
|
'time_append_timezone': ('<L', (0x3A0, 1, 2), None),
|
||||||
})
|
})
|
||||||
|
# ========
|
||||||
Setting_6_2_1_6 = Setting_6_2_1_3
|
Setting_6_2_1_6 = copy.deepcopy(Setting_6_2_1_3)
|
||||||
Setting_6_2_1_6.update({
|
Setting_6_2_1_6.update({
|
||||||
'energy_frequency_calibration': ('<L', 0x7C8, None),
|
'energy_frequency_calibration': ('<L', 0x7C8, None),
|
||||||
})
|
})
|
||||||
|
# ========
|
||||||
Setting_6_2_1_10 = Setting_6_2_1_6
|
Setting_6_2_1_10 = copy.deepcopy(Setting_6_2_1_6)
|
||||||
Setting_6_2_1_10.update({
|
Setting_6_2_1_10.update({
|
||||||
'rgbwwTable': ('B', 0x71A, [5]),
|
'rgbwwTable': ('B', 0x71A, [5]),
|
||||||
})
|
})
|
||||||
|
# ========
|
||||||
Setting_6_2_1_14 = Setting_6_2_1_10
|
Setting_6_2_1_14 = copy.deepcopy(Setting_6_2_1_10)
|
||||||
Setting_6_2_1_14.update({
|
Setting_6_2_1_14.update({
|
||||||
'weight_item': ('<H', 0x7BC, None),
|
'weight_item': ('<H', 0x7BC, None),
|
||||||
'weight_max': ('<H', 0x7BE, None, ('float($) / 10', 'int($ * 10)')),
|
'weight_max': ('<H', 0x7BE, None, ('float($) / 10', 'int($ * 10)')),
|
||||||
|
@ -632,23 +633,23 @@ Setting_6_2_1_14.update({
|
||||||
Setting_6_2_1_14['flag2'][0].update ({
|
Setting_6_2_1_14['flag2'][0].update ({
|
||||||
'weight_resolution': ('<L', (0x5BC, 2, 9), None),
|
'weight_resolution': ('<L', (0x5BC, 2, 9), None),
|
||||||
})
|
})
|
||||||
|
# ========
|
||||||
Setting_6_2_1_19 = Setting_6_2_1_14
|
Setting_6_2_1_19 = copy.deepcopy(Setting_6_2_1_14)
|
||||||
Setting_6_2_1_19.update({
|
Setting_6_2_1_19.update({
|
||||||
'weight_max': ('<L', 0x7B8, None, ('float($) / 10', 'int($ * 10)')),
|
'weight_max': ('<L', 0x7B8, None, ('float($) / 10', 'int($ * 10)')),
|
||||||
})
|
})
|
||||||
|
# ========
|
||||||
Setting_6_2_1_20 = Setting_6_2_1_19
|
Setting_6_2_1_20 = copy.deepcopy(Setting_6_2_1_19)
|
||||||
Setting_6_2_1_20['flag3'][0].update ({
|
Setting_6_2_1_20['flag3'][0].update ({
|
||||||
'gui_hostname_ip': ('<L', (0x3A0, 1, 3), None),
|
'gui_hostname_ip': ('<L', (0x3A0, 1, 3), None),
|
||||||
})
|
})
|
||||||
|
# ========
|
||||||
Setting_6_3_0 = Setting_6_2_1_20
|
Setting_6_3_0 = copy.deepcopy(Setting_6_2_1_20)
|
||||||
Setting_6_3_0.update({
|
Setting_6_3_0.update({
|
||||||
'energy_kWhtotal_time': ('<L', 0x7B4, None),
|
'energy_kWhtotal_time': ('<L', 0x7B4, None),
|
||||||
})
|
})
|
||||||
|
# ========
|
||||||
Setting_6_3_0_2 = Setting_6_3_0
|
Setting_6_3_0_2 = copy.deepcopy(Setting_6_3_0)
|
||||||
Setting_6_3_0_2.update({
|
Setting_6_3_0_2.update({
|
||||||
'timezone_minutes': ('B', 0x66D, None),
|
'timezone_minutes': ('B', 0x66D, None),
|
||||||
})
|
})
|
||||||
|
@ -656,8 +657,22 @@ Setting_6_3_0_2['flag'][0].pop('rules_once',None)
|
||||||
Setting_6_3_0_2['flag'][0].update ({
|
Setting_6_3_0_2['flag'][0].update ({
|
||||||
'pressure_conversion': ('<L', (0x010, 1, 24), None),
|
'pressure_conversion': ('<L', (0x010, 1, 24), None),
|
||||||
})
|
})
|
||||||
|
# ========
|
||||||
|
Setting_6_3_0_4 = copy.deepcopy(Setting_6_3_0_2)
|
||||||
|
Setting_6_3_0_4.update({
|
||||||
|
'drivers': ('<L', 0x794, [3]),
|
||||||
|
'monitors': ('<L', 0x7A0, None),
|
||||||
|
'sensors': ('<L', 0x7A4, [3]),
|
||||||
|
'displays': ('<L', 0x7B0, None),
|
||||||
|
})
|
||||||
|
Setting_6_3_0_4['flag3'][0].update ({
|
||||||
|
'tuya_apply_o20': ('<L', (0x3A0, 1, 4), None),
|
||||||
|
})
|
||||||
|
|
||||||
|
# ========
|
||||||
|
|
||||||
Settings = [
|
Settings = [
|
||||||
|
(0x6030004, 0xe00, Setting_6_3_0_4),
|
||||||
(0x6030002, 0xe00, Setting_6_3_0_2),
|
(0x6030002, 0xe00, Setting_6_3_0_2),
|
||||||
(0x6030000, 0xe00, Setting_6_3_0),
|
(0x6030000, 0xe00, Setting_6_3_0),
|
||||||
(0x6020114, 0xe00, Setting_6_2_1_20),
|
(0x6020114, 0xe00, Setting_6_2_1_20),
|
||||||
|
@ -699,32 +714,27 @@ def GetTemplateSizes():
|
||||||
|
|
||||||
def GetTemplateSetting(decode_cfg):
|
def GetTemplateSetting(decode_cfg):
|
||||||
"""
|
"""
|
||||||
Search for version, template, size and settings to be used depending on given binary config data
|
Search for version, size and settings to be used depending on given binary config data
|
||||||
|
|
||||||
@param decode_cfg:
|
@param decode_cfg:
|
||||||
binary config data (decrypted)
|
binary config data (decrypted)
|
||||||
|
|
||||||
@return:
|
@return:
|
||||||
version, template, size, settings to use; None if version is invalid
|
version, size, settings to use; None if version is invalid
|
||||||
"""
|
"""
|
||||||
version = 0x0
|
version = 0x0
|
||||||
template = size = setting = None
|
size = setting = None
|
||||||
try:
|
|
||||||
version = GetField(decode_cfg, 'version', Setting_6_2_1['version'], raw=True)
|
|
||||||
# search setting definition
|
|
||||||
template = None
|
|
||||||
setting = None
|
|
||||||
size = None
|
|
||||||
for cfg in Settings:
|
|
||||||
if version >= cfg[0]:
|
|
||||||
template = cfg
|
|
||||||
size = template[1]
|
|
||||||
setting = template[2]
|
|
||||||
break
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return version, template, size, setting
|
version = GetField(decode_cfg, 'version', Setting_6_2_1['version'], raw=True)
|
||||||
|
# search setting definition top-down
|
||||||
|
for cfg in sorted(Settings, key=lambda s: s[0], reverse=True):
|
||||||
|
if version >= cfg[0]:
|
||||||
|
version = cfg[0]
|
||||||
|
size = cfg[1]
|
||||||
|
setting = cfg[2].copy()
|
||||||
|
break
|
||||||
|
|
||||||
|
return version, size, setting
|
||||||
|
|
||||||
|
|
||||||
class LogType:
|
class LogType:
|
||||||
|
@ -944,6 +954,8 @@ def GetVersionStr(version):
|
||||||
@return:
|
@return:
|
||||||
version string
|
version string
|
||||||
"""
|
"""
|
||||||
|
if isinstance(version, (unicode,str)):
|
||||||
|
version = int(version, 0)
|
||||||
major = ((version>>24) & 0xff)
|
major = ((version>>24) & 0xff)
|
||||||
minor = ((version>>16) & 0xff)
|
minor = ((version>>16) & 0xff)
|
||||||
release = ((version>> 8) & 0xff)
|
release = ((version>> 8) & 0xff)
|
||||||
|
@ -1007,16 +1019,29 @@ def MakeFilename(filename, filetype, decode_cfg):
|
||||||
if 'hostname' in decode_cfg:
|
if 'hostname' in decode_cfg:
|
||||||
filename = filename.replace('@h', decode_cfg['hostname'] )
|
filename = filename.replace('@h', decode_cfg['hostname'] )
|
||||||
|
|
||||||
filename = MakeValidFilename(filename)
|
dirname = basename = ext = ''
|
||||||
ext = ''
|
|
||||||
try:
|
try:
|
||||||
name, ext = os.path.splitext(filename)
|
dirname = os.path.normpath(os.path.dirname(filename))
|
||||||
|
basename = os.path.basename(filename)
|
||||||
|
name, ext = os.path.splitext(basename)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
name = MakeValidFilename(name)
|
||||||
|
|
||||||
if len(ext) and ext[0]=='.':
|
if len(ext) and ext[0]=='.':
|
||||||
ext = ext[1:]
|
ext = ext[1:]
|
||||||
if filetype is not None and args.extension and (len(ext)<2 or all(c.isdigit() for c in ext)):
|
if filetype is not None and args.extension and (len(ext)<2 or all(c.isdigit() for c in ext)):
|
||||||
filename += '.'+filetype.lower()
|
ext = filetype.lower()
|
||||||
|
|
||||||
|
if len(ext):
|
||||||
|
name_ext = name+'.'+ext
|
||||||
|
else:
|
||||||
|
name_ext = name
|
||||||
|
|
||||||
|
try:
|
||||||
|
filename = os.path.join(dirname, name_ext)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
return filename
|
return filename
|
||||||
|
|
||||||
|
@ -1042,59 +1067,84 @@ def MakeUrl(host, port=80, location=''):
|
||||||
slocation=location )
|
slocation=location )
|
||||||
|
|
||||||
|
|
||||||
def PullTasmotaConfig():
|
def LoadTasmotaConfig(filename):
|
||||||
"""
|
"""
|
||||||
Pull config from Tasmota device/file
|
Load config from Tasmota file
|
||||||
|
|
||||||
|
@param filename:
|
||||||
|
filename to load
|
||||||
|
|
||||||
@return:
|
@return:
|
||||||
binary config data (encrypted) or None on error
|
binary config data (encrypted) or None on error
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if args.device is not None:
|
encode_cfg = None
|
||||||
# read config direct from device via http
|
|
||||||
|
|
||||||
c = pycurl.Curl()
|
|
||||||
buffer = io.BytesIO()
|
|
||||||
c.setopt(c.WRITEDATA, buffer)
|
|
||||||
header = HTTPHeader()
|
|
||||||
c.setopt(c.HEADERFUNCTION, header.store)
|
|
||||||
c.setopt(c.FOLLOWLOCATION, True)
|
|
||||||
c.setopt(c.URL, MakeUrl(args.device, args.port, 'dl'))
|
|
||||||
if args.username is not None and args.password is not None:
|
|
||||||
c.setopt(c.HTTPAUTH, c.HTTPAUTH_BASIC)
|
|
||||||
c.setopt(c.USERPWD, args.username + ':' + args.password)
|
|
||||||
c.setopt(c.VERBOSE, False)
|
|
||||||
|
|
||||||
responsecode = 200
|
# read config from a file
|
||||||
try:
|
if not os.path.isfile(filename): # check file exists
|
||||||
c.perform()
|
exit(ExitCode.FILE_NOT_FOUND, "File '{}' not found".format(filename),line=inspect.getlineno(inspect.currentframe()))
|
||||||
responsecode = c.getinfo(c.RESPONSE_CODE)
|
try:
|
||||||
response = header.response()
|
tasmotafile = open(filename, "rb")
|
||||||
except Exception, e:
|
encode_cfg = tasmotafile.read()
|
||||||
exit(e[0], e[1],line=inspect.getlineno(inspect.currentframe()))
|
tasmotafile.close()
|
||||||
finally:
|
except Exception, e:
|
||||||
c.close()
|
exit(e[0], "'{}' {}".format(filename, e[1]),line=inspect.getlineno(inspect.currentframe()))
|
||||||
|
|
||||||
if responsecode>=400:
|
return encode_cfg
|
||||||
exit(responsecode, 'HTTP result: {}'.format(header.response()),line=inspect.getlineno(inspect.currentframe()))
|
|
||||||
elif header.contenttype()!='application/octet-stream':
|
|
||||||
exit(ExitCode.DOWNLOAD_CONFIG_ERROR, "Device did not response properly, may be Tasmota webserver admin mode is disabled (WebServer 2)",line=inspect.getlineno(inspect.currentframe()))
|
def PullTasmotaConfig(host, port, username=DEFAULTS['source']['username'], password=None):
|
||||||
|
"""
|
||||||
|
Pull config from Tasmota device
|
||||||
|
|
||||||
|
@param host:
|
||||||
|
hostname or IP of Tasmota device
|
||||||
|
@param port:
|
||||||
|
http port of Tasmota device
|
||||||
|
@param username:
|
||||||
|
optional username for Tasmota web login
|
||||||
|
@param password
|
||||||
|
optional password for Tasmota web login
|
||||||
|
|
||||||
|
@return:
|
||||||
|
binary config data (encrypted) or None on error
|
||||||
|
"""
|
||||||
|
|
||||||
|
encode_cfg = None
|
||||||
|
|
||||||
|
# read config direct from device via http
|
||||||
|
c = pycurl.Curl()
|
||||||
|
buffer = io.BytesIO()
|
||||||
|
c.setopt(c.WRITEDATA, buffer)
|
||||||
|
header = HTTPHeader()
|
||||||
|
c.setopt(c.HEADERFUNCTION, header.store)
|
||||||
|
c.setopt(c.FOLLOWLOCATION, True)
|
||||||
|
c.setopt(c.URL, MakeUrl(host, port, 'dl'))
|
||||||
|
if username is not None and password is not None:
|
||||||
|
c.setopt(c.HTTPAUTH, c.HTTPAUTH_BASIC)
|
||||||
|
c.setopt(c.USERPWD, username + ':' + password)
|
||||||
|
c.setopt(c.VERBOSE, False)
|
||||||
|
|
||||||
|
responsecode = 200
|
||||||
|
try:
|
||||||
|
c.perform()
|
||||||
|
responsecode = c.getinfo(c.RESPONSE_CODE)
|
||||||
|
response = header.response()
|
||||||
|
except Exception, e:
|
||||||
|
exit(e[0], e[1],line=inspect.getlineno(inspect.currentframe()))
|
||||||
|
finally:
|
||||||
|
c.close()
|
||||||
|
|
||||||
|
if responsecode >= 400:
|
||||||
|
exit(responsecode, 'HTTP result: {}'.format(header.response()),line=inspect.getlineno(inspect.currentframe()))
|
||||||
|
elif header.contenttype()!='application/octet-stream':
|
||||||
|
exit(ExitCode.DOWNLOAD_CONFIG_ERROR, "Device did not response properly, may be Tasmota webserver admin mode is disabled (WebServer 2)",line=inspect.getlineno(inspect.currentframe()))
|
||||||
|
|
||||||
|
try:
|
||||||
encode_cfg = buffer.getvalue()
|
encode_cfg = buffer.getvalue()
|
||||||
|
except:
|
||||||
elif args.tasmotafile is not None:
|
pass
|
||||||
# read config from a file
|
|
||||||
if not os.path.isfile(args.tasmotafile): # check file exists
|
|
||||||
exit(ExitCode.FILE_NOT_FOUND, "File '{}' not found".format(args.tasmotafile),line=inspect.getlineno(inspect.currentframe()))
|
|
||||||
try:
|
|
||||||
tasmotafile = open(args.tasmotafile, "rb")
|
|
||||||
encode_cfg = tasmotafile.read()
|
|
||||||
tasmotafile.close()
|
|
||||||
except Exception, e:
|
|
||||||
exit(e[0], "'{}' {}".format(args.tasmotafile, e[1]),line=inspect.getlineno(inspect.currentframe()))
|
|
||||||
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return encode_cfg
|
return encode_cfg
|
||||||
|
|
||||||
|
|
||||||
|
@ -1238,14 +1288,14 @@ def GetFieldDef(fielddef):
|
||||||
<format>, <baseaddr>, <bits>, <bitshift>, <datadef>, <convert>
|
<format>, <baseaddr>, <bits>, <bitshift>, <datadef>, <convert>
|
||||||
undefined items can be None
|
undefined items can be None
|
||||||
"""
|
"""
|
||||||
_format = baseaddr = datadef = convert = None
|
format = baseaddr = datadef = convert = None
|
||||||
bits = bitshift = 0
|
bits = bitshift = 0
|
||||||
if len(fielddef)==3:
|
if len(fielddef)==3:
|
||||||
# def without convert tuple
|
# def without convert tuple
|
||||||
_format, baseaddr, datadef = fielddef
|
format, baseaddr, datadef = fielddef
|
||||||
elif len(fielddef)==4:
|
elif len(fielddef)==4:
|
||||||
# def with convert tuple
|
# def with convert tuple
|
||||||
_format, baseaddr, datadef, convert = fielddef
|
format, baseaddr, datadef, convert = fielddef
|
||||||
|
|
||||||
if isinstance(baseaddr, (list,tuple)):
|
if isinstance(baseaddr, (list,tuple)):
|
||||||
baseaddr, bits, bitshift = baseaddr
|
baseaddr, bits, bitshift = baseaddr
|
||||||
|
@ -1253,7 +1303,7 @@ def GetFieldDef(fielddef):
|
||||||
if isinstance(datadef, int):
|
if isinstance(datadef, int):
|
||||||
# convert single int into list with one item
|
# convert single int into list with one item
|
||||||
datadef = [datadef]
|
datadef = [datadef]
|
||||||
return _format, baseaddr, bits, bitshift, datadef, convert
|
return format, baseaddr, bits, bitshift, datadef, convert
|
||||||
|
|
||||||
|
|
||||||
def MakeFieldBaseAddr(baseaddr, bits, bitshift):
|
def MakeFieldBaseAddr(baseaddr, bits, bitshift):
|
||||||
|
@ -1293,7 +1343,7 @@ def ConvertFieldValue(value, fielddef, read=True, raw=False):
|
||||||
@return:
|
@return:
|
||||||
(un)converted value
|
(un)converted value
|
||||||
"""
|
"""
|
||||||
_format, baseaddr, bits, bitshift, datadef, convert = GetFieldDef(fielddef)
|
format, baseaddr, bits, bitshift, datadef, convert = GetFieldDef(fielddef)
|
||||||
|
|
||||||
# call password functions even if raw value should be processed
|
# call password functions even if raw value should be processed
|
||||||
if callable(convert) and (convert==passwordread or convert==passwordwrite):
|
if callable(convert) and (convert==passwordread or convert==passwordwrite):
|
||||||
|
@ -1345,15 +1395,15 @@ def GetFieldMinMax(fielddef):
|
||||||
'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, baseaddr, bits, bitshift, datadef, convert = GetFieldDef(fielddef)
|
format, baseaddr, bits, bitshift, datadef, convert = GetFieldDef(fielddef)
|
||||||
_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']:
|
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)
|
match = re.search("\s*(\d+)", format)
|
||||||
if match:
|
if match:
|
||||||
_max=int(match.group(0))
|
_max=int(match.group(0))
|
||||||
return _min,_max
|
return _min,_max
|
||||||
|
@ -1371,7 +1421,7 @@ def GetFieldLength(fielddef):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
length=0
|
length=0
|
||||||
_format, baseaddr, bits, bitshift, datadef, convert = GetFieldDef(fielddef)
|
format, baseaddr, bits, bitshift, datadef, convert = GetFieldDef(fielddef)
|
||||||
|
|
||||||
if datadef is not None:
|
if datadef is not None:
|
||||||
# datadef contains a list
|
# datadef contains a list
|
||||||
|
@ -1388,10 +1438,10 @@ def GetFieldLength(fielddef):
|
||||||
length += GetFieldLength( (fielddef[0], fielddef[1], None) )
|
length += GetFieldLength( (fielddef[0], fielddef[1], None) )
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if isinstance(_format, dict):
|
if 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:
|
||||||
_dummy1, baseaddr, bits, bitshift, _dummy2, _dummy3 = GetFieldDef(setting[name])
|
_dummy1, baseaddr, bits, bitshift, _dummy2, _dummy3 = GetFieldDef(setting[name])
|
||||||
_len = GetFieldLength(setting[name])
|
_len = GetFieldLength(setting[name])
|
||||||
|
@ -1400,17 +1450,17 @@ def GetFieldLength(fielddef):
|
||||||
length += _len
|
length += _len
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if _format[-1:] in ['b','B','c','?']:
|
if format[-1:] in ['b','B','c','?']:
|
||||||
length=1
|
length=1
|
||||||
elif _format[-1:] in ['h','H']:
|
elif format[-1:] in ['h','H']:
|
||||||
length=2
|
length=2
|
||||||
elif _format[-1:] in ['i','I','l','L','f']:
|
elif format[-1:] in ['i','I','l','L','f']:
|
||||||
length=4
|
length=4
|
||||||
elif _format[-1:] in ['q','Q','d']:
|
elif format[-1:] in ['q','Q','d']:
|
||||||
length=8
|
length=8
|
||||||
elif _format[-1:] in ['s','p']:
|
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)
|
match = re.search("\s*(\d+)", format)
|
||||||
if match:
|
if match:
|
||||||
length=int(match.group(0))
|
length=int(match.group(0))
|
||||||
|
|
||||||
|
@ -1429,18 +1479,18 @@ def GetSubfieldDef(fielddef):
|
||||||
"""
|
"""
|
||||||
subfielddef = None
|
subfielddef = None
|
||||||
|
|
||||||
_format, baseaddr, bits, bitshift, datadef, convert = GetFieldDef(fielddef)
|
format, baseaddr, bits, bitshift, datadef, convert = GetFieldDef(fielddef)
|
||||||
if isinstance(datadef, list) and len(datadef)>1:
|
if isinstance(datadef, list) and len(datadef)>1:
|
||||||
if len(fielddef)<4:
|
if len(fielddef)<4:
|
||||||
subfielddef = (_format, MakeFieldBaseAddr(baseaddr, bits, bitshift), datadef[1:])
|
subfielddef = (format, MakeFieldBaseAddr(baseaddr, bits, bitshift), datadef[1:])
|
||||||
else:
|
else:
|
||||||
subfielddef = (_format, MakeFieldBaseAddr(baseaddr, bits, bitshift), datadef[1:], convert)
|
subfielddef = (format, MakeFieldBaseAddr(baseaddr, bits, bitshift), datadef[1:], convert)
|
||||||
# single array
|
# single array
|
||||||
else:
|
else:
|
||||||
if len(fielddef)<4:
|
if len(fielddef)<4:
|
||||||
subfielddef = (_format, MakeFieldBaseAddr(baseaddr, bits, bitshift), None)
|
subfielddef = (format, MakeFieldBaseAddr(baseaddr, bits, bitshift), None)
|
||||||
else:
|
else:
|
||||||
subfielddef = (_format, MakeFieldBaseAddr(baseaddr, bits, bitshift), None, convert)
|
subfielddef = (format, MakeFieldBaseAddr(baseaddr, bits, bitshift), None, convert)
|
||||||
return subfielddef
|
return subfielddef
|
||||||
|
|
||||||
|
|
||||||
|
@ -1472,7 +1522,7 @@ def GetField(dobj, fieldname, fielddef, raw=False, addroffset=0):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# get field definition
|
# get field definition
|
||||||
_format, baseaddr, bits, bitshift, datadef, convert = GetFieldDef(fielddef)
|
format, baseaddr, bits, bitshift, datadef, convert = GetFieldDef(fielddef)
|
||||||
|
|
||||||
# <datadef> contains a integer list
|
# <datadef> contains a integer list
|
||||||
if isinstance(datadef, list):
|
if isinstance(datadef, list):
|
||||||
|
@ -1486,19 +1536,19 @@ 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):
|
||||||
config = {}
|
config = {}
|
||||||
for name in _format: # -> iterate through _format
|
for name in format: # -> iterate through format
|
||||||
if name != 'raw' or args.jsonrawkeys:
|
if name != 'raw' or args.jsonrawkeys:
|
||||||
config[name] = GetField(dobj, name, _format[name], raw=raw, addroffset=addroffset)
|
config[name] = GetField(dobj, name, format[name], raw=raw, addroffset=addroffset)
|
||||||
result = config
|
result = config
|
||||||
|
|
||||||
# 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:
|
||||||
result = struct.unpack_from(_format, dobj, baseaddr+addroffset)[0]
|
result = struct.unpack_from(format, dobj, baseaddr+addroffset)[0]
|
||||||
|
|
||||||
if not _format[-1:].lower() in ['s','p']:
|
if not format[-1:].lower() in ['s','p']:
|
||||||
if bitshift>=0:
|
if bitshift>=0:
|
||||||
result >>= bitshift
|
result >>= bitshift
|
||||||
else:
|
else:
|
||||||
|
@ -1507,7 +1557,7 @@ def GetField(dobj, fieldname, fielddef, raw=False, addroffset=0):
|
||||||
result &= (1<<bits)-1
|
result &= (1<<bits)-1
|
||||||
|
|
||||||
# additional processing for strings
|
# additional processing for strings
|
||||||
if _format[-1:].lower() in ['s','p']:
|
if format[-1:].lower() in ['s','p']:
|
||||||
# use left string until \0
|
# use left string until \0
|
||||||
s = str(result).split('\0')[0]
|
s = str(result).split('\0')[0]
|
||||||
# remove character > 127
|
# remove character > 127
|
||||||
|
@ -1516,7 +1566,7 @@ def GetField(dobj, fieldname, fielddef, raw=False, addroffset=0):
|
||||||
result = ConvertFieldValue(result, fielddef, read=True, raw=raw)
|
result = ConvertFieldValue(result, 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 result
|
return result
|
||||||
|
|
||||||
|
@ -1538,7 +1588,7 @@ def SetField(dobj, fieldname, fielddef, restore, raw=False, addroffset=0, filena
|
||||||
@param restore
|
@param restore
|
||||||
restore mapping with the new value(s)
|
restore mapping with the new value(s)
|
||||||
"""
|
"""
|
||||||
_format, baseaddr, bits, bitshift, datadef, convert = GetFieldDef(fielddef)
|
format, baseaddr, bits, bitshift, datadef, convert = GetFieldDef(fielddef)
|
||||||
fieldname = str(fieldname)
|
fieldname = str(fieldname)
|
||||||
|
|
||||||
if fieldname == 'raw' and not args.jsonrawkeys:
|
if fieldname == 'raw' and not args.jsonrawkeys:
|
||||||
|
@ -1547,7 +1597,7 @@ def SetField(dobj, fieldname, fielddef, restore, raw=False, addroffset=0, filena
|
||||||
# do not write readonly values
|
# do not write readonly values
|
||||||
if isinstance(convert, (list,tuple)) and len(convert)>1 and convert[1]==None:
|
if isinstance(convert, (list,tuple)) and len(convert)>1 and convert[1]==None:
|
||||||
if args.debug:
|
if args.debug:
|
||||||
print >> sys.stderr, "SetField(): Readonly '{}' using '{}'/{}{} @{} skipped".format(fieldname, _format, datadef, bits, hex(baseaddr+addroffset))
|
print >> sys.stderr, "SetField(): Readonly '{}' using '{}'/{}{} @{} skipped".format(fieldname, format, datadef, bits, hex(baseaddr+addroffset))
|
||||||
return dobj
|
return dobj
|
||||||
|
|
||||||
# <datadef> contains a list
|
# <datadef> contains a list
|
||||||
|
@ -1569,41 +1619,46 @@ def SetField(dobj, fieldname, fielddef, restore, raw=False, addroffset=0, filena
|
||||||
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], raw=raw, addroffset=addroffset, filename=filename)
|
dobj = SetField(dobj, name, format[name], restore[name], raw=raw, 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 = "outside range"
|
err = ""
|
||||||
|
errformat = ""
|
||||||
|
|
||||||
_min, _max = GetFieldMinMax(fielddef)
|
_min, _max = GetFieldMinMax(fielddef)
|
||||||
value = _value = valid = None
|
value = _value = None
|
||||||
|
skip = False
|
||||||
|
|
||||||
# simple one value
|
# simple one value
|
||||||
if _format[-1:] in ['c']:
|
if format[-1:] in ['c']:
|
||||||
try:
|
try:
|
||||||
value = ConvertFieldValue(restore.encode(STR_ENCODING)[0], fielddef, read=False, raw=raw)
|
value = ConvertFieldValue(restore.encode(STR_ENCODING)[0], fielddef, read=False, raw=raw)
|
||||||
except:
|
except:
|
||||||
|
err = "valid range exceeding"
|
||||||
valid = False
|
valid = False
|
||||||
# bool
|
# bool
|
||||||
elif _format[-1:] in ['?']:
|
elif format[-1:] in ['?']:
|
||||||
try:
|
try:
|
||||||
value = ConvertFieldValue(bool(restore), fielddef, read=False, raw=raw)
|
value = ConvertFieldValue(bool(restore), fielddef, read=False, raw=raw)
|
||||||
except:
|
except:
|
||||||
|
err = "valid range exceeding"
|
||||||
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']:
|
||||||
try:
|
try:
|
||||||
value = ConvertFieldValue(restore, fielddef, read=False, raw=raw)
|
value = ConvertFieldValue(restore, fielddef, read=False, raw=raw)
|
||||||
if isinstance(value, (str, unicode)):
|
if isinstance(value, (str, unicode)):
|
||||||
value = int(value, 0)
|
value = int(value, 0)
|
||||||
else:
|
else:
|
||||||
value = int(value)
|
value = int(value)
|
||||||
# bits
|
# bit value
|
||||||
if bits!=0:
|
if bits!=0:
|
||||||
value = struct.unpack_from(_format, dobj, baseaddr+addroffset)[0]
|
value = struct.unpack_from(format, dobj, baseaddr+addroffset)[0]
|
||||||
bitvalue = int(restore)
|
bitvalue = int(restore)
|
||||||
mask = (1<<bits)-1
|
mask = (1<<bits)-1
|
||||||
if bitvalue>mask:
|
if bitvalue>mask:
|
||||||
|
@ -1611,6 +1666,7 @@ def SetField(dobj, fieldname, fielddef, restore, raw=False, addroffset=0, filena
|
||||||
_max = mask
|
_max = mask
|
||||||
_value = bitvalue
|
_value = bitvalue
|
||||||
valid = False
|
valid = False
|
||||||
|
err = "valid bit range exceeding"
|
||||||
else:
|
else:
|
||||||
if bitshift>=0:
|
if bitshift>=0:
|
||||||
bitvalue <<= bitshift
|
bitvalue <<= bitshift
|
||||||
|
@ -1620,46 +1676,69 @@ def SetField(dobj, fieldname, fielddef, restore, raw=False, addroffset=0, filena
|
||||||
mask >>= abs(bitshift)
|
mask >>= abs(bitshift)
|
||||||
value &= (0xffffffff ^ mask)
|
value &= (0xffffffff ^ mask)
|
||||||
value |= bitvalue
|
value |= bitvalue
|
||||||
|
# full size values
|
||||||
else:
|
else:
|
||||||
_value = value
|
_value = value
|
||||||
except:
|
except:
|
||||||
valid = False
|
valid = False
|
||||||
|
err = "valid range exceeding"
|
||||||
# float
|
# float
|
||||||
elif _format[-1:] in ['f','d']:
|
elif format[-1:] in ['f','d']:
|
||||||
try:
|
try:
|
||||||
value = ConvertFieldValue(float(restore), fielddef, read=False, raw=raw)
|
value = ConvertFieldValue(float(restore), fielddef, read=False, raw=raw)
|
||||||
except:
|
except:
|
||||||
|
err = "valid range exceeding"
|
||||||
valid = False
|
valid = False
|
||||||
# string
|
# string
|
||||||
elif _format[-1:] in ['s','p']:
|
elif format[-1:] in ['s','p']:
|
||||||
try:
|
try:
|
||||||
value = ConvertFieldValue(restore.encode(STR_ENCODING), fielddef, read=False, raw=raw)
|
value = ConvertFieldValue(restore.encode(STR_ENCODING), fielddef, read=False, raw=raw)
|
||||||
# be aware 0 byte at end of string (str must be < max, not <= max)
|
err = "string length exceeding"
|
||||||
_max -= 1
|
if value is not None:
|
||||||
valid = (len(value)>=_min) and (len(value)<=_max)
|
# be aware 0 byte at end of string (str must be < max, not <= max)
|
||||||
err = "string exceeds max length"
|
_max -= 1
|
||||||
|
valid = _min <= len(value) < _max
|
||||||
|
else:
|
||||||
|
skip = True
|
||||||
|
valid = True
|
||||||
except:
|
except:
|
||||||
valid = False
|
valid = False
|
||||||
|
|
||||||
if value is None:
|
if value is None and not skip:
|
||||||
|
# None is an invalid value
|
||||||
valid = False
|
valid = False
|
||||||
if valid is None:
|
|
||||||
valid = (value>=_min) and (value<=_max)
|
if valid is None and not skip:
|
||||||
|
# validate against object type size
|
||||||
|
valid = _min <= value <= _max
|
||||||
|
if not valid:
|
||||||
|
err = "type range exceeding"
|
||||||
|
errformat = " [{smin},{smax}]"
|
||||||
|
|
||||||
if _value is None:
|
if _value is None:
|
||||||
|
# copy value before possible change below
|
||||||
_value = value
|
_value = value
|
||||||
|
|
||||||
if isinstance(value, (str, unicode)):
|
if isinstance(value, (str, unicode)):
|
||||||
_value = "'{}'".format(_value)
|
_value = "'{}'".format(_value)
|
||||||
|
|
||||||
if valid:
|
if valid:
|
||||||
if args.debug:
|
if not skip:
|
||||||
if bits:
|
if args.debug:
|
||||||
sbits=" {} bits shift {}".format(bits, bitshift)
|
if bits:
|
||||||
else:
|
sbits=" {} bits shift {}".format(bits, bitshift)
|
||||||
sbits = ""
|
else:
|
||||||
print >> sys.stderr, "SetField(): Set '{}' using '{}'/{}{} @{} to {}".format(fieldname, _format, datadef, sbits, hex(baseaddr+addroffset), _value)
|
sbits = ""
|
||||||
struct.pack_into(_format, dobj, baseaddr+addroffset, value)
|
print >> sys.stderr, "SetField(): Set '{}' using '{}'/{}{} @{} to {}".format(fieldname, format, datadef, 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 prevvalue != curvalue and args.verbose:
|
||||||
|
message("Value for '{}' changed from {} to {}".format(fieldname, prevvalue, curvalue), typ=LogType.INFO)
|
||||||
else:
|
else:
|
||||||
exit(ExitCode.RESTORE_DATA_ERROR, "file '{sfile}', value for name '{sname}': {svalue} {serror} [{smin},{smax}]".format(sfile=filename, sname=fieldname, serror=err, svalue=_value, smin=_min, smax=_max), typ=LogType.WARNING, doexit=not args.ignorewarning)
|
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)
|
||||||
|
|
||||||
return dobj
|
return dobj
|
||||||
|
|
||||||
|
@ -1679,23 +1758,26 @@ def Bin2Mapping(decode_cfg, raw=True):
|
||||||
if isinstance(decode_cfg, bytearray):
|
if isinstance(decode_cfg, bytearray):
|
||||||
decode_cfg = str(decode_cfg)
|
decode_cfg = str(decode_cfg)
|
||||||
|
|
||||||
# get binary header and template to use
|
# get binary header to use
|
||||||
version, template, size, setting = GetTemplateSetting(decode_cfg)
|
version, size, setting = GetTemplateSetting(decode_cfg)
|
||||||
|
|
||||||
# if we did not found a mathching setting
|
# if we did not found a mathching setting
|
||||||
if template is None:
|
if setting is None:
|
||||||
exit(ExitCode.UNSUPPORTED_VERSION, "Tasmota configuration version 0x{:x} not supported".format(version),line=inspect.getlineno(inspect.currentframe()))
|
exit(ExitCode.UNSUPPORTED_VERSION, "Tasmota configuration version 0x{:x} not supported".format(version),line=inspect.getlineno(inspect.currentframe()))
|
||||||
|
|
||||||
|
if 'version' in setting:
|
||||||
|
cfg_version = GetField(decode_cfg, 'version', setting['version'], raw=True)
|
||||||
|
|
||||||
# check size if exists
|
# check size if exists
|
||||||
if 'cfg_size' in setting:
|
if 'cfg_size' in setting:
|
||||||
cfg_size = GetField(decode_cfg, 'cfg_size', setting['cfg_size'], raw=True)
|
cfg_size = GetField(decode_cfg, 'cfg_size', setting['cfg_size'], raw=True)
|
||||||
# read size should be same as definied in template
|
# read size should be same as definied in template
|
||||||
if cfg_size > size:
|
if cfg_size > size:
|
||||||
# may be processed
|
# may be processed
|
||||||
exit(ExitCode.DATA_SIZE_MISMATCH, "Number of bytes read does ot match - read {}, expected {} byte".format(cfg_size, template[1]), typ=LogType.ERROR,line=inspect.getlineno(inspect.currentframe()))
|
exit(ExitCode.DATA_SIZE_MISMATCH, "Number of bytes read does ot match - read {}, expected {} byte".format(cfg_size, size), typ=LogType.ERROR,line=inspect.getlineno(inspect.currentframe()))
|
||||||
elif cfg_size < size:
|
elif cfg_size < size:
|
||||||
# less number of bytes can not be processed
|
# less number of bytes can not be processed
|
||||||
exit(ExitCode.DATA_SIZE_MISMATCH, "Number of bytes read to small to process - read {}, expected {} byte".format(cfg_size, template[1]), typ=LogType.ERROR,line=inspect.getlineno(inspect.currentframe()))
|
exit(ExitCode.DATA_SIZE_MISMATCH, "Number of bytes read to small to process - read {}, expected {} byte".format(cfg_size, size), typ=LogType.ERROR,line=inspect.getlineno(inspect.currentframe()))
|
||||||
|
|
||||||
# check crc if exists
|
# check crc if exists
|
||||||
if 'cfg_crc' in setting:
|
if 'cfg_crc' in setting:
|
||||||
|
@ -1719,15 +1801,13 @@ def Bin2Mapping(decode_cfg, raw=True):
|
||||||
'jsonrawkeys': args.jsonrawkeys,
|
'jsonrawkeys': args.jsonrawkeys,
|
||||||
'jsonhidepw': args.jsonhidepw,
|
'jsonhidepw': args.jsonhidepw,
|
||||||
},
|
},
|
||||||
'src': {
|
'template': {
|
||||||
'crc': hex(cfg_crc),
|
|
||||||
'size': cfg_size,
|
|
||||||
'version': hex(version),
|
'version': hex(version),
|
||||||
|
'crc': hex(cfg_crc),
|
||||||
},
|
},
|
||||||
'data': {
|
'data': {
|
||||||
'crc': hex(GetSettingsCrc(decode_cfg)),
|
'crc': hex(GetSettingsCrc(decode_cfg)),
|
||||||
'size': len(decode_cfg),
|
'size': len(decode_cfg),
|
||||||
'version': hex(template[0]),
|
|
||||||
},
|
},
|
||||||
'script': {
|
'script': {
|
||||||
'name': os.path.basename(__file__),
|
'name': os.path.basename(__file__),
|
||||||
|
@ -1736,6 +1816,10 @@ def Bin2Mapping(decode_cfg, raw=True):
|
||||||
'os': (platform.machine(), platform.system(), platform.release(), platform.version(), platform.platform()),
|
'os': (platform.machine(), platform.system(), platform.release(), platform.version(), platform.platform()),
|
||||||
'python': platform.python_version(),
|
'python': platform.python_version(),
|
||||||
}
|
}
|
||||||
|
if 'cfg_crc' in setting:
|
||||||
|
config['header']['template'].update({'size': cfg_size})
|
||||||
|
if 'version' in setting:
|
||||||
|
config['header']['data'].update({'version': hex(cfg_version)})
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
@ -1759,12 +1843,12 @@ def Mapping2Bin(decode_cfg, jsonconfig, filename=""):
|
||||||
|
|
||||||
|
|
||||||
# get binary header data to use the correct version template from device
|
# get binary header data to use the correct version template from device
|
||||||
version, template, size, setting = GetTemplateSetting(decode_cfg)
|
version, size, setting = GetTemplateSetting(decode_cfg)
|
||||||
|
|
||||||
_buffer = bytearray()
|
_buffer = bytearray()
|
||||||
_buffer.extend(decode_cfg)
|
_buffer.extend(decode_cfg)
|
||||||
|
|
||||||
if template is not None:
|
if setting is not None:
|
||||||
try:
|
try:
|
||||||
raw = jsonconfig['header']['format']['jsonrawvalues']
|
raw = jsonconfig['header']['format']['jsonrawvalues']
|
||||||
except:
|
except:
|
||||||
|
@ -1828,6 +1912,8 @@ def Backup(backupfile, backupfileformat, encode_cfg, decode_cfg, configuration):
|
||||||
if backupfileformat.lower() == FileType.BIN.lower():
|
if backupfileformat.lower() == FileType.BIN.lower():
|
||||||
fileformat = "binary"
|
fileformat = "binary"
|
||||||
backup_filename = MakeFilename(backupfile, FileType.BIN, configuration)
|
backup_filename = MakeFilename(backupfile, FileType.BIN, configuration)
|
||||||
|
if args.verbose:
|
||||||
|
message("Writing backup file '{}' ({} format)".format(backup_filename, fileformat), typ=LogType.INFO)
|
||||||
try:
|
try:
|
||||||
backupfp = open(backup_filename, "wb")
|
backupfp = open(backup_filename, "wb")
|
||||||
magic = BINARYFILE_MAGIC
|
magic = BINARYFILE_MAGIC
|
||||||
|
@ -1842,6 +1928,8 @@ def Backup(backupfile, backupfileformat, encode_cfg, decode_cfg, configuration):
|
||||||
if backupfileformat.lower() == FileType.DMP.lower():
|
if backupfileformat.lower() == FileType.DMP.lower():
|
||||||
fileformat = "Tasmota"
|
fileformat = "Tasmota"
|
||||||
backup_filename = MakeFilename(backupfile, FileType.DMP, configuration)
|
backup_filename = MakeFilename(backupfile, FileType.DMP, configuration)
|
||||||
|
if args.verbose:
|
||||||
|
message("Writing backup file '{}' ({} format)".format(backup_filename, fileformat), typ=LogType.INFO)
|
||||||
try:
|
try:
|
||||||
backupfp = open(backup_filename, "wb")
|
backupfp = open(backup_filename, "wb")
|
||||||
backupfp.write(encode_cfg)
|
backupfp.write(encode_cfg)
|
||||||
|
@ -1854,6 +1942,8 @@ def Backup(backupfile, backupfileformat, encode_cfg, decode_cfg, configuration):
|
||||||
elif backupfileformat.lower() == FileType.JSON.lower():
|
elif backupfileformat.lower() == FileType.JSON.lower():
|
||||||
fileformat = "JSON"
|
fileformat = "JSON"
|
||||||
backup_filename = MakeFilename(backupfile, FileType.JSON, configuration)
|
backup_filename = MakeFilename(backupfile, FileType.JSON, configuration)
|
||||||
|
if args.verbose:
|
||||||
|
message("Writing backup file '{}' ({} format)".format(backup_filename, fileformat), typ=LogType.INFO)
|
||||||
try:
|
try:
|
||||||
backupfp = open(backup_filename, "w")
|
backupfp = open(backup_filename, "w")
|
||||||
json.dump(configuration, backupfp, sort_keys=args.jsonsort, indent=None if args.jsonindent<0 else args.jsonindent, separators=(',', ':') if args.jsoncompact else (', ', ': ') )
|
json.dump(configuration, backupfp, sort_keys=args.jsonsort, indent=None if args.jsonindent<0 else args.jsonindent, separators=(',', ':') if args.jsoncompact else (', ', ': ') )
|
||||||
|
@ -1867,7 +1957,7 @@ def Backup(backupfile, backupfileformat, encode_cfg, decode_cfg, configuration):
|
||||||
if args.tasmotafile is not None:
|
if args.tasmotafile is not None:
|
||||||
srctype = 'file'
|
srctype = 'file'
|
||||||
src = args.tasmotafile
|
src = args.tasmotafile
|
||||||
message("Backup successful from {} '{}' using {} format to file '{}' ".format(srctype, src, fileformat, backup_filename), typ=LogType.INFO)
|
message("Backup successful from {} '{}' to file '{}' ({} format)".format(srctype, src, backup_filename, fileformat), typ=LogType.INFO)
|
||||||
|
|
||||||
|
|
||||||
def Restore(restorefile, encode_cfg, decode_cfg, configuration):
|
def Restore(restorefile, encode_cfg, decode_cfg, configuration):
|
||||||
|
@ -1888,6 +1978,8 @@ def Restore(restorefile, encode_cfg, decode_cfg, configuration):
|
||||||
filetype = GetFileType(restorefilename)
|
filetype = GetFileType(restorefilename)
|
||||||
|
|
||||||
if filetype == FileType.DMP:
|
if filetype == FileType.DMP:
|
||||||
|
if args.verbose:
|
||||||
|
message("Reading restore file '{}' (Tasmota format)".format(restorefilename), typ=LogType.INFO)
|
||||||
try:
|
try:
|
||||||
restorefp = open(restorefilename, "rb")
|
restorefp = open(restorefilename, "rb")
|
||||||
new_encode_cfg = restorefp.read()
|
new_encode_cfg = restorefp.read()
|
||||||
|
@ -1896,6 +1988,8 @@ def Restore(restorefile, encode_cfg, decode_cfg, configuration):
|
||||||
exit(e[0], "'{}' {}".format(restorefilename, e[1]),line=inspect.getlineno(inspect.currentframe()))
|
exit(e[0], "'{}' {}".format(restorefilename, e[1]),line=inspect.getlineno(inspect.currentframe()))
|
||||||
|
|
||||||
elif filetype == FileType.BIN:
|
elif filetype == FileType.BIN:
|
||||||
|
if args.verbose:
|
||||||
|
message("Reading restore file '{}' (binary format)".format(restorefilename), typ=LogType.INFO)
|
||||||
try:
|
try:
|
||||||
restorefp = open(restorefilename, "rb")
|
restorefp = open(restorefilename, "rb")
|
||||||
restorebin = restorefp.read()
|
restorebin = restorefp.read()
|
||||||
|
@ -1908,6 +2002,8 @@ def Restore(restorefile, encode_cfg, decode_cfg, configuration):
|
||||||
new_encode_cfg = DecryptEncrypt(decode_cfg) # process binary to binary config
|
new_encode_cfg = DecryptEncrypt(decode_cfg) # process binary to binary config
|
||||||
|
|
||||||
elif filetype == FileType.JSON or filetype == FileType.INVALID_JSON:
|
elif filetype == FileType.JSON or filetype == FileType.INVALID_JSON:
|
||||||
|
if args.verbose:
|
||||||
|
message("Reading restore file '{}' (JSON format)".format(restorefilename), typ=LogType.INFO)
|
||||||
try:
|
try:
|
||||||
restorefp = open(restorefilename, "r")
|
restorefp = open(restorefilename, "r")
|
||||||
jsonconfig = json.load(restorefp)
|
jsonconfig = json.load(restorefp)
|
||||||
|
@ -1932,6 +2028,8 @@ def Restore(restorefile, encode_cfg, decode_cfg, configuration):
|
||||||
if new_encode_cfg != encode_cfg or args.ignorewarning:
|
if new_encode_cfg != encode_cfg or args.ignorewarning:
|
||||||
# write config direct to device via http
|
# write config direct to device via http
|
||||||
if args.device is not None:
|
if args.device is not None:
|
||||||
|
if args.verbose:
|
||||||
|
message("Push new data to '{}' using restore file '{}'".format(args.device, restorefilename), typ=LogType.INFO)
|
||||||
error_code, error_str = PushTasmotaConfig(new_encode_cfg, args.device, args.port, args.username, args.password)
|
error_code, error_str = PushTasmotaConfig(new_encode_cfg, args.device, args.port, args.username, args.password)
|
||||||
if error_code:
|
if error_code:
|
||||||
exit(ExitCode.UPLOAD_CONFIG_ERROR, "Config data upload failed - {}".format(error_str),line=inspect.getlineno(inspect.currentframe()))
|
exit(ExitCode.UPLOAD_CONFIG_ERROR, "Config data upload failed - {}".format(error_str),line=inspect.getlineno(inspect.currentframe()))
|
||||||
|
@ -1941,6 +2039,8 @@ def Restore(restorefile, encode_cfg, decode_cfg, configuration):
|
||||||
|
|
||||||
# write config from a file
|
# write config from a file
|
||||||
elif args.tasmotafile is not None:
|
elif args.tasmotafile is not None:
|
||||||
|
if args.verbose:
|
||||||
|
message("Write new data to file '{}' using restore file '{}'".format(args.tasmotafile, restorefilename), typ=LogType.INFO)
|
||||||
try:
|
try:
|
||||||
outputfile = open(args.tasmotafile, "wb")
|
outputfile = open(args.tasmotafile, "wb")
|
||||||
outputfile.write(new_encode_cfg)
|
outputfile.write(new_encode_cfg)
|
||||||
|
@ -1955,7 +2055,7 @@ def Restore(restorefile, encode_cfg, decode_cfg, configuration):
|
||||||
global exitcode
|
global exitcode
|
||||||
exitcode = ExitCode.RESTORE_SKIPPED
|
exitcode = ExitCode.RESTORE_SKIPPED
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
exit(exitcode, "Configuration data unchanged, upload skipped", typ=LogType.WARNING)
|
message("Configuration data leaving unchanged", typ=LogType.INFO)
|
||||||
|
|
||||||
|
|
||||||
def ParseArgs():
|
def ParseArgs():
|
||||||
|
@ -2132,15 +2232,25 @@ if __name__ == "__main__":
|
||||||
if args.shorthelp:
|
if args.shorthelp:
|
||||||
ShortHelp()
|
ShortHelp()
|
||||||
|
|
||||||
# default no configuration available
|
|
||||||
encode_cfg = None
|
|
||||||
|
|
||||||
# check source args
|
# check source args
|
||||||
if args.device is not None and args.tasmotafile is not None:
|
if args.device is not None and args.tasmotafile is not None:
|
||||||
exit(ExitCode.ARGUMENT_ERROR, "Unable to select source, do not use -d and -f together",line=inspect.getlineno(inspect.currentframe()))
|
exit(ExitCode.ARGUMENT_ERROR, "Unable to select source, do not use -d and -f together",line=inspect.getlineno(inspect.currentframe()))
|
||||||
|
|
||||||
# pull config from Tasmota device/file
|
# default no configuration available
|
||||||
encode_cfg = PullTasmotaConfig()
|
encode_cfg = None
|
||||||
|
|
||||||
|
# pull config from Tasmota device
|
||||||
|
if args.tasmotafile is not None:
|
||||||
|
if args.verbose:
|
||||||
|
message("Load data from file '{}'".format(args.tasmotafile), typ=LogType.INFO)
|
||||||
|
encode_cfg = LoadTasmotaConfig(args.tasmotafile)
|
||||||
|
|
||||||
|
# load config from Tasmota file
|
||||||
|
if args.device is not None:
|
||||||
|
if args.verbose:
|
||||||
|
message("Load data from device '{}'".format(args.device), typ=LogType.INFO)
|
||||||
|
encode_cfg = PullTasmotaConfig(args.device, args.port, username=args.username, password=args.password)
|
||||||
|
|
||||||
if encode_cfg is None:
|
if encode_cfg is None:
|
||||||
# no config source given
|
# no config source given
|
||||||
ShortHelp(False)
|
ShortHelp(False)
|
||||||
|
@ -2157,6 +2267,11 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
# decode into mappings dictionary
|
# decode into mappings dictionary
|
||||||
configuration = Bin2Mapping(decode_cfg, args.jsonrawvalues)
|
configuration = Bin2Mapping(decode_cfg, args.jsonrawvalues)
|
||||||
|
if args.verbose and 'version' in configuration:
|
||||||
|
if args.tasmotafile is not None:
|
||||||
|
message("File '{}' contains data for Tasmota v{}".format(args.tasmotafile, GetVersionStr(configuration['version'])),typ=LogType.INFO)
|
||||||
|
else:
|
||||||
|
message("Device '{}' runs Tasmota v{}".format(args.device,GetVersionStr(configuration['version'])),typ=LogType.INFO)
|
||||||
|
|
||||||
# backup to file
|
# backup to file
|
||||||
if args.backupfile is not None:
|
if args.backupfile is not None:
|
||||||
|
|
Loading…
Reference in New Issue