2018-05-19 16:31:55 +01:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
|
|
"""
|
|
|
|
decode-status.py - decode status for Sonoff-Tasmota
|
|
|
|
|
2019-01-01 12:55:01 +00:00
|
|
|
Copyright (C) 2019 Theo Arends
|
2018-05-19 16:31:55 +01:00
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
Requirements:
|
|
|
|
- Python
|
|
|
|
- pip json pycurl
|
|
|
|
|
|
|
|
Instructions:
|
|
|
|
Execute command with option -d to retrieve status report from device or
|
|
|
|
get a copy of the status message with http command http://sonoff/cm?cmnd=status%200
|
|
|
|
and store it in file status.json
|
|
|
|
|
|
|
|
Usage:
|
2018-09-14 15:39:06 +01:00
|
|
|
./decode-status.py -d <hostname or IP address> [-u username] [-p password]
|
2018-05-19 16:31:55 +01:00
|
|
|
or
|
|
|
|
./decode-status.py -f <JSON status information file>
|
|
|
|
|
|
|
|
Example:
|
|
|
|
./decode-status.py -d sonoff1
|
2018-09-14 15:39:06 +01:00
|
|
|
./decode-status.py -d sonoff1 -p 12345678
|
2018-05-19 16:31:55 +01:00
|
|
|
or
|
|
|
|
./decode-status.py -f status.json
|
|
|
|
"""
|
|
|
|
|
|
|
|
import io
|
|
|
|
import os.path
|
|
|
|
import json
|
|
|
|
import pycurl
|
2018-09-14 15:39:06 +01:00
|
|
|
import urllib2
|
2018-05-19 16:31:55 +01:00
|
|
|
from sys import exit
|
|
|
|
from optparse import OptionParser
|
|
|
|
from StringIO import StringIO
|
|
|
|
|
|
|
|
a_on_off = ["OFF","ON "]
|
|
|
|
|
2018-07-25 11:42:53 +01:00
|
|
|
a_setoption = [[
|
2018-05-19 16:31:55 +01:00
|
|
|
"Save power state and use after restart",
|
|
|
|
"Restrict button actions to single, double and hold",
|
|
|
|
"Show value units in JSON messages",
|
2018-07-25 11:42:53 +01:00
|
|
|
"MQTT enabled",
|
2018-05-19 16:31:55 +01:00
|
|
|
"Respond as Command topic instead of RESULT",
|
|
|
|
"MQTT retain on Power",
|
|
|
|
"MQTT retain on Button",
|
|
|
|
"MQTT retain on Switch",
|
|
|
|
"Convert temperature to Fahrenheit",
|
|
|
|
"MQTT retain on Sensor",
|
|
|
|
"MQTT retained LWT to OFFLINE when topic changes",
|
|
|
|
"Swap Single and Double press Button",
|
|
|
|
"Do not use flash page rotate",
|
|
|
|
"Button single press only",
|
|
|
|
"Power interlock mode",
|
|
|
|
"Do not allow PWM control",
|
|
|
|
"Reverse clock",
|
|
|
|
"Allow entry of decimal color values",
|
|
|
|
"CO2 color to light signal",
|
|
|
|
"HASS discovery",
|
|
|
|
"Do not control Power with Dimmer",
|
|
|
|
"Energy monitoring while powered off",
|
|
|
|
"MQTT serial",
|
2018-07-31 10:49:23 +01:00
|
|
|
"MQTT serial binary",
|
2018-11-04 15:55:12 +00:00
|
|
|
"Convert pressure to mmHg",
|
2018-07-25 11:42:53 +01:00
|
|
|
"KNX enabled",
|
2018-05-19 16:31:55 +01:00
|
|
|
"Use Power device index on single relay devices",
|
|
|
|
"KNX enhancement",
|
2018-06-26 10:38:30 +01:00
|
|
|
"RF receive decimal",
|
|
|
|
"IR receive decimal",
|
2018-07-15 16:30:38 +01:00
|
|
|
"Enforce HASS light group",
|
2018-07-25 11:42:53 +01:00
|
|
|
"Do not show Wifi and Mqtt state using Led"
|
|
|
|
],[
|
2019-02-04 00:04:42 +00:00
|
|
|
"Key hold time (ms)",
|
|
|
|
"Sonoff POW Max_Power_Retry",
|
|
|
|
"Tuya dimmer device id",
|
2019-02-04 10:41:51 +00:00
|
|
|
"(not used) mDNS delayed start (Sec)",
|
|
|
|
"Boot loop retry offset (0 = disable)",
|
2019-03-28 10:25:38 +00:00
|
|
|
"RGBWW remap",
|
2019-02-04 00:04:42 +00:00
|
|
|
"","","","","","",
|
|
|
|
"","","","","","",
|
|
|
|
],[
|
2018-07-25 11:42:53 +01:00
|
|
|
"Timers enabled",
|
2018-09-06 17:08:10 +01:00
|
|
|
"Generic ESP8285 GPIO enabled",
|
|
|
|
"Add UTC time offset to JSON message",
|
2018-11-04 15:55:12 +00:00
|
|
|
"Show hostname and IP address in GUI",
|
2018-11-20 14:00:24 +00:00
|
|
|
"Apply SetOption20 to Tuya",
|
2019-01-03 17:07:03 +00:00
|
|
|
"mDNS enabled",
|
2018-11-20 14:00:24 +00:00
|
|
|
"Use wifi network scan at restart",
|
|
|
|
"Use wifi network rescan regularly",
|
2018-11-21 15:36:10 +00:00
|
|
|
"Add IR raw data to JSON message",
|
2018-11-27 10:04:45 +00:00
|
|
|
"Change state topic from tele/STATE to stat/RESULT",
|
2018-12-01 17:58:30 +00:00
|
|
|
"Enable normal sleep instead of dynamic sleep",
|
2019-01-03 17:07:03 +00:00
|
|
|
"Force local operation when button/switch topic is set",
|
2019-03-28 10:25:38 +00:00
|
|
|
"Do not use retain flag on HOLD messages",
|
2019-01-07 15:43:03 +00:00
|
|
|
"","","",
|
2018-07-25 11:42:53 +01:00
|
|
|
"","","","",
|
|
|
|
"","","","",
|
|
|
|
"","","","",
|
|
|
|
"","","",""
|
|
|
|
]]
|
2018-05-19 16:31:55 +01:00
|
|
|
|
|
|
|
a_features = [[
|
|
|
|
"","","USE_I2C","USE_SPI",
|
|
|
|
"USE_DISCOVERY","USE_ARDUINO_OTA","USE_MQTT_TLS","USE_WEBSERVER",
|
|
|
|
"WEBSERVER_ADVERTISE","USE_EMULATION","MQTT_PUBSUBCLIENT","MQTT_TASMOTAMQTT",
|
|
|
|
"MQTT_ESPMQTTARDUINO","MQTT_HOST_DISCOVERY","USE_ARILUX_RF","USE_WS2812",
|
|
|
|
"USE_WS2812_DMA","USE_IR_REMOTE","USE_IR_HVAC","USE_IR_RECEIVE",
|
|
|
|
"USE_DOMOTICZ","USE_DISPLAY","USE_HOME_ASSISTANT","USE_SERIAL_BRIDGE",
|
|
|
|
"USE_TIMERS","USE_SUNRISE","USE_TIMERS_WEB","USE_RULES",
|
2018-10-02 16:07:30 +01:00
|
|
|
"USE_KNX","USE_WPS","USE_SMARTCONFIG","MQTT_ARDUINOMQTT"
|
2018-06-26 10:38:30 +01:00
|
|
|
],[
|
2019-02-08 13:55:45 +00:00
|
|
|
"USE_CONFIG_OVERRIDE","FIRMWARE_MINIMAL","FIRMWARE_SENSORS","FIRMWARE_CLASSIC",
|
|
|
|
"FIRMWARE_KNX_NO_EMULATION","USE_DISPLAY_MODES1TO5","USE_DISPLAY_GRAPH","USE_DISPLAY_LCD",
|
2018-08-25 11:26:36 +01:00
|
|
|
"USE_DISPLAY_SSD1306","USE_DISPLAY_MATRIX","USE_DISPLAY_ILI9341","USE_DISPLAY_EPAPER",
|
2018-10-21 11:44:45 +01:00
|
|
|
"USE_DISPLAY_SH1106","USE_MP3_PLAYER","USE_PCA9685","USE_TUYA_DIMMER",
|
2018-11-20 14:00:24 +00:00
|
|
|
"USE_RC_SWITCH","USE_ARMTRONIX_DIMMERS","","",
|
2018-09-09 13:31:40 +01:00
|
|
|
"","","","NO_EXTRA_4K_HEAP",
|
|
|
|
"VTABLES_IN_IRAM","VTABLES_IN_DRAM","VTABLES_IN_FLASH","PIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH",
|
2018-06-26 10:38:30 +01:00
|
|
|
"PIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY","PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH","DEBUG_THEO","USE_DEBUG_DRIVER"
|
|
|
|
],[
|
2018-05-19 16:31:55 +01:00
|
|
|
"","USE_ADC_VCC","USE_ENERGY_SENSOR","USE_PZEM004T",
|
|
|
|
"USE_DS18B20","USE_DS18x20_LEGACY","USE_DS18x20","USE_DHT",
|
|
|
|
"USE_SHT","USE_HTU","USE_BMP","USE_BME680",
|
|
|
|
"USE_BH1750","USE_VEML6070","USE_ADS1115_I2CDEV","USE_ADS1115",
|
|
|
|
"USE_INA219","USE_SHT3X","USE_MHZ19","USE_TSL2561",
|
|
|
|
"USE_SENSEAIR","USE_PMS5003","USE_MGS","USE_NOVA_SDS",
|
|
|
|
"USE_SGP30","USE_SR04","USE_SDM120","USE_SI1145",
|
2018-06-29 10:15:27 +01:00
|
|
|
"USE_SDM630","USE_LM75AD","USE_APDS9960","USE_TM1638"
|
2018-06-26 10:38:30 +01:00
|
|
|
],[
|
2018-07-31 10:49:23 +01:00
|
|
|
"USE_MCP230xx","USE_MPR121","USE_CCS811","USE_MPU6050",
|
2018-09-06 17:08:10 +01:00
|
|
|
"USE_MCP230xx_OUTPUT","USE_MCP230xx_DISPLAYOUTPUT","USE_HLW8012","USE_CSE7766",
|
2018-10-17 11:27:05 +01:00
|
|
|
"USE_MCP39F501","USE_PZEM_AC","USE_DS3231","USE_HX711",
|
2018-12-11 13:24:52 +00:00
|
|
|
"USE_PZEM_DC","USE_TX20_WIND_SENSOR","USE_MGC3130","USE_RF_SENSOR",
|
2019-01-01 12:45:44 +00:00
|
|
|
"USE_THEO_V2","USE_ALECTO_V2","USE_AZ7798","USE_MAX31855",
|
2019-03-28 10:25:38 +00:00
|
|
|
"USE_PN532_I2C","USE_MAX44009","USE_SCD30","USE_HRE",
|
2019-04-15 17:12:42 +01:00
|
|
|
"USE_ADE7953","","","",
|
2018-05-19 16:31:55 +01:00
|
|
|
"","","",""]]
|
|
|
|
|
|
|
|
usage = "usage: decode-status {-d | -f} arg"
|
|
|
|
parser = OptionParser(usage)
|
|
|
|
parser.add_option("-d", "--dev", action="store", type="string",
|
|
|
|
dest="device", help="device to retrieve status from")
|
2018-09-14 15:39:06 +01:00
|
|
|
parser.add_option("-u", "--username", action="store", type="string",
|
|
|
|
dest="username", help="username for login", default="admin")
|
|
|
|
parser.add_option("-p", "--password", action="store", type="string",
|
|
|
|
dest="password", help="password for login", default=None)
|
2018-05-19 16:31:55 +01:00
|
|
|
parser.add_option("-f", "--file", metavar="FILE",
|
|
|
|
dest="jsonfile", default="status.json", help="status json file (default: status.json)")
|
|
|
|
(options, args) = parser.parse_args()
|
|
|
|
|
|
|
|
if (options.device):
|
|
|
|
buffer = StringIO()
|
2018-09-14 15:39:06 +01:00
|
|
|
loginstr = ""
|
|
|
|
if options.password is not None:
|
|
|
|
loginstr = "user={}&password={}&".format(urllib2.quote(options.username), urllib2.quote(options.password))
|
|
|
|
url = str("http://{}/cm?{}cmnd=status%200".format(options.device, loginstr))
|
2018-05-19 16:31:55 +01:00
|
|
|
c = pycurl.Curl()
|
|
|
|
c.setopt(c.URL, url)
|
|
|
|
c.setopt(c.WRITEDATA, buffer)
|
|
|
|
c.perform()
|
|
|
|
c.close()
|
|
|
|
body = buffer.getvalue()
|
|
|
|
obj = json.loads(body)
|
|
|
|
else:
|
|
|
|
jsonfile = options.jsonfile
|
2019-02-04 10:41:51 +00:00
|
|
|
with open(jsonfile, "r") as fp:
|
2019-02-04 00:04:42 +00:00
|
|
|
obj = json.load(fp)
|
2018-05-19 16:31:55 +01:00
|
|
|
|
|
|
|
def StartDecode():
|
2019-02-04 10:41:51 +00:00
|
|
|
print ("\n*** decode-status.py v20190204 by Theo Arends and Jacek Ziolkowski ***")
|
2018-07-25 11:42:53 +01:00
|
|
|
|
2018-05-19 16:31:55 +01:00
|
|
|
# print("Decoding\n{}".format(obj))
|
|
|
|
|
2019-02-04 00:04:42 +00:00
|
|
|
if "StatusSNS" in obj:
|
|
|
|
if "Time" in obj["StatusSNS"]:
|
2018-07-17 15:07:03 +01:00
|
|
|
time = str(" from status report taken at {}".format(obj["StatusSNS"]["Time"]))
|
|
|
|
|
2019-02-04 00:04:42 +00:00
|
|
|
if "Status" in obj:
|
|
|
|
if "FriendlyName" in obj["Status"]:
|
2018-07-25 11:42:53 +01:00
|
|
|
print("Decoding information for device {}{}".format(obj["Status"]["FriendlyName"][0], time))
|
2018-07-17 15:07:03 +01:00
|
|
|
|
2019-02-04 00:04:42 +00:00
|
|
|
if "StatusLOG" in obj:
|
|
|
|
if "SetOption" in obj["StatusLOG"]:
|
2018-07-17 15:07:03 +01:00
|
|
|
options = []
|
2019-02-04 10:41:51 +00:00
|
|
|
|
2019-02-04 00:04:42 +00:00
|
|
|
i = 0
|
2019-02-04 10:41:51 +00:00
|
|
|
for r,opt_group in enumerate(a_setoption):
|
2019-02-04 00:04:42 +00:00
|
|
|
register = obj["StatusLOG"]["SetOption"][r]
|
2019-02-04 10:41:51 +00:00
|
|
|
|
2019-02-04 00:04:42 +00:00
|
|
|
if r > 0 and len(obj["StatusLOG"]["SetOption"]) == 2: # old firmware: array consisted only of SetOptions 0..31 and resolution
|
2019-02-04 10:41:51 +00:00
|
|
|
break
|
|
|
|
|
2019-02-04 00:04:42 +00:00
|
|
|
if r == 1:
|
|
|
|
if len(register) == 8: # pre 6.1.1.14: array consisted of SetOptions 0..31, resolution, and SetOptions 50..81
|
|
|
|
i += 18 # adjust option index and skip 2nd register
|
|
|
|
continue
|
2019-02-04 10:41:51 +00:00
|
|
|
|
|
|
|
elif len(register) == 36: # 6.1.1.14: array consists of SetOptions 0..31, SetOptions 32..49, and SetOptions 50..81
|
2019-02-04 00:04:42 +00:00
|
|
|
split_register = [int(register[opt*2:opt*2+2],16) for opt in range(18)] # split register into 18 values
|
2019-02-04 10:41:51 +00:00
|
|
|
|
2019-02-04 00:04:42 +00:00
|
|
|
for opt_idx, option in enumerate(opt_group):
|
2019-02-04 10:41:51 +00:00
|
|
|
options.append(str("{0:2d} ({1:3d}) {2}".format(i, split_register[opt_idx], option)))
|
2019-02-04 00:04:42 +00:00
|
|
|
i += 1
|
2019-02-04 10:41:51 +00:00
|
|
|
|
2019-02-04 00:04:42 +00:00
|
|
|
if r in (0, 2): #registers 1 and 3 hold binary values
|
|
|
|
for opt_idx, option in enumerate(opt_group):
|
|
|
|
i_register = int(register,16)
|
|
|
|
state = (i_register >> opt_idx) & 1
|
2019-02-04 10:41:51 +00:00
|
|
|
options.append(str("{0:2d} ({1}) {2}".format(i, a_on_off[state], option)))
|
2019-02-04 00:04:42 +00:00
|
|
|
i += 1
|
2019-02-04 10:41:51 +00:00
|
|
|
|
2018-07-17 15:07:03 +01:00
|
|
|
print("\nOptions")
|
2019-02-04 00:04:42 +00:00
|
|
|
for o in options:
|
|
|
|
print(" {}".format(o))
|
2018-07-17 15:07:03 +01:00
|
|
|
|
2019-02-04 00:04:42 +00:00
|
|
|
if "StatusMEM" in obj:
|
|
|
|
if "Features" in obj["StatusMEM"]:
|
2018-07-17 15:07:03 +01:00
|
|
|
features = []
|
|
|
|
for f in range(5):
|
|
|
|
feature = obj["StatusMEM"]["Features"][f]
|
|
|
|
i_feature = int(feature,16)
|
2019-02-04 00:04:42 +00:00
|
|
|
if f == 0:
|
2018-07-17 15:07:03 +01:00
|
|
|
features.append(str("Language LCID = {}".format(i_feature & 0xFFFF)))
|
|
|
|
else:
|
|
|
|
for i in range(len(a_features[f -1])):
|
2019-02-04 00:04:42 +00:00
|
|
|
if (i_feature >> i) & 1:
|
2018-07-17 15:07:03 +01:00
|
|
|
features.append(a_features[f -1][i])
|
|
|
|
|
|
|
|
features.sort()
|
|
|
|
print("\nFeatures")
|
2019-02-04 00:04:42 +00:00
|
|
|
for f in features:
|
|
|
|
print(" {}".format(f))
|
2018-05-19 16:31:55 +01:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
try:
|
|
|
|
StartDecode()
|
|
|
|
except Exception as e:
|
2018-09-14 15:39:06 +01:00
|
|
|
print("E: {}".format(e))
|