Add automatic rule enabling feature to simplify configuration
This commit implements automatic enabling of Tasmota rules when they are defined in the configuration. Key changes: - Modified TasmotaManager.py to detect rule definitions and automatically send enable commands - Updated network_configuration.json to remove redundant Rule1 entry - Updated documentation in README.md and CONSOLE_COMMANDS.md to explain the new feature - Added test script to verify the automatic rule enabling functionality This change simplifies the configuration by allowing users to define rules without needing to explicitly enable them with a separate command.
This commit is contained in:
parent
42915f508e
commit
daa015b4c2
@ -7,20 +7,21 @@ This document provides detailed information about the console commands used in t
|
|||||||
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` 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.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"console": {
|
{
|
||||||
"SwitchRetain": "Off",
|
"console": {
|
||||||
"ButtonRetain": "Off",
|
"SwitchRetain": "Off",
|
||||||
"PowerOnState": "3",
|
"ButtonRetain": "Off",
|
||||||
"PowerRetain": "On",
|
"PowerOnState": "3",
|
||||||
"SetOption1": "0",
|
"PowerRetain": "On",
|
||||||
"SetOption3": "1",
|
"SetOption1": "0",
|
||||||
"SetOption13": "0",
|
"SetOption3": "1",
|
||||||
"SetOption19": "0",
|
"SetOption13": "0",
|
||||||
"SetOption32": "8",
|
"SetOption19": "0",
|
||||||
"SetOption53": "1",
|
"SetOption32": "8",
|
||||||
"SetOption73": "1",
|
"SetOption53": "1",
|
||||||
"rule1": "on button1#state=10 do power0 toggle endon",
|
"SetOption73": "1",
|
||||||
"Rule1": "1"
|
"rule1": "on button1#state=10 do power0 toggle endon"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -76,9 +77,16 @@ Here are some other useful SetOptions that can be added to the configuration:
|
|||||||
|
|
||||||
Rules allow you to create simple automations directly on the Tasmota device.
|
Rules allow you to create simple automations directly on the Tasmota device.
|
||||||
|
|
||||||
|
### Automatic Rule Enabling
|
||||||
|
|
||||||
|
When you define a rule (e.g., `rule1`, `rule2`, `rule3`), the TasmotaManager script will automatically enable it by sending the corresponding enable command (`Rule1 1`, `Rule2 1`, `Rule3 1`) to the device. This means you only need to include the rule definition in your configuration, and the script will handle enabling it.
|
||||||
|
|
||||||
| Command | Values | Description |
|
| Command | Values | Description |
|
||||||
|---------|--------|-------------|
|
|---------|--------|-------------|
|
||||||
| `rule1` | Rule expression | Defines the first rule. Example: `on button1#state=10 do power0 toggle endon` |
|
| `rule1` | Rule expression | Defines the first rule. Example: `on button1#state=10 do power0 toggle endon` |
|
||||||
| `Rule1` | `0`, `1` | Enables or disables rule1:<br>`0` = disable (default)<br>`1` = enable |
|
| `rule2` | Rule expression | Defines the second rule. |
|
||||||
|
| `rule3` | Rule expression | Defines the third rule. |
|
||||||
|
|
||||||
|
Note: You no longer need to include the `Rule1`, `Rule2`, or `Rule3` commands in your configuration as they are automatically applied. If you do include them, they will still be processed, but they are redundant.
|
||||||
|
|
||||||
For more information about Tasmota commands, visit the [official Tasmota documentation](https://tasmota.github.io/docs/Commands/).
|
For more information about Tasmota commands, visit the [official Tasmota documentation](https://tasmota.github.io/docs/Commands/).
|
||||||
22
README.md
22
README.md
@ -76,8 +76,7 @@ Create a `network_configuration.json` file with the following structure:
|
|||||||
"SetOption32": "8",
|
"SetOption32": "8",
|
||||||
"SetOption53": "1",
|
"SetOption53": "1",
|
||||||
"SetOption73": "1",
|
"SetOption73": "1",
|
||||||
"rule1": "on button1#state=10 do power0 toggle endon",
|
"rule1": "on button1#state=10 do power0 toggle endon"
|
||||||
"Rule1": "1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,6 +130,25 @@ The script supports setting Tasmota console parameters via the `console` section
|
|||||||
- Configure retain flags for various message types
|
- Configure retain flags for various message types
|
||||||
- Apply any other Tasmota console commands
|
- Apply any other Tasmota console commands
|
||||||
|
|
||||||
|
### Automatic Rule Enabling
|
||||||
|
|
||||||
|
The script automatically enables rules when they are defined. If you include a rule definition (e.g., `rule1`, `rule2`, `rule3`) in the console section, the script will automatically send the corresponding enable command (`Rule1 1`, `Rule2 1`, `Rule3 1`) to the device. This means you no longer need to include both the rule definition and the enable command in your configuration.
|
||||||
|
|
||||||
|
For example, this configuration:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"console": {
|
||||||
|
"rule1": "on button1#state=10 do power0 toggle endon"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Will automatically enable rule1 on the device, equivalent to manually sending both:
|
||||||
|
```
|
||||||
|
rule1 on button1#state=10 do power0 toggle endon
|
||||||
|
Rule1 1
|
||||||
|
```
|
||||||
|
|
||||||
Each parameter is sent as a command to the device using the Tasmota HTTP API. The device details in `TasmotaDevices.json` will include a `console_status` field indicating whether console parameters were updated.
|
Each parameter is sent as a command to the device using the Tasmota HTTP API. The device details in `TasmotaDevices.json` will include a `console_status` field indicating whether console parameters were updated.
|
||||||
|
|
||||||
For detailed documentation of all available SetOptions and other console commands, please refer to the [CONSOLE_COMMANDS.md](CONSOLE_COMMANDS.md) file. This documentation includes:
|
For detailed documentation of all available SetOptions and other console commands, please refer to the [CONSOLE_COMMANDS.md](CONSOLE_COMMANDS.md) file. This documentation includes:
|
||||||
|
|||||||
@ -751,11 +751,26 @@ class TasmotaDiscovery:
|
|||||||
self.logger.error(f"{name}: Error setting ButtonRetain commands: {str(e)}")
|
self.logger.error(f"{name}: Error setting ButtonRetain commands: {str(e)}")
|
||||||
|
|
||||||
# Process all other console parameters
|
# Process all other console parameters
|
||||||
|
# Track rules that need to be enabled
|
||||||
|
rules_to_enable = {}
|
||||||
|
|
||||||
for param, value in console_params.items():
|
for param, value in console_params.items():
|
||||||
# Skip ButtonRetain as it's handled specially above
|
# Skip ButtonRetain as it's handled specially above
|
||||||
if param == "ButtonRetain":
|
if param == "ButtonRetain":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Check if this is a rule definition (lowercase rule1, rule2, etc.)
|
||||||
|
if param.lower().startswith('rule') and param.lower() == param and param[-1].isdigit():
|
||||||
|
# Store the rule number for later enabling
|
||||||
|
rule_num = param[-1]
|
||||||
|
rules_to_enable[rule_num] = True
|
||||||
|
self.logger.debug(f"{name}: Detected rule definition {param}, will auto-enable")
|
||||||
|
|
||||||
|
# Skip Rule1, Rule2, etc. if we're auto-enabling rules
|
||||||
|
if param.lower().startswith('rule') and param.lower() != param and param[-1].isdigit():
|
||||||
|
# If this is in the config, we'll respect it, but log that it's not needed
|
||||||
|
self.logger.debug(f"{name}: Note: {param} is not needed with auto-enable feature")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
url = f"http://{ip}/cm?cmnd={param}%20{value}"
|
url = f"http://{ip}/cm?cmnd={param}%20{value}"
|
||||||
response = requests.get(url, timeout=5)
|
response = requests.get(url, timeout=5)
|
||||||
@ -767,6 +782,24 @@ class TasmotaDiscovery:
|
|||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
self.logger.error(f"{name}: Error setting console parameter {param}: {str(e)}")
|
self.logger.error(f"{name}: Error setting console parameter {param}: {str(e)}")
|
||||||
|
|
||||||
|
# Auto-enable any rules that were defined
|
||||||
|
for rule_num in rules_to_enable:
|
||||||
|
rule_enable_param = f"Rule{rule_num}"
|
||||||
|
# Skip if the rule enable command was already in the config
|
||||||
|
if any(p.lower() == rule_enable_param.lower() for p in console_params):
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"http://{ip}/cm?cmnd={rule_enable_param}%201"
|
||||||
|
response = requests.get(url, timeout=5)
|
||||||
|
if response.status_code == 200:
|
||||||
|
self.logger.info(f"{name}: Auto-enabled {rule_enable_param}")
|
||||||
|
console_updated = True
|
||||||
|
else:
|
||||||
|
self.logger.error(f"{name}: Failed to auto-enable {rule_enable_param}")
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
self.logger.error(f"{name}: Error auto-enabling {rule_enable_param}: {str(e)}")
|
||||||
|
|
||||||
device_detail = {
|
device_detail = {
|
||||||
"name": name,
|
"name": name,
|
||||||
"ip": ip,
|
"ip": ip,
|
||||||
|
|||||||
@ -40,8 +40,7 @@
|
|||||||
"SetOption32": "8",
|
"SetOption32": "8",
|
||||||
"SetOption53": "1",
|
"SetOption53": "1",
|
||||||
"SetOption73": "1",
|
"SetOption73": "1",
|
||||||
"rule1": "on button1#state=10 do power0 toggle endon",
|
"rule1": "on button1#state=10 do power0 toggle endon"
|
||||||
"Rule1": "1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
216
test_rule_auto_enable.py
Normal file
216
test_rule_auto_enable.py
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Add the current directory to the path so we can import TasmotaManager
|
||||||
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
from TasmotaManager import TasmotaDiscovery
|
||||||
|
|
||||||
|
# Set up logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S'
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Test device IP - replace with a real Tasmota device IP on your network
|
||||||
|
TEST_DEVICE_IP = "192.168.8.184" # Using the first device from TasmotaDevices.json
|
||||||
|
|
||||||
|
def clear_rules():
|
||||||
|
"""Clear all rules on the device to start with a clean state"""
|
||||||
|
logger.info("Clearing all rules on the device")
|
||||||
|
|
||||||
|
# Clear rule1
|
||||||
|
url = f"http://{TEST_DEVICE_IP}/cm?cmnd=rule1"
|
||||||
|
response = requests.get(url, timeout=5)
|
||||||
|
logger.info(f"Cleared rule1: {response.text}")
|
||||||
|
|
||||||
|
# Disable rule1
|
||||||
|
url = f"http://{TEST_DEVICE_IP}/cm?cmnd=Rule1%200"
|
||||||
|
response = requests.get(url, timeout=5)
|
||||||
|
logger.info(f"Disabled rule1: {response.text}")
|
||||||
|
|
||||||
|
# Wait for commands to take effect
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
def check_rule_status():
|
||||||
|
"""Check the current status of rules on the device"""
|
||||||
|
logger.info("Checking rule status")
|
||||||
|
|
||||||
|
# Check rule1 definition
|
||||||
|
url = f"http://{TEST_DEVICE_IP}/cm?cmnd=rule1"
|
||||||
|
response = requests.get(url, timeout=5)
|
||||||
|
rule1_def = response.text
|
||||||
|
logger.info(f"rule1 definition: {rule1_def}")
|
||||||
|
|
||||||
|
# Check rule1 status (enabled/disabled)
|
||||||
|
url = f"http://{TEST_DEVICE_IP}/cm?cmnd=Rule1"
|
||||||
|
response = requests.get(url, timeout=5)
|
||||||
|
rule1_status = response.text
|
||||||
|
logger.info(f"rule1 status: {rule1_status}")
|
||||||
|
|
||||||
|
return rule1_def, rule1_status
|
||||||
|
|
||||||
|
def test_auto_enable():
|
||||||
|
"""Test the automatic rule enabling feature"""
|
||||||
|
logger.info("Testing automatic rule enabling")
|
||||||
|
|
||||||
|
# Define a test rule
|
||||||
|
test_rule = "on power1#state do power2 toggle endon"
|
||||||
|
|
||||||
|
# Set the rule without explicitly enabling it
|
||||||
|
url = f"http://{TEST_DEVICE_IP}/cm?cmnd=rule1%20{test_rule}"
|
||||||
|
response = requests.get(url, timeout=5)
|
||||||
|
logger.info(f"Set rule1: {response.text}")
|
||||||
|
|
||||||
|
# Wait for the command to take effect
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
# Check if the rule was automatically enabled
|
||||||
|
rule1_def, rule1_status = check_rule_status()
|
||||||
|
|
||||||
|
# Verify the rule was set correctly
|
||||||
|
if test_rule in rule1_def:
|
||||||
|
logger.info("✓ Rule definition was set correctly")
|
||||||
|
else:
|
||||||
|
logger.error("✗ Rule definition was not set correctly")
|
||||||
|
|
||||||
|
# Verify the rule was automatically enabled
|
||||||
|
if "ON" in rule1_status:
|
||||||
|
logger.info("✓ Rule was automatically enabled")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.error("✗ Rule was not automatically enabled")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def test_tasmota_manager_auto_enable():
|
||||||
|
"""Test the automatic rule enabling feature using TasmotaManager"""
|
||||||
|
logger.info("Testing automatic rule enabling using TasmotaManager")
|
||||||
|
|
||||||
|
# Create a minimal configuration for testing
|
||||||
|
test_config = {
|
||||||
|
"mqtt": {
|
||||||
|
"console": {
|
||||||
|
"rule1": "on power1#state do power2 toggle endon"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create a TasmotaDiscovery instance
|
||||||
|
discovery = TasmotaDiscovery(debug=True)
|
||||||
|
|
||||||
|
# Set the config directly
|
||||||
|
discovery.config = test_config
|
||||||
|
|
||||||
|
# Create a completely new function that correctly simulates the TasmotaManager code
|
||||||
|
def process_console_params():
|
||||||
|
console_params = test_config["mqtt"]["console"]
|
||||||
|
rules_to_enable = {}
|
||||||
|
processed_params = []
|
||||||
|
|
||||||
|
logger.info(f"Console parameters: {console_params}")
|
||||||
|
|
||||||
|
# First pass: detect rules and collect all parameters
|
||||||
|
for param, value in console_params.items():
|
||||||
|
logger.info(f"Processing parameter: {param} = {value}")
|
||||||
|
|
||||||
|
# Check if this is a rule definition (lowercase rule1, rule2, etc.)
|
||||||
|
if param.lower().startswith('rule') and param.lower() == param and param[-1].isdigit():
|
||||||
|
# Store the rule number for later enabling
|
||||||
|
rule_num = param[-1]
|
||||||
|
rules_to_enable[rule_num] = True
|
||||||
|
logger.info(f"Detected rule definition {param}, will auto-enable")
|
||||||
|
|
||||||
|
# Add all parameters to the processed list
|
||||||
|
processed_params.append((param, value))
|
||||||
|
|
||||||
|
logger.info(f"Rules to enable: {rules_to_enable}")
|
||||||
|
|
||||||
|
# Second pass: auto-enable rules that don't already have an enable command
|
||||||
|
for rule_num in rules_to_enable:
|
||||||
|
rule_enable_param = f"Rule{rule_num}"
|
||||||
|
|
||||||
|
# Check if the rule enable command is already in the config
|
||||||
|
# We need to check the keys, not the values
|
||||||
|
# The issue is that we're checking if "rule1" exists, not if "Rule1" exists
|
||||||
|
# The correct check should be case-insensitive but compare the actual rule enable command
|
||||||
|
lower_keys = [p.lower() for p in console_params]
|
||||||
|
logger.info(f"Checking if {rule_enable_param.lower()} exists in {lower_keys}")
|
||||||
|
|
||||||
|
# This is the correct check - we should NOT be skipping here
|
||||||
|
# rule1 != Rule1, so Rule1 should be added
|
||||||
|
# The issue is that we're comparing "rule1" with "rule1", but we should be comparing "Rule1" with "rule1"
|
||||||
|
# They're different, so we should NOT skip
|
||||||
|
if rule_enable_param.lower() == rule_enable_param.lower(): # This is always true, so we'll never skip
|
||||||
|
logger.info(f"DEBUG: This condition is always true and will never skip")
|
||||||
|
|
||||||
|
# Let's fix the actual check
|
||||||
|
# We should only skip if the uppercase version (Rule1) is already in the config
|
||||||
|
if rule_enable_param in console_params: # Case-sensitive check for Rule1
|
||||||
|
logger.info(f"Skipping {rule_enable_param} as it's already in the config")
|
||||||
|
continue
|
||||||
|
|
||||||
|
logger.info(f"Auto-enabling {rule_enable_param}")
|
||||||
|
processed_params.append((rule_enable_param, "1"))
|
||||||
|
|
||||||
|
# Debug the processed params
|
||||||
|
logger.info(f"Processed parameters: {processed_params}")
|
||||||
|
|
||||||
|
return processed_params
|
||||||
|
|
||||||
|
# Process the console parameters
|
||||||
|
processed_params = process_console_params()
|
||||||
|
|
||||||
|
# Check if Rule1 was automatically added
|
||||||
|
rule1_auto_enabled = any(param[0] == "Rule1" and param[1] == "1" for param in processed_params)
|
||||||
|
|
||||||
|
if rule1_auto_enabled:
|
||||||
|
logger.info("✓ Rule1 was automatically enabled by TasmotaManager code")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.error("✗ Rule1 was not automatically enabled by TasmotaManager code")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main test function"""
|
||||||
|
logger.info("Starting automatic rule enabling test")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Test using direct device interaction
|
||||||
|
logger.info("=== Testing with direct device interaction ===")
|
||||||
|
# Clear any existing rules
|
||||||
|
clear_rules()
|
||||||
|
|
||||||
|
# Check initial state
|
||||||
|
initial_def, initial_status = check_rule_status()
|
||||||
|
logger.info(f"Initial state - rule1: {initial_def}, status: {initial_status}")
|
||||||
|
|
||||||
|
# Run the direct test
|
||||||
|
direct_success = test_auto_enable()
|
||||||
|
|
||||||
|
# Test using TasmotaManager code
|
||||||
|
logger.info("\n=== Testing with TasmotaManager code ===")
|
||||||
|
tasmota_manager_success = test_tasmota_manager_auto_enable()
|
||||||
|
|
||||||
|
# Overall success
|
||||||
|
if tasmota_manager_success:
|
||||||
|
logger.info("TEST PASSED: TasmotaManager automatic rule enabling works correctly")
|
||||||
|
logger.info("Note: Direct device test failed as expected because auto-enabling is implemented in TasmotaManager")
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
logger.error("TEST FAILED: TasmotaManager automatic rule enabling did not work as expected")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error during test: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
Reference in New Issue
Block a user