Merge pull request #4352 from curzon01/development

decode-config.py: add/fix Tasmota cmnd output and filename macros
This commit is contained in:
Theo Arends 2018-11-14 08:56:30 +01:00 committed by GitHub
commit 5b5b0b928f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 177 additions and 98 deletions

View File

@ -4,7 +4,7 @@
<ul> <ul>
<li><em>decode-config.py</em> uses human readable and editable <a href="http://www.json.org/">JSON</a>-format for backup/restore,</li> <li><em>decode-config.py</em> uses human readable and editable <a href="http://www.json.org/">JSON</a>-format for backup/restore,</li>
<li><em>decode-config.py</em> can restore previous backuped and changed <a href="http://www.json.org/">JSON</a>-format files,</li> <li><em>decode-config.py</em> can restore previous backuped and changed <a href="http://www.json.org/">JSON</a>-format files,</li>
<li><em>decode-config.py</em> is able to create Tasomta commands based on given configuration</li> <li><em>decode-config.py</em> is able to create Tasmota commands based on given configuration</li>
</ul> </ul>
<p>Comparing backup files created by <em>decode-config.py</em> and *.dmp files created by Tasmota &quot;Backup/Restore Configuration&quot;: </p> <p>Comparing backup files created by <em>decode-config.py</em> and *.dmp files created by Tasmota &quot;Backup/Restore Configuration&quot;: </p>
<table> <table>
@ -69,6 +69,7 @@
<li><a href="decode-config.md#use-batch-processing">Use batch processing</a></li> <li><a href="decode-config.md#use-batch-processing">Use batch processing</a></li>
</ul> </ul>
</li> </li>
<li><a href="decode-config.md#notes">Notes</a></li>
</ul> </ul>
</li> </li>
</ul> </ul>
@ -190,7 +191,7 @@
<span class="hljs-selector-tag">WifiConfig</span> 5 <span class="hljs-selector-tag">WifiConfig</span> 5
</code></pre><p>Note: A few very specific module commands like MPC230xx, KNX and some Display commands are not supported. These are still available by JSON output.</p> </code></pre><p>Note: A few very specific module commands like MPC230xx, KNX and some Display commands are not supported. These are still available by JSON output.</p>
<h3 id="filter-data">Filter data</h3> <h3 id="filter-data">Filter data</h3>
<p>The huge number of Tasomta configuration data can be overstrained and confusing, so the most of the configuration data are grouped into categories. </p> <p>The huge number of Tasmota configuration data can be overstrained and confusing, so the most of the configuration data are grouped into categories. </p>
<p>With <em>decode-config.py</em> the following categories are available: <code>Display</code>, <code>Domoticz</code>, <code>Internal</code>, <code>KNX</code>, <code>Led</code>, <code>Logging</code>, <code>MCP230xx</code>, <code>MQTT</code>, <code>Main</code>, <code>Management</code>, <code>Pow</code>, <code>Sensor</code>, <code>Serial</code>, <code>SetOption</code>, <code>SonoffRF</code>, <code>System</code>, <code>Timers</code>, <code>Wifi</code></p> <p>With <em>decode-config.py</em> the following categories are available: <code>Display</code>, <code>Domoticz</code>, <code>Internal</code>, <code>KNX</code>, <code>Led</code>, <code>Logging</code>, <code>MCP230xx</code>, <code>MQTT</code>, <code>Main</code>, <code>Management</code>, <code>Pow</code>, <code>Sensor</code>, <code>Serial</code>, <code>SetOption</code>, <code>SonoffRF</code>, <code>System</code>, <code>Timers</code>, <code>Wifi</code></p>
<p>These are similary to the categories on <a href="Tasmota Command Wiki">https://github.com/arendst/Sonoff-Tasmota/wiki/Commands</a>.</p> <p>These are similary to the categories on <a href="Tasmota Command Wiki">https://github.com/arendst/Sonoff-Tasmota/wiki/Commands</a>.</p>
<p>To filter outputs to a subset of groups use the <code>-g</code> or <code>--group</code> arg concatenating the grooup you want, e. g.</p> <p>To filter outputs to a subset of groups use the <code>-g</code> or <code>--group</code> arg concatenating the grooup you want, e. g.</p>
@ -247,12 +248,16 @@
-i, <span class="hljs-comment">--restore-file &lt;filename&gt;</span> -i, <span class="hljs-comment">--restore-file &lt;filename&gt;</span>
file to restore configuration from (<span class="hljs-keyword">default</span>: <span class="hljs-type">None</span>). file to restore configuration from (<span class="hljs-keyword">default</span>: <span class="hljs-type">None</span>).
<span class="hljs-type">Replacements</span>: @v=firmware version, @f=device friendly <span class="hljs-type">Replacements</span>: @v=firmware version from config,
name, @h=device hostname @f=device friendly name from config, @h=device
hostname from config, @<span class="hljs-type">H</span>=device hostname from device
(-d arg only)
-o, <span class="hljs-comment">--backup-file &lt;filename&gt;</span> -o, <span class="hljs-comment">--backup-file &lt;filename&gt;</span>
file to backup configuration to (<span class="hljs-keyword">default</span>: <span class="hljs-type">None</span>). file to backup configuration to (<span class="hljs-keyword">default</span>: <span class="hljs-type">None</span>).
<span class="hljs-type">Replacements</span>: @v=firmware version, @f=device friendly <span class="hljs-type">Replacements</span>: @v=firmware version from config,
name, @h=device hostname @f=device friendly name from config, @h=device
hostname from config, @<span class="hljs-type">H</span>=device hostname from device
(-d arg only)
-t, <span class="hljs-comment">--backup-type json|bin|dmp</span> -t, <span class="hljs-comment">--backup-type json|bin|dmp</span>
backup filetype (<span class="hljs-keyword">default</span>: 'json') backup filetype (<span class="hljs-keyword">default</span>: 'json')
-<span class="hljs-type">E</span>, <span class="hljs-comment">--extension append filetype extension for -i and -o filename</span> -<span class="hljs-type">E</span>, <span class="hljs-comment">--extension append filetype extension for -i and -o filename</span>
@ -339,3 +344,12 @@ json-indent <span class="hljs-number">2</span>
</code></pre><p>or under windows</p> </code></pre><p>or under windows</p>
<pre><code><span class="hljs-keyword">for</span> device <span class="hljs-keyword">in</span> (sonoff1 sonoff2 sonoff3) <span class="hljs-keyword">do</span> <span class="hljs-keyword">python</span> decode-config.py -c my.conf -d %device -o Config_@f_@v <pre><code><span class="hljs-keyword">for</span> device <span class="hljs-keyword">in</span> (sonoff1 sonoff2 sonoff3) <span class="hljs-keyword">do</span> <span class="hljs-keyword">python</span> decode-config.py -c my.conf -d %device -o Config_@f_@v
</code></pre><p>will produce JSON configuration files for host sonoff1, sonoff2 and sonoff3 using friendly name and Tasmota firmware version for backup filenames.</p> </code></pre><p>will produce JSON configuration files for host sonoff1, sonoff2 and sonoff3 using friendly name and Tasmota firmware version for backup filenames.</p>
<h2 id="notes">Notes</h2>
<p>Some general notes:</p>
<ul>
<li>Filename replacement macros <strong>@h</strong> and <strong>@H</strong>:<ul>
<li><strong>@h</strong><br>The <strong>@h</strong> replacement macro uses the hostname configured with the Tasomta Wifi <code>Hostname &lt;host&gt;</code> command (defaults to <code>%s-%04d</code>). It will not use the network hostname of your device because this is not available when working with files only (e.g. <code>--file &lt;filename&gt;</code> as source).<br>To prevent having a useless % in your filename, <strong>@h</strong> will not replaced by configuration data hostname if this contains &#39;%&#39; characters.</li>
<li><strong>@H</strong><br>If you want to use the network hostname within your filename, use the <strong>@H</strong> replacement macro instead - but be aware this will only replaced if you are using a network device as source (<code>-d</code>, <code>--device</code>, <code>--host</code>); it will not work when using a file as source (<code>-f</code>, <code>--file</code>)</li>
</ul>
</li>
</ul>

