diff --git a/tools/decode-config.html b/tools/decode-config.html
index 4fa7b4e04..dd24495b7 100644
--- a/tools/decode-config.html
+++ b/tools/decode-config.html
@@ -4,7 +4,7 @@
@@ -69,6 +69,7 @@
Use batch processing
+Notes
@@ -190,7 +191,7 @@
WifiConfig 5
Note: A few very specific module commands like MPC230xx, KNX and some Display commands are not supported. These are still available by JSON output.
Filter data
-The huge number of Tasomta configuration data can be overstrained and confusing, so the most of the configuration data are grouped into categories.
+The huge number of Tasmota configuration data can be overstrained and confusing, so the most of the configuration data are grouped into categories.
With decode-config.py the following categories are available: Display
, Domoticz
, Internal
, KNX
, Led
, Logging
, MCP230xx
, MQTT
, Main
, Management
, Pow
, Sensor
, Serial
, SetOption
, SonoffRF
, System
, Timers
, Wifi
These are similary to the categories on https://github.com/arendst/Sonoff-Tasmota/wiki/Commands.
To filter outputs to a subset of groups use the -g
or --group
arg concatenating the grooup you want, e. g.
@@ -247,12 +248,16 @@
-i,
file to restore configuration from (default: None).
- Replacements: @v=firmware version, @f=device friendly
- name, @h=device hostname
+ Replacements: @v=firmware version from config,
+ @f=device friendly name from config, @h=device
+ hostname from config, @H=device hostname from device
+ (-d arg only)
-o,
file to backup configuration to (default: None).
- Replacements: @v=firmware version, @f=device friendly
- name, @h=device hostname
+ Replacements: @v=firmware version from config,
+ @f=device friendly name from config, @h=device
+ hostname from config, @H=device hostname from device
+ (-d arg only)
-t,
backup filetype (default: 'json')
-E,
@@ -339,3 +344,12 @@ json-indent 2
or under windows
for device in (sonoff1 sonoff2 sonoff3) do python decode-config.py -c my.conf -d %device -o Config_@f_@v
will produce JSON configuration files for host sonoff1, sonoff2 and sonoff3 using friendly name and Tasmota firmware version for backup filenames.
+Notes
+Some general notes:
+
+- Filename replacement macros @h and @H:
+- @h
The @h replacement macro uses the hostname configured with the Tasomta Wifi Hostname <host>
command (defaults to %s-%04d
). It will not use the network hostname of your device because this is not available when working with files only (e.g. --file <filename>
as source).
To prevent having a useless % in your filename, @h will not replaced by configuration data hostname if this contains '%' characters.
+- @H
If you want to use the network hostname within your filename, use the @H replacement macro instead - but be aware this will only replaced if you are using a network device as source (-d
, --device
, --host
); it will not work when using a file as source (-f
, --file
)
+
+
+
diff --git a/tools/decode-config.md b/tools/decode-config.md
index dab16a2c8..97978114a 100644
--- a/tools/decode-config.md
+++ b/tools/decode-config.md
@@ -4,7 +4,7 @@ _decode-config.py_ is able to backup and restore Sonoff-Tasmota configuration.
In contrast to the Tasmota build-in "Backup/Restore Configuration" function,
* _decode-config.py_ uses human readable and editable [JSON](http://www.json.org/)-format for backup/restore,
* _decode-config.py_ can restore previous backuped and changed [JSON](http://www.json.org/)-format files,
-* _decode-config.py_ is able to create Tasomta commands based on given configuration
+* _decode-config.py_ is able to create Tasmota commands based on given configuration
Comparing backup files created by *decode-config.py* and *.dmp files created by Tasmota "Backup/Restore Configuration":
@@ -38,6 +38,7 @@ _decode-config.py_ is able to handle Tasmota configurations for release version
* [Config file](decode-config.md#config-file)
* [Using Tasmota binary configuration files](decode-config.md#using-tasmota-binary-configuration-files)
* [Use batch processing](decode-config.md#use-batch-processing)
+ * [Notes](decode-config.md#notes)
## Prerequisite
* [Python](https://en.wikipedia.org/wiki/Python_(programming_language))
@@ -191,7 +192,7 @@ Example:
Note: A few very specific module commands like MPC230xx, KNX and some Display commands are not supported. These are still available by JSON output.
### Filter data
-The huge number of Tasomta configuration data can be overstrained and confusing, so the most of the configuration data are grouped into categories.
+The huge number of Tasmota configuration data can be overstrained and confusing, so the most of the configuration data are grouped into categories.
With _decode-config.py_ the following categories are available: `Display`, `Domoticz`, `Internal`, `KNX`, `Led`, `Logging`, `MCP230xx`, `MQTT`, `Main`, `Management`, `Pow`, `Sensor`, `Serial`, `SetOption`, `SonoffRF`, `System`, `Timers`, `Wifi`
@@ -266,12 +267,16 @@ For advanced help use `-H` or `--full-help`:
-i, --restore-file
file to restore configuration from (default: None).
- Replacements: @v=firmware version, @f=device friendly
- name, @h=device hostname
+ Replacements: @v=firmware version from config,
+ @f=device friendly name from config, @h=device
+ hostname from config, @H=device hostname from device
+ (-d arg only)
-o, --backup-file
file to backup configuration to (default: None).
- Replacements: @v=firmware version, @f=device friendly
- name, @h=device hostname
+ Replacements: @v=firmware version from config,
+ @f=device friendly name from config, @h=device
+ hostname from config, @H=device hostname from device
+ (-d arg only)
-t, --backup-type json|bin|dmp
backup filetype (default: 'json')
-E, --extension append filetype extension for -i and -o filename
@@ -374,3 +379,12 @@ or under windows
for device in (sonoff1 sonoff2 sonoff3) do python decode-config.py -c my.conf -d %device -o Config_@f_@v
will produce JSON configuration files for host sonoff1, sonoff2 and sonoff3 using friendly name and Tasmota firmware version for backup filenames.
+
+## Notes
+Some general notes:
+* Filename replacement macros **@h** and **@H**:
+ * **@h**
+The **@h** replacement macro uses the hostname configured with the Tasomta Wifi `Hostname ` command (defaults to `%s-%04d`). It will not use the network hostname of your device because this is not available when working with files only (e.g. `--file ` as source).
+To prevent having a useless % in your filename, **@h** will not replaced by configuration data hostname if this contains '%' characters.
+ * **@H**
+If you want to use the network hostname within your filename, use the **@H** replacement macro instead - but be aware this will only replaced if you are using a network device as source (`-d`, `--device`, `--host`); it will not work when using a file as source (`-f`, `--file`)
diff --git a/tools/decode-config.py b/tools/decode-config.py
index d6632dbda..a6299f104 100755
--- a/tools/decode-config.py
+++ b/tools/decode-config.py
@@ -1,13 +1,13 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-VER = '2.1.0006'
+VER = '2.1.0007'
"""
decode-config.py - Backup/Restore Sonoff-Tasmota configuration data
Copyright (C) 2018 Norbert Richter
- This program is free software: you can redistribute it and/or modfy
+ This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
@@ -73,12 +73,16 @@ Usage: decode-config.py [-f ] [-d ] [-P ]
-i, --restore-file
file to restore configuration from (default: None).
- Replacements: @v=firmware version, @f=device friendly
- name, @h=device hostname
+ Replacements: @v=firmware version from config,
+ @f=device friendly name from config, @h=device
+ hostname from config, @H=device hostname from device
+ (-d arg only)
-o, --backup-file
file to backup configuration to (default: None).
- Replacements: @v=firmware version, @f=device friendly
- name, @h=device hostname
+ Replacements: @v=firmware version from config,
+ @f=device friendly name from config, @h=device
+ hostname from config, @H=device hostname from device
+ (-d arg only)
-t, --backup-type json|bin|dmp
backup filetype (default: 'json')
-E, --extension append filetype extension for -i and -o filename
@@ -461,7 +465,7 @@ Setting_5_10_0 = {
'altitude': ('= 0 and args.device is not None:
+ device_hostname = GetTasmotaHostname(args.device, args.port, username=args.username, password=args.password)
+ if device_hostname is None:
+ device_hostname = ''
+
+ filename = filename.replace('@v', config_version)
+ filename = filename.replace('@f', config_friendlyname )
+ filename = filename.replace('@h', config_hostname )
+ filename = filename.replace('@H', device_hostname )
+
dirname = basename = ext = ''
name = filename
@@ -1196,6 +1212,94 @@ def LoadTasmotaConfig(filename):
return encode_cfg
+def TasmotaGet(cmnd, host, port, username=DEFAULTS['source']['username'], password=None, contenttype = None):
+ """
+ Tasmota http request
+
+ @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
+ """
+ body = 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, cmnd))
+ 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.HTTPGET, True)
+ 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 contenttype is not None and header.contenttype()!=contenttype:
+ 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:
+ body = buffer.getvalue()
+ except:
+ pass
+
+ return responsecode, body
+
+
+def GetTasmotaHostname(host, port, username=DEFAULTS['source']['username'], password=None):
+ """
+ Get Tasmota hostname from 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:
+ Tasmota real hostname or None on error
+ """
+ hostname = None
+
+ loginstr = ""
+ if password is not None:
+ loginstr = "user={}&password={}&".format(urllib2.quote(username), urllib2.quote(password))
+ # get hostname
+ responsecode, body = TasmotaGet("cm?{}cmnd=status%205".format(loginstr), host, port, username=username, password=password)
+ if body is not None:
+ jsonbody = json.loads(body)
+ if "StatusNET" in jsonbody and "Hostname" in jsonbody["StatusNET"]:
+ hostname = jsonbody["StatusNET"]["Hostname"]
+ if args.verbose:
+ message("Hostname for '{}' retrieved: '{}'".format(host, hostname), typ=LogType.INFO)
+
+ return hostname
+
+
def PullTasmotaConfig(host, port, username=DEFAULTS['source']['username'], password=None):
"""
Pull config from Tasmota device
@@ -1212,43 +1316,9 @@ def PullTasmotaConfig(host, port, username=DEFAULTS['source']['username'], passw
@return:
binary config data (encrypted) or None on error
"""
+ responsecode, body = TasmotaGet('dl', host, port, username, password, contenttype='application/octet-stream')
- 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()
- except:
- pass
-
- return encode_cfg
+ return body
def PushTasmotaConfig(encode_cfg, host, port, username=DEFAULTS['source']['username'], password=None):
@@ -1273,40 +1343,21 @@ def PushTasmotaConfig(encode_cfg, host, port, username=DEFAULTS['source']['usern
if isinstance(encode_cfg, bytearray):
encode_cfg = str(encode_cfg)
- c = pycurl.Curl()
- buffer = io.BytesIO()
- c.setopt(c.WRITEDATA, buffer)
- header = HTTPHeader()
- c.setopt(c.HEADERFUNCTION, header.store)
- c.setopt(c.FOLLOWLOCATION, True)
# get restore config page first to set internal Tasmota vars
- c.setopt(c.URL, MakeUrl(host, port, 'rs?'))
- 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.HTTPGET, True)
- c.setopt(c.VERBOSE, False)
-
- responsecode = 200
- try:
- c.perform()
- responsecode = c.getinfo(c.RESPONSE_CODE)
- except Exception, e:
- c.close()
- return e[0], e[1]
-
- if responsecode >= 400:
- c.close()
- return responsecode, header.response()
- elif header.contenttype() != 'text/html':
- c.close()
- return ExitCode.UPLOAD_CONFIG_ERROR, "Device did not response properly, may be Tasmota webserver admin mode is disabled (WebServer 2)"
+ responsecode, body = TasmotaGet('rs?', host, port, username, password, contenttype='text/html')
+ if body is None:
+ return responsecode, "ERROR"
# post data
- header.clear()
+ c = pycurl.Curl()
+ header = HTTPHeader()
c.setopt(c.HEADERFUNCTION, header.store)
+ c.setopt(c.WRITEFUNCTION, lambda x: None)
c.setopt(c.POST, 1)
c.setopt(c.URL, MakeUrl(host, port, 'u2'))
+ if username is not None and password is not None:
+ c.setopt(c.HTTPAUTH, c.HTTPAUTH_BASIC)
+ c.setopt(c.USERPWD, username + ':' + password)
try:
isfile = os.path.isfile(encode_cfg)
except:
@@ -2501,12 +2552,12 @@ def ParseArgs():
metavar='',
dest='restorefile',
default=DEFAULTS['backup']['backupfile'],
- help="file to restore configuration from (default: {}). Replacements: @v=firmware version, @f=device friendly name, @h=device hostname".format(DEFAULTS['backup']['restorefile']))
+ help="file to restore configuration from (default: {}). Replacements: @v=firmware version from config, @f=device friendly name from config, @h=device hostname from config, @H=device hostname from device (-d arg only)".format(DEFAULTS['backup']['restorefile']))
backup.add_argument('-o', '--backup-file',
metavar='',
dest='backupfile',
default=DEFAULTS['backup']['backupfile'],
- help="file to backup configuration to (default: {}). Replacements: @v=firmware version, @f=device friendly name, @h=device hostname".format(DEFAULTS['backup']['backupfile']))
+ help="file to backup configuration to (default: {}). Replacements: @v=firmware version from config, @f=device friendly name from config, @h=device hostname from config, @H=device hostname from device (-d arg only)".format(DEFAULTS['backup']['backupfile']))
backup_file_formats = ['json', 'bin', 'dmp']
backup.add_argument('-t', '--backup-type',
metavar='|'.join(backup_file_formats),