Save latest session changes: migrate to console_set profiles, device_list structure with per-device console_set, docs updated, formatting updates
This commit is contained in:
parent
142d825909
commit
35e6098a61
@ -4,23 +4,40 @@ This document provides detailed information about the console commands used in t
|
||||
|
||||
## Console Section in network_configuration.json
|
||||
|
||||
The `console` section in the `network_configuration.json` file allows you to configure various Tasmota device settings through the TasmotaManager script. These settings are applied to all devices during the version check process.
|
||||
The `console_set` section in the `network_configuration.json` file is now a dictionary of named command lists. This lets you define multiple sets (profiles) and choose one per device via device_list. These settings are applied during processing.
|
||||
|
||||
```json
|
||||
{
|
||||
"console": {
|
||||
"SwitchRetain": "Off",
|
||||
"ButtonRetain": "Off",
|
||||
"PowerOnState": "3",
|
||||
"PowerRetain": "On",
|
||||
"SetOption1": "0",
|
||||
"SetOption3": "1",
|
||||
"SetOption13": "0",
|
||||
"SetOption19": "0",
|
||||
"SetOption32": "8",
|
||||
"SetOption53": "1",
|
||||
"SetOption73": "1",
|
||||
"rule1": "on button1#state=10 do power0 toggle endon"
|
||||
"console_set": {
|
||||
"Default": [
|
||||
"SwitchRetain Off",
|
||||
"ButtonRetain Off",
|
||||
"PowerOnState 3",
|
||||
"PowerRetain On",
|
||||
"SetOption1 0",
|
||||
"SetOption3 1",
|
||||
"SetOption13 0",
|
||||
"SetOption19 0",
|
||||
"SetOption32 8",
|
||||
"SetOption53 1",
|
||||
"SetOption73 1",
|
||||
"rule1 on button1#state=10 do power0 toggle endon"
|
||||
],
|
||||
"alt": [
|
||||
"SwitchRetain Off",
|
||||
"ButtonRetain Off",
|
||||
"PowerOnState 3",
|
||||
"PowerRetain On",
|
||||
"SetOption1 0",
|
||||
"SetOption3 1",
|
||||
"SetOption4 1",
|
||||
"SetOption13 0",
|
||||
"SetOption19 0",
|
||||
"SetOption32 8",
|
||||
"SetOption53 1",
|
||||
"SetOption73 1",
|
||||
"rule1 on button1#state=10 do power0 toggle endon"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
57
README.md
57
README.md
@ -65,27 +65,50 @@ Create a `network_configuration.json` file with the following structure:
|
||||
"FullTopic": "%prefix%/%topic%/",
|
||||
"NoRetain": false
|
||||
},
|
||||
"config_other": {
|
||||
"Example_Device_Template": "{\"NAME\":\"Example\",\"GPIO\":[0],\"FLAG\":0,\"BASE\":18}"
|
||||
"device_list": {
|
||||
"Example_Device_Template": {"template": "{\"NAME\":\"Example\",\"GPIO\":[0],\"FLAG\":0,\"BASE\":18}", "console_set": "Default"}
|
||||
},
|
||||
"console": {
|
||||
"SwitchRetain": "Off",
|
||||
"ButtonRetain": "Off",
|
||||
"PowerOnState": "3",
|
||||
"PowerRetain": "On",
|
||||
"SetOption1": "0",
|
||||
"SetOption3": "1",
|
||||
"SetOption4": "1",
|
||||
"SetOption13": "0",
|
||||
"SetOption19": "0",
|
||||
"SetOption32": "8",
|
||||
"SetOption53": "1",
|
||||
"SetOption73": "1",
|
||||
"rule1": "on button1#state=10 do power0 toggle endon"
|
||||
"console_set": {
|
||||
"Default": [
|
||||
"SwitchRetain Off",
|
||||
"ButtonRetain Off",
|
||||
"PowerOnState 3",
|
||||
"PowerRetain On",
|
||||
"SetOption1 0",
|
||||
"SetOption3 1",
|
||||
"SetOption4 1",
|
||||
"SetOption13 0",
|
||||
"SetOption19 0",
|
||||
"SetOption32 8",
|
||||
"SetOption53 1",
|
||||
"SetOption73 1",
|
||||
"rule1 on button1#state=10 do power0 toggle endon"
|
||||
],
|
||||
"alt": [
|
||||
"SwitchRetain Off",
|
||||
"ButtonRetain Off",
|
||||
"PowerOnState 3",
|
||||
"PowerRetain On",
|
||||
"SetOption1 0",
|
||||
"SetOption3 1",
|
||||
"SetOption4 1",
|
||||
"SetOption13 0",
|
||||
"SetOption19 0",
|
||||
"SetOption32 8",
|
||||
"SetOption53 1",
|
||||
"SetOption73 1",
|
||||
"rule1 on button1#state=10 do power0 toggle endon"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
About device_list and console_set profiles:
|
||||
- device_list: map each device key to an object with:
|
||||
- template: the Tasmota template JSON string to apply when matching that device name
|
||||
- console_set: the name of the console_set profile to apply (e.g., "Default" or "alt")
|
||||
- console_set: a dictionary of named command lists. Define as many profiles as needed and select them per device via device_list.
|
||||
|
||||
Note:
|
||||
- In the mqtt section, the Topic supports the placeholder "%hostname_base%". The script will replace this with the base of the device's hostname (everything before the first dash). For example, for a device named "KitchenLamp-1234", the Topic will be set to "KitchenLamp".
|
||||
- NoRetain controls Tasmota's SetOption62 (true = No Retain, false = Use Retain).
|
||||
@ -195,7 +218,7 @@ This feature helps automate the setup of new Tasmota devices that haven't been p
|
||||
|
||||
## Console Parameters
|
||||
|
||||
The script supports setting Tasmota console parameters via the `console` section in the configuration. After verifying and updating MQTT settings, the script will apply all console parameters to each device. This allows you to:
|
||||
The script supports setting Tasmota console parameters via `console_set` (preferred). As of this version, `console_set` is a dictionary of named lists (e.g., "Default", "alt"). You can select which set to apply per device by specifying the `console_set` name in each `device_list` entry. A legacy `console` dict and the legacy list-style `console_set` are still accepted for backward compatibility, but may be removed in the future. After verifying and updating MQTT settings, the script will apply all console parameters to each device. This allows you to:
|
||||
|
||||
- Configure device behavior (PowerOnState, SetOptions, etc.)
|
||||
- Set up rules for button actions
|
||||
|
||||
@ -956,10 +956,18 @@ class TasmotaDiscovery:
|
||||
bool: True if template was updated, False otherwise
|
||||
"""
|
||||
try:
|
||||
# Get config_other settings
|
||||
config_other = self.config.get('config_other', {})
|
||||
# Get device_list (new) or legacy config_other settings
|
||||
device_list = self.config.get('device_list')
|
||||
config_other = {}
|
||||
if isinstance(device_list, dict):
|
||||
# Map device_list entries to name -> template string
|
||||
for k, v in device_list.items():
|
||||
if isinstance(v, dict) and 'template' in v:
|
||||
config_other[k] = v.get('template', '')
|
||||
else:
|
||||
config_other = self.config.get('config_other', {})
|
||||
if not config_other:
|
||||
self.logger.debug(f"{name}: No config_other settings found in configuration")
|
||||
self.logger.debug(f"{name}: No device_list/config_other settings found in configuration")
|
||||
return False
|
||||
|
||||
# Get Status 0 for device name from Configuration/Other page with increased timeout
|
||||
@ -1280,7 +1288,7 @@ class TasmotaDiscovery:
|
||||
print(f"\nNo template match found for device {name} at {ip}")
|
||||
print(f" Device Name on device: '{device_name}'")
|
||||
print(f" Template on device: '{current_template}'")
|
||||
print("Please add an appropriate entry to config_other in your configuration file.")
|
||||
print("Please add an appropriate entry to device_list (preferred) or legacy config_other in your configuration file.")
|
||||
|
||||
return template_updated
|
||||
|
||||
@ -1480,9 +1488,74 @@ class TasmotaDiscovery:
|
||||
def apply_console_settings(self, ip, name, with_retry=False):
|
||||
"""Apply console parameters from configuration to the device.
|
||||
Returns True if any setting was updated, False otherwise.
|
||||
Supports both legacy 'console' dict and new 'console_set' list formats
|
||||
at either the config root or under the 'mqtt' section. The 'console_set'
|
||||
list uses strings like "SetOption1 0" or "rule1 on ...".
|
||||
"""
|
||||
console_updated = False
|
||||
console_params = self.config.get('console', {})
|
||||
|
||||
# Build a unified console_params dict from various supported locations
|
||||
console_params = {}
|
||||
|
||||
def add_from_console_set(console_set_list):
|
||||
# Parse list of "Param Value..." into dict preserving last-wins
|
||||
for entry in console_set_list or []:
|
||||
try:
|
||||
if not isinstance(entry, str):
|
||||
continue
|
||||
parts = entry.strip().split(' ', 1)
|
||||
if not parts:
|
||||
continue
|
||||
param = parts[0].strip()
|
||||
value = parts[1] if len(parts) > 1 else ''
|
||||
console_params[param] = value
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
# Determine selected console set name based on device_list mapping
|
||||
selected_set_name = 'Default'
|
||||
try:
|
||||
# Attempt to read device name from device_list using Status 0 like template logic (best-effort)
|
||||
device_list = self.config.get('device_list', {})
|
||||
if isinstance(device_list, dict) and device_list:
|
||||
# Fetch Status 0 to get DeviceName for key matching
|
||||
try:
|
||||
url_status0 = f"http://{ip}/cm?cmnd=Status%200"
|
||||
resp = requests.get(url_status0, timeout=5)
|
||||
data = resp.json()
|
||||
device_name = data.get('Status', {}).get('DeviceName')
|
||||
except Exception:
|
||||
device_name = None
|
||||
if device_name and device_name in device_list:
|
||||
entry = device_list.get(device_name, {})
|
||||
if isinstance(entry, dict) and isinstance(entry.get('console_set'), str):
|
||||
selected_set_name = entry['console_set']
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Prefer top-level console_set (new format), then mqtt.console_set
|
||||
top_console_set = self.config.get('console_set')
|
||||
mqtt_console_set = self.config.get('mqtt', {}).get('console_set')
|
||||
|
||||
def add_named_or_list(cs, set_name):
|
||||
if isinstance(cs, dict):
|
||||
add_from_console_set(cs.get(set_name) or cs.get('Default') or [])
|
||||
elif isinstance(cs, list):
|
||||
add_from_console_set(cs)
|
||||
|
||||
add_named_or_list(top_console_set, selected_set_name)
|
||||
add_named_or_list(mqtt_console_set, selected_set_name)
|
||||
|
||||
# Backward compatibility: legacy dicts (top-level and mqtt.console)
|
||||
legacy_console = self.config.get('console', {})
|
||||
if isinstance(legacy_console, dict):
|
||||
for k, v in legacy_console.items():
|
||||
console_params.setdefault(k, v)
|
||||
legacy_mqtt_console = self.config.get('mqtt', {}).get('console', {})
|
||||
if isinstance(legacy_mqtt_console, dict):
|
||||
for k, v in legacy_mqtt_console.items():
|
||||
console_params.setdefault(k, v)
|
||||
|
||||
if not console_params:
|
||||
return False
|
||||
|
||||
|
||||
@ -27,5 +27,22 @@
|
||||
"Topic": "%hostname_base%",
|
||||
"FullTopic": "%prefix%/%topic%/",
|
||||
"NoRetain": false
|
||||
},
|
||||
"device_list": {
|
||||
"Example_Device": {
|
||||
"template": "{\"NAME\":\"Example\",\"GPIO\":[0],\"FLAG\":0,\"BASE\":18}",
|
||||
"console_set": "Default"
|
||||
}
|
||||
},
|
||||
"console_set": {
|
||||
"Default": [
|
||||
"SwitchRetain Off",
|
||||
"ButtonRetain Off",
|
||||
"PowerOnState 3",
|
||||
"PowerRetain On"
|
||||
],
|
||||
"alt": [
|
||||
"SwitchRetain Off"
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -30,31 +30,78 @@
|
||||
"FullTopic": "%prefix%/%topic%/",
|
||||
"NoRetain": false
|
||||
},
|
||||
"config_other": {
|
||||
"TreatLife_SW_SS01S": "{\"NAME\":\"TL SS01S Swtch\",\"GPIO\":[0,0,0,0,52,158,0,0,21,17,0,0,0],\"FLAG\":0,\"BASE\":18}\n",
|
||||
"TreatLife_SW_SS02S": "{\"NAME\":\"Treatlife SS02\",\"GPIO\":[0,0,0,0,288,576,0,0,224,32,0,0,0,0],\"FLAG\":0,\"BASE\":18}",
|
||||
"TreatLife_SW_SS02S_Orig": "{\"NAME\":\"Treatlife SS02\",\"GPIO\":[0,0,0,0,289,0,0,0,224,32,0,0,0,0],\"FLAG\":0,\"BASE\":18}",
|
||||
"TreatLife_DIM_DS02S": "{\"NAME\":\"DS02S Dimmer\",\"GPIO\":[0,107,0,108,0,0,0,0,0,0,0,0,0],\"FLAG\":0,\"BASE\":54}",
|
||||
"CloudFree_SW1": "{\"NAME\":\"CloudFree SW1\",\"GPIO\":[0,224,0,32,320,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"FLAG\":0,\"BASE\":1}",
|
||||
"Gosund_WP5_Plug": "{\"NAME\":\"Gosund-WP5\",\"GPIO\":[0,0,0,0,17,0,0,0,56,57,21,0,0],\"FLAG\":0,\"BASE\":18}",
|
||||
"Gosund_Plug": "{\"NAME\":\"Gosund-WP5\",\"GPIO\":[0,0,0,0,32,0,0,0,320,321,224,0,0,0],\"FLAG\":0,\"BASE\":18}",
|
||||
"CloudFree_X10S_Plug": "{\"NAME\":\"Aoycocr X10S\",\"GPIO\":[56,0,57,0,21,134,0,0,131,17,132,0,0],\"FLAG\":0,\"BASE\":45}\n",
|
||||
"Sonoff_S31_PM_Plug": "{\"NAME\":\"Sonoff S31\",\"GPIO\":[17,145,0,146,0,0,0,0,21,56,0,0,0],\"FLAG\":0,\"BASE\":41}",
|
||||
"Sonoff S31": ""
|
||||
"device_list": {
|
||||
"TreatLife_SW_SS01S": {
|
||||
"template": "{\"NAME\":\"TL SS01S Swtch\",\"GPIO\":[0,0,0,0,52,158,0,0,21,17,0,0,0],\"FLAG\":0,\"BASE\":18}\n",
|
||||
"console_set": "Default"
|
||||
},
|
||||
"TreatLife_SW_SS02S": {
|
||||
"template": "{\"NAME\":\"Treatlife SS02\",\"GPIO\":[0,0,0,0,288,576,0,0,224,32,0,0,0,0],\"FLAG\":0,\"BASE\":18}",
|
||||
"console_set": "Default"
|
||||
},
|
||||
"TreatLife_SW_SS02S_Orig": {
|
||||
"template": "{\"NAME\":\"Treatlife SS02\",\"GPIO\":[0,0,0,0,289,0,0,0,224,32,0,0,0,0],\"FLAG\":0,\"BASE\":18}",
|
||||
"console_set": "Default"
|
||||
},
|
||||
"TreatLife_DIM_DS02S": {
|
||||
"template": "{\"NAME\":\"DS02S Dimmer\",\"GPIO\":[0,107,0,108,0,0,0,0,0,0,0,0,0],\"FLAG\":0,\"BASE\":54}",
|
||||
"console_set": "Default"
|
||||
},
|
||||
"CloudFree_SW1": {
|
||||
"template": "{\"NAME\":\"CloudFree SW1\",\"GPIO\":[0,224,0,32,320,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"FLAG\":0,\"BASE\":1}",
|
||||
"console_set": "Default"
|
||||
},
|
||||
"Gosund_WP5_Plug": {
|
||||
"template": "{\"NAME\":\"Gosund-WP5\",\"GPIO\":[0,0,0,0,17,0,0,0,56,57,21,0,0],\"FLAG\":0,\"BASE\":18}",
|
||||
"console_set": "Default"
|
||||
},
|
||||
"Gosund_Plug": {
|
||||
"template": "{\"NAME\":\"Gosund-WP5\",\"GPIO\":[0,0,0,0,32,0,0,0,320,321,224,0,0,0],\"FLAG\":0,\"BASE\":18}",
|
||||
"console_set": "Default"
|
||||
},
|
||||
"CloudFree_X10S_Plug": {
|
||||
"template": "{\"NAME\":\"Aoycocr X10S\",\"GPIO\":[56,0,57,0,21,134,0,0,131,17,132,0,0],\"FLAG\":0,\"BASE\":45}\n",
|
||||
"console_set": "alt"
|
||||
},
|
||||
"Sonoff_S31_PM_Plug": {
|
||||
"template": "{\"NAME\":\"Sonoff S31\",\"GPIO\":[17,145,0,146,0,0,0,0,21,56,0,0,0],\"FLAG\":0,\"BASE\":41}",
|
||||
"console_set": "Default"
|
||||
},
|
||||
"Sonoff S31": {
|
||||
"template": "",
|
||||
"console_set": "Default"
|
||||
}
|
||||
},
|
||||
"console": {
|
||||
"SwitchRetain": "Off",
|
||||
"ButtonRetain": "Off",
|
||||
"PowerRetain": "On",
|
||||
"PowerOnState": "3",
|
||||
"SetOption1": "0",
|
||||
"SetOption3": "1",
|
||||
"SetOption4": "1",
|
||||
"SetOption13": "0",
|
||||
"SetOption19": "0",
|
||||
"SetOption32": "8",
|
||||
"SetOption53": "1",
|
||||
"SetOption73": "1",
|
||||
"rule1": "on button1#state=10 do power0 toggle endon"
|
||||
"console_set": {
|
||||
"Default": [
|
||||
"SwitchRetain Off",
|
||||
"ButtonRetain Off",
|
||||
"PowerRetain On",
|
||||
"PowerOnState 3",
|
||||
"SetOption1 0",
|
||||
"SetOption3 1",
|
||||
"SetOption4 1",
|
||||
"SetOption13 0",
|
||||
"SetOption19 0",
|
||||
"SetOption32 8",
|
||||
"SetOption53 1",
|
||||
"SetOption73 1",
|
||||
"rule1 on button1#state=10 do power0 toggle endon"
|
||||
],
|
||||
"alt": [
|
||||
"SwitchRetain Off",
|
||||
"ButtonRetain Off",
|
||||
"PowerRetain On",
|
||||
"PowerOnState 3",
|
||||
"SetOption1 0",
|
||||
"SetOption3 1",
|
||||
"SetOption4 1",
|
||||
"SetOption13 0",
|
||||
"SetOption19 0",
|
||||
"SetOption32 8",
|
||||
"SetOption53 1",
|
||||
"SetOption73 1",
|
||||
"rule1 on button1#state=10 do power0 toggle endon"
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -39,8 +39,15 @@ def main():
|
||||
with open('network_configuration.json', 'r') as f:
|
||||
config = json.load(f)
|
||||
|
||||
# Find a key in config_other that has a non-empty value
|
||||
config_other = config.get('config_other', {})
|
||||
# Find a key in device_list/config_other that has a non-empty template value
|
||||
config_other = {}
|
||||
dl = config.get('device_list', {})
|
||||
if isinstance(dl, dict):
|
||||
for k, v in dl.items():
|
||||
if isinstance(v, dict) and v.get('template'):
|
||||
config_other[k] = v.get('template')
|
||||
else:
|
||||
config_other = config.get('config_other', {})
|
||||
key_to_modify = None
|
||||
for key, value in config_other.items():
|
||||
if value: # If value is not empty
|
||||
@ -48,24 +55,29 @@ def main():
|
||||
break
|
||||
|
||||
if not key_to_modify:
|
||||
logger.error("Could not find a key with a non-empty value in config_other")
|
||||
logger.error("Could not find a key with a non-empty value in device_list/config_other")
|
||||
return 1
|
||||
|
||||
logger.info(f"Using key: {key_to_modify} for testing")
|
||||
|
||||
# Save the original value
|
||||
# Save the original value and set blank in underlying config
|
||||
original_value = config_other[key_to_modify]
|
||||
|
||||
# Set a blank value for the key
|
||||
config_other[key_to_modify] = ""
|
||||
# Apply blank value into config (device_list preferred)
|
||||
if 'device_list' in config and isinstance(config['device_list'], dict) and key_to_modify in config['device_list']:
|
||||
if isinstance(config['device_list'][key_to_modify], dict):
|
||||
config['device_list'][key_to_modify]['template'] = ""
|
||||
else:
|
||||
if 'config_other' in config and isinstance(config['config_other'], dict):
|
||||
config['config_other'][key_to_modify] = ""
|
||||
|
||||
# Create a TasmotaDiscovery instance with the modified configuration
|
||||
discovery = TasmotaDiscovery(debug=True)
|
||||
discovery.config = config
|
||||
|
||||
# Log the config_other and the key we're testing with
|
||||
logger.info(f"config_other keys: {list(config_other.keys())}")
|
||||
logger.info(f"config_other[{key_to_modify}] = '{config_other[key_to_modify]}'")
|
||||
# Log the config mapping and the key we're testing with
|
||||
logger.info(f"template mapping keys: {list(config_other.keys())}")
|
||||
logger.info(f"template[{key_to_modify}] = '{config_other[key_to_modify]}'")
|
||||
|
||||
# Add a debug method to the TasmotaDiscovery class to log what's happening
|
||||
original_check_and_update_template = discovery.check_and_update_template
|
||||
@ -111,8 +123,13 @@ def main():
|
||||
logger.info(f"Calling check_and_update_template method with device name: {key_to_modify}")
|
||||
result = discovery.check_and_update_template("192.168.8.100", "test_device")
|
||||
|
||||
# Restore the original value
|
||||
config_other[key_to_modify] = original_value
|
||||
# Restore the original value in config
|
||||
if 'device_list' in config and isinstance(config['device_list'], dict) and key_to_modify in config['device_list']:
|
||||
if isinstance(config['device_list'][key_to_modify], dict):
|
||||
config['device_list'][key_to_modify]['template'] = original_value
|
||||
else:
|
||||
if 'config_other' in config and isinstance(config['config_other'], dict):
|
||||
config['config_other'][key_to_modify] = original_value
|
||||
|
||||
# Verify the result
|
||||
if result is False:
|
||||
|
||||
@ -33,15 +33,28 @@ def main():
|
||||
|
||||
# Get console settings from configuration
|
||||
mqtt_config = discovery.config.get('mqtt', {})
|
||||
console_params = mqtt_config.get('console', {})
|
||||
|
||||
if not console_params:
|
||||
print("No console parameters found in configuration. Please add some to test.")
|
||||
sys.exit(1)
|
||||
|
||||
print("Console parameters that will be applied:")
|
||||
for param, value in console_params.items():
|
||||
print(f" {param}: {value}")
|
||||
# Prefer console_set if present, else fall back to legacy console dicts
|
||||
console_set = discovery.config.get('console_set') or mqtt_config.get('console_set')
|
||||
if console_set:
|
||||
if isinstance(console_set, dict):
|
||||
print("Available console_set profiles:")
|
||||
for name, entries in console_set.items():
|
||||
print(f"- {name} ({len(entries)} commands)")
|
||||
print("\nCommands in 'Default' (if present):")
|
||||
for entry in console_set.get('Default', []):
|
||||
print(f" {entry}")
|
||||
else:
|
||||
print("Console commands that will be applied (console_set):")
|
||||
for entry in console_set:
|
||||
print(f" {entry}")
|
||||
else:
|
||||
console_params = discovery.config.get('console', {}) or mqtt_config.get('console', {})
|
||||
if not console_params:
|
||||
print("No console parameters found in configuration. Please add some to test.")
|
||||
sys.exit(1)
|
||||
print("Console parameters that will be applied (legacy console):")
|
||||
for param, value in console_params.items():
|
||||
print(f" {param}: {value}")
|
||||
|
||||
# Process the single device
|
||||
print("\nProcessing device...")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user