View File

@ -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, 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_ 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_ 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": 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) * [Config file](decode-config.md#config-file)
* [Using Tasmota binary configuration files](decode-config.md#using-tasmota-binary-configuration-files) * [Using Tasmota binary configuration files](decode-config.md#using-tasmota-binary-configuration-files)
* [Use batch processing](decode-config.md#use-batch-processing) * [Use batch processing](decode-config.md#use-batch-processing)
* [Notes](decode-config.md#notes)
## Prerequisite ## Prerequisite
* [Python](https://en.wikipedia.org/wiki/Python_(programming_language)) * [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. 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 ### 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` 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 <filename> -i, --restore-file <filename>
file to restore configuration from (default: None). file to restore configuration from (default: None).
Replacements: @v=firmware version, @f=device friendly Replacements: @v=firmware version from config,
name, @h=device hostname @f=device friendly name from config, @h=device
hostname from config, @H=device hostname from device
(-d arg only)
-o, --backup-file <filename> -o, --backup-file <filename>
file to backup configuration to (default: None). file to backup configuration to (default: None).
Replacements: @v=firmware version, @f=device friendly Replacements: @v=firmware version from config,
name, @h=device hostname @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 -t, --backup-type json|bin|dmp
backup filetype (default: 'json') backup filetype (default: 'json')
-E, --extension append filetype extension for -i and -o filename -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 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. 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`)

View File

@ -1,13 +1,13 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
VER = '2.1.0006' VER = '2.1.0007'
""" """
decode-config.py - Backup/Restore Sonoff-Tasmota configuration data decode-config.py - Backup/Restore Sonoff-Tasmota configuration data
Copyright (C) 2018 Norbert Richter <nr@prsolution.eu> Copyright (C) 2018 Norbert Richter <nr@prsolution.eu>
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 it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
@ -73,12 +73,16 @@ Usage: decode-config.py [-f <filename>] [-d <host>] [-P <port>]
-i, --restore-file <filename> -i, --restore-file <filename>
file to restore configuration from (default: None). file to restore configuration from (default: None).
Replacements: @v=firmware version, @f=device friendly Replacements: @v=firmware version from config,
name, @h=device hostname @f=device friendly name from config, @h=device
hostname from config, @H=device hostname from device
(-d arg only)
-o, --backup-file <filename> -o, --backup-file <filename>
file to backup configuration to (default: None). file to backup configuration to (default: None).
Replacements: @v=firmware version, @f=device friendly Replacements: @v=firmware version from config,
name, @h=device hostname @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 -t, --backup-type json|bin|dmp
backup filetype (default: 'json') backup filetype (default: 'json')
-E, --extension append filetype extension for -i and -o filename -E, --extension append filetype extension for -i and -o filename
@ -461,7 +465,7 @@ Setting_5_10_0 = {
'altitude': ('<h', 0x2F6, (None, '-30000 <= $ <= 30000', ('Sensor', '"Altitude {}".format($)')) ), 'altitude': ('<h', 0x2F6, (None, '-30000 <= $ <= 30000', ('Sensor', '"Altitude {}".format($)')) ),
'tele_period': ('<H', 0x2F8, (None, '0 <= $ <= 1 or 10 <= $ <= 3600',('MQTT', '"TelePeriod {}".format($)')) ), 'tele_period': ('<H', 0x2F8, (None, '0 <= $ <= 1 or 10 <= $ <= 3600',('MQTT', '"TelePeriod {}".format($)')) ),
'ledstate': ('B', 0x2FB, (None, '0 <= ($ & 0x7) <= 7', ('Main', '"LedState {}".format($)')) ), 'ledstate': ('B', 0x2FB, (None, '0 <= ($ & 0x7) <= 7', ('Main', '"LedState {}".format($)')) ),
'param': ('B', 0x2FC, ([23], None, ('SetOption', '"SetOption{} {}".format(#+32,$)')) ), 'param': ('B', 0x2FC, ([23], None, ('SetOption', '"SetOption{} {}".format(#+31,$)')) ),
'state_text': ('11s', 0x313, ([4], None, ('MQTT', '"StateText{} {}".format(#,$)')) ), 'state_text': ('11s', 0x313, ([4], None, ('MQTT', '"StateText{} {}".format(#,$)')) ),
'domoticz_update_timer': ('<H', 0x340, (None, '0 <= $ <= 3600', ('Domoticz', '"DomoticzUpdateTimer {}".format($)')) ), 'domoticz_update_timer': ('<H', 0x340, (None, '0 <= $ <= 3600', ('Domoticz', '"DomoticzUpdateTimer {}".format($)')) ),
'pwm_range': ('<H', 0x342, (None, '$==1 or 255 <= $ <= 1023', ('Management', '"PwmRange {}".format($)')) ), 'pwm_range': ('<H', 0x342, (None, '$==1 or 255 <= $ <= 1023', ('Management', '"PwmRange {}".format($)')) ),
@ -615,7 +619,7 @@ Setting_5_14_0.update ({
'dow': ('<H', (0x2E2,3, 8), (None, '1 <= $ <= 7', ('Management', None)) ), 'dow': ('<H', (0x2E2,3, 8), (None, '1 <= $ <= 7', ('Management', None)) ),
'hour': ('<H', (0x2E2,5,11), (None, '0 <= $ <= 23', ('Management', None)) ), 'hour': ('<H', (0x2E2,5,11), (None, '0 <= $ <= 23', ('Management', None)) ),
}, 0x2E2, ([2], None, ('Management', None)), (None, False) ), }, 0x2E2, ([2], None, ('Management', None)), (None, False) ),
'param': ('B', 0x2FC, ([18], None, ('SetOption', '"SetOption{} {}".format(#+32,$)')) ), 'param': ('B', 0x2FC, ([18], None, ('SetOption', '"SetOption{} {}".format(#+31,$)')) ),
'toffset': ('<h', 0x30E, ([2], None, ('Management', '"{cmnd} {hemis},{week},{month},{dow},{hour},{toffset}".format(cmnd="TimeSTD" if idx==1 else "TimeDST", hemis=@["tflag"][#-1]["hemis"], week=@["tflag"][#-1]["week"], month=@["tflag"][#-1]["month"], dow=@["tflag"][#-1]["dow"], hour=@["tflag"][#-1]["hour"], toffset=value)')) ), 'toffset': ('<h', 0x30E, ([2], None, ('Management', '"{cmnd} {hemis},{week},{month},{dow},{hour},{toffset}".format(cmnd="TimeSTD" if idx==1 else "TimeDST", hemis=@["tflag"][#-1]["hemis"], week=@["tflag"][#-1]["week"], month=@["tflag"][#-1]["month"], dow=@["tflag"][#-1]["dow"], hour=@["tflag"][#-1]["hour"], toffset=value)')) ),
}) })
# ====================================================================== # ======================================================================
@ -692,8 +696,7 @@ Setting_6_2_1['flag2'][0].update ({
# ====================================================================== # ======================================================================
Setting_6_2_1_2 = copy.deepcopy(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 ({
# hardcoded to 0 'user_esp8285_enable': ('<L', (0x3A0,1, 1), (None, None, ('SetOption', '"SetOption51 {}".format($)')) ),
'user_esp8285_enable': ('<L', (0x3A0,1, 1), (None, None, ('System', None)) ),
}) })
# ====================================================================== # ======================================================================
Setting_6_2_1_3 = copy.deepcopy(Setting_6_2_1_2) Setting_6_2_1_3 = copy.deepcopy(Setting_6_2_1_2)
@ -732,7 +735,7 @@ Setting_6_2_1_19.update({
}) })
Setting_6_2_1_20 = Setting_6_2_1_19 Setting_6_2_1_20 = 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, None, ('System', None)) ), 'gui_hostname_ip': ('<L', (0x3A0,1,3), (None, None, ('SetOption', '"SetOption53 {}".format($)')) ),
}) })
# ====================================================================== # ======================================================================
Setting_6_3_0 = copy.deepcopy(Setting_6_2_1_20) Setting_6_3_0 = copy.deepcopy(Setting_6_2_1_20)
@ -757,7 +760,7 @@ Setting_6_3_0_4.update({
'displays': ('<L', 0x7B0, (None, None, ('System', None)), '"0x{:08x}".format($)' ), 'displays': ('<L', 0x7B0, (None, None, ('System', None)), '"0x{:08x}".format($)' ),
}) })
Setting_6_3_0_4['flag3'][0].update ({ Setting_6_3_0_4['flag3'][0].update ({
'tuya_apply_o20': ('<L', (0x3A0,1, 4), (None, None, ('System', None)) ), 'tuya_apply_o20': ('<L', (0x3A0,1, 4), (None, None, ('SetOption', '"SetOption54 {}".format($)')) ),
}) })
# ====================================================================== # ======================================================================
Settings = [ Settings = [
@ -1091,11 +1094,13 @@ def MakeFilename(filename, filetype, configmapping):
@param filename: @param filename:
original filename possible containing replacements: original filename possible containing replacements:
@v: @v:
Tasmota version Tasmota version from config data
@f: @f:
friendlyname friendlyname from config data
@h: @h:
hostname hostname from config data
@H:
hostname from device (-d arg only)
@param filetype: @param filetype:
FileType.x object - creates extension if not None FileType.x object - creates extension if not None
@param configmapping: @param configmapping:
@ -1104,14 +1109,25 @@ def MakeFilename(filename, filetype, configmapping):
@return: @return:
New filename with replacements New filename with replacements
""" """
v = f1 = f2 = f3 = f4 = '' config_version = config_friendlyname = config_hostname = device_hostname = ''
if 'version' in configmapping: if 'version' in configmapping:
v = GetVersionStr( int(str(configmapping['version']), 0) ) config_version = GetVersionStr( int(str(configmapping['version']), 0) )
filename = filename.replace('@v', v)
if 'friendlyname' in configmapping: if 'friendlyname' in configmapping:
filename = filename.replace('@f', configmapping['friendlyname'][0] ) config_friendlyname = configmapping['friendlyname'][0]
if 'hostname' in configmapping: if 'hostname' in configmapping:
filename = filename.replace('@h', configmapping['hostname'] ) if configmapping['hostname'].find('%') < 0:
config_hostname = configmapping['hostname']
if filename.find('@H') >= 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 = '' dirname = basename = ext = ''
name = filename name = filename
@ -1196,6 +1212,94 @@ def LoadTasmotaConfig(filename):
return encode_cfg 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): def PullTasmotaConfig(host, port, username=DEFAULTS['source']['username'], password=None):
""" """
Pull config from Tasmota device Pull config from Tasmota device
@ -1212,43 +1316,9 @@ def PullTasmotaConfig(host, port, username=DEFAULTS['source']['username'], passw
@return: @return:
binary config data (encrypted) or None on error binary config data (encrypted) or None on error
""" """
responsecode, body = TasmotaGet('dl', host, port, username, password, contenttype='application/octet-stream')
encode_cfg = None return body
# 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
def PushTasmotaConfig(encode_cfg, host, port, username=DEFAULTS['source']['username'], password=None): 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): if isinstance(encode_cfg, bytearray):
encode_cfg = str(encode_cfg) 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 # get restore config page first to set internal Tasmota vars
c.setopt(c.URL, MakeUrl(host, port, 'rs?')) responsecode, body = TasmotaGet('rs?', host, port, username, password, contenttype='text/html')
if args.username is not None and args.password is not None: if body is None:
c.setopt(c.HTTPAUTH, c.HTTPAUTH_BASIC) return responsecode, "ERROR"
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)"
# post data # post data
header.clear() c = pycurl.Curl()
header = HTTPHeader()
c.setopt(c.HEADERFUNCTION, header.store) c.setopt(c.HEADERFUNCTION, header.store)
c.setopt(c.WRITEFUNCTION, lambda x: None)
c.setopt(c.POST, 1) c.setopt(c.POST, 1)
c.setopt(c.URL, MakeUrl(host, port, 'u2')) c.setopt(c.URL, MakeUrl(host, port, 'u2'))
if username is not None and password is not None:
c.setopt(c.HTTPAUTH, c.HTTPAUTH_BASIC)
c.setopt(c.USERPWD, username + ':' + password)
try: try:
isfile = os.path.isfile(encode_cfg) isfile = os.path.isfile(encode_cfg)
except: except:
@ -2501,12 +2552,12 @@ def ParseArgs():
metavar='<filename>', metavar='<filename>',
dest='restorefile', dest='restorefile',
default=DEFAULTS['backup']['backupfile'], 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', backup.add_argument('-o', '--backup-file',
metavar='<filename>', metavar='<filename>',
dest='backupfile', dest='backupfile',
default=DEFAULTS['backup']['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_file_formats = ['json', 'bin', 'dmp']
backup.add_argument('-t', '--backup-type', backup.add_argument('-t', '--backup-type',
metavar='|'.join(backup_file_formats), metavar='|'.join(backup_file_formats),