mirror of
https://github.com/arendst/Tasmota.git
synced 2025-07-19 16:56:34 +00:00
Berry split tasmota-specific reference documentation (#23687)
This commit is contained in:
parent
08f2826e97
commit
722db4157d
@ -1,12 +1,12 @@
|
||||
# Berry Language Reference for Tasmota
|
||||
|
||||
Note: this file is supposed to use as a reference manual for Generative AI in a compact form. For Claude AI it costs ~10k tokens.
|
||||
Note: this file is supposed to use as a reference manual for Generative AI in a compact form. For Claude AI it costs ~6k tokens.
|
||||
|
||||
## Introduction
|
||||
|
||||
Berry is an ultra-lightweight, dynamically typed embedded scripting language designed for resource-constrained environments. The language primarily supports procedural programming, with additional support for object-oriented and functional programming paradigms. Berry's key design goal is to run efficiently on embedded devices with very limited memory, making the language highly streamlined while maintaining rich scripting capabilities.
|
||||
|
||||
**Tasmota Integration**: Berry is the next generation scripting for Tasmota, embedded by default in all ESP32 based firmwares (NOT supported on ESP8266). It is used for advanced scripting, superseding Rules, and enables building drivers, automations, and UI extensions.
|
||||
**Tasmota Integration**: Berry is integrated into Tasmota firmware. For Tasmota-specific features, see the companion document `BERRY_TASMOTA.md`.
|
||||
|
||||
## Basic Information
|
||||
|
||||
@ -972,711 +972,3 @@ assert(condition, "message") # Raises with custom message
|
||||
- Avoid creating unnecessary objects
|
||||
- Reuse buffers when processing large data
|
||||
- Use native functions for performance-critical code
|
||||
|
||||
## Tasmota-Specific Features
|
||||
|
||||
### Tasmota-Specific Modules
|
||||
|
||||
Beyond standard Berry modules, Tasmota provides additional modules:
|
||||
|
||||
#### `tasmota` - Core integration module (automatically imported)
|
||||
#### `light` - Light control (automatically imported)
|
||||
#### `mqtt` - MQTT operations (`import mqtt`)
|
||||
#### `webserver` - Web server extensions (`import webserver`)
|
||||
#### `gpio` - GPIO control (`import gpio`)
|
||||
#### `persist` - Data persistence (`import persist`)
|
||||
#### `path` - File system operations (`import path`)
|
||||
#### `energy` - Energy monitoring (automatically imported)
|
||||
#### `display` - Display driver integration (`import display`)
|
||||
#### `crypto` - Cryptographic functions (`import crypto`)
|
||||
#### `re` - Regular expressions (`import re`)
|
||||
#### `mdns` - mDNS/Bonjour support (`import mdns`)
|
||||
#### `ULP` - Ultra Low Power coprocessor (`import ULP`)
|
||||
#### `uuid` - UUID generation (`import uuid`)
|
||||
#### `crc` - CRC calculations (`import crc`)
|
||||
|
||||
### Tasmota Constants and Enums
|
||||
|
||||
```berry
|
||||
# GPIO constants (gpio module)
|
||||
gpio.INPUT, gpio.OUTPUT, gpio.PULLUP, gpio.PULLDOWN
|
||||
gpio.HIGH, gpio.LOW
|
||||
gpio.REL1, gpio.KEY1, gpio.LED1, gpio.I2C_SCL, gpio.I2C_SDA
|
||||
# ... many more GPIO function constants
|
||||
|
||||
# Serial constants
|
||||
serial.SERIAL_8N1, serial.SERIAL_7E1, etc.
|
||||
|
||||
# Webserver constants
|
||||
webserver.HTTP_GET, webserver.HTTP_POST, webserver.HTTP_OPTIONS, webserver.HTTP_ANY
|
||||
webserver.HTTP_OFF, webserver.HTTP_USER, webserver.HTTP_ADMIN, webserver.HTTP_MANAGER
|
||||
webserver.HTTP_MANAGER_RESET_ONLY
|
||||
webserver.BUTTON_MAIN, webserver.BUTTON_CONFIGURATION, webserver.BUTTON_INFORMATION
|
||||
webserver.BUTTON_MANAGEMENT, webserver.BUTTON_MODULE
|
||||
```
|
||||
|
||||
### Console and REPL
|
||||
|
||||
Access Berry console via *Configuration* → *Berry Scripting Console*. The console supports:
|
||||
- Multi-line input (press Enter twice or click "Run")
|
||||
- Command history (arrow keys)
|
||||
- Colorful syntax highlighting
|
||||
- Berry VM restart with `BrRestart` command
|
||||
|
||||
### File System and Loading
|
||||
|
||||
Berry files can be source (`.be`) or pre-compiled bytecode (`.bec`):
|
||||
|
||||
```berry
|
||||
load("filename") # Loads .be or .bec file
|
||||
tasmota.compile("file.be") # Compiles .be to .bec
|
||||
```
|
||||
|
||||
**Autostart**: Place `autoexec.be` in filesystem to run Berry code at boot.
|
||||
|
||||
### Tasmota Integration Functions
|
||||
|
||||
#### Core Tasmota Functions
|
||||
|
||||
```berry
|
||||
# System information
|
||||
tasmota.get_free_heap() # Free heap bytes
|
||||
tasmota.memory() # Memory stats map
|
||||
tasmota.arch() # Architecture: "esp32", "esp32s2", etc.
|
||||
tasmota.millis() # Milliseconds since boot
|
||||
tasmota.yield() # Give time to low-level functions
|
||||
tasmota.delay(ms) # Block execution for ms milliseconds
|
||||
|
||||
# Commands and responses
|
||||
tasmota.cmd("command") # Execute Tasmota command
|
||||
tasmota.resp_cmnd_done() # Respond "Done"
|
||||
tasmota.resp_cmnd_error() # Respond "Error"
|
||||
tasmota.resp_cmnd_str(msg) # Custom response string
|
||||
tasmota.resp_cmnd(json) # Custom JSON response
|
||||
|
||||
# Configuration
|
||||
tasmota.get_option(index) # Get SetOption value
|
||||
tasmota.read_sensors() # Get sensor JSON string
|
||||
tasmota.wifi() # WiFi connection info
|
||||
tasmota.eth() # Ethernet connection info
|
||||
```
|
||||
|
||||
#### Rules and Events
|
||||
|
||||
```berry
|
||||
# Add rules (similar to Tasmota Rules but more powerful)
|
||||
tasmota.add_rule("trigger", function)
|
||||
tasmota.add_rule(["trigger1", "trigger2"], function) # AND logic
|
||||
tasmota.remove_rule("trigger")
|
||||
|
||||
# Rule function signature
|
||||
def rule_function(value, trigger, msg)
|
||||
# value: trigger value (%value% equivalent)
|
||||
# trigger: full trigger string
|
||||
# msg: parsed JSON map or original string
|
||||
end
|
||||
|
||||
# Examples
|
||||
tasmota.add_rule("Dimmer>50", def() print("Bright!") end)
|
||||
tasmota.add_rule("ANALOG#A1>300", def(val) print("ADC:", val) end)
|
||||
```
|
||||
|
||||
#### Timers and Scheduling
|
||||
|
||||
```berry
|
||||
# Timers (50ms resolution)
|
||||
tasmota.set_timer(delay_ms, function)
|
||||
tasmota.remove_timer(id)
|
||||
tasmota.defer(function) # Run in next millisecond
|
||||
|
||||
# Cron scheduling
|
||||
tasmota.add_cron("*/15 * * * * *", function, "id")
|
||||
tasmota.remove_cron("id")
|
||||
tasmota.next_cron("id") # Next execution timestamp
|
||||
|
||||
# Time functions
|
||||
tasmota.rtc() # Current time info
|
||||
tasmota.time_dump(timestamp) # Decompose timestamp
|
||||
tasmota.time_str(timestamp) # ISO 8601 string
|
||||
tasmota.strftime(format, timestamp)
|
||||
tasmota.strptime(time_str, format)
|
||||
```
|
||||
|
||||
#### Device Control
|
||||
|
||||
```berry
|
||||
# Relays and Power
|
||||
tasmota.get_power() # Array of relay states
|
||||
tasmota.set_power(idx, state) # Set relay state
|
||||
|
||||
# Lights (use light module)
|
||||
light.get() # Current light status
|
||||
light.set({"power": true, "bri": 128, "hue": 120})
|
||||
|
||||
# Light attributes: power, bri (0-255), hue (0-360), sat (0-255),
|
||||
# ct (153-500), rgb (hex string), channels (array)
|
||||
```
|
||||
|
||||
#### Custom Commands
|
||||
|
||||
```berry
|
||||
# Add custom Tasmota commands
|
||||
def my_command(cmd, idx, payload, payload_json)
|
||||
# cmd: command name, idx: command index
|
||||
# payload: raw string, payload_json: parsed JSON
|
||||
tasmota.resp_cmnd_done()
|
||||
end
|
||||
|
||||
tasmota.add_cmd("MyCmd", my_command)
|
||||
tasmota.remove_cmd("MyCmd")
|
||||
```
|
||||
|
||||
### Tasmota Drivers
|
||||
|
||||
Create complete Tasmota drivers by implementing event methods:
|
||||
|
||||
```berry
|
||||
class MyDriver
|
||||
def every_second() # Called every second
|
||||
end
|
||||
|
||||
def every_50ms() # Called every 50ms
|
||||
end
|
||||
|
||||
def web_sensor() # Add to web UI
|
||||
tasmota.web_send("{s}Sensor{m}Value{e}")
|
||||
end
|
||||
|
||||
def json_append() # Add to JSON teleperiod
|
||||
tasmota.response_append(',"MySensor":{"Value":123}')
|
||||
end
|
||||
|
||||
def web_add_main_button() # Add button to main page
|
||||
import webserver
|
||||
webserver.content_send("<button onclick='la(\"&myaction=1\");'>My Button</button>")
|
||||
end
|
||||
|
||||
def button_pressed() # Handle button press
|
||||
end
|
||||
|
||||
def mqtt_data(topic, idx, data, databytes) # Handle MQTT
|
||||
end
|
||||
|
||||
def save_before_restart() # Before restart
|
||||
end
|
||||
end
|
||||
|
||||
# Register driver
|
||||
driver = MyDriver()
|
||||
tasmota.add_driver(driver)
|
||||
```
|
||||
|
||||
### Fast Loop
|
||||
|
||||
For near real-time events (200Hz, 5ms intervals):
|
||||
|
||||
```berry
|
||||
def fast_function()
|
||||
# High-frequency processing
|
||||
end
|
||||
|
||||
tasmota.add_fast_loop(fast_function)
|
||||
tasmota.remove_fast_loop(fast_function)
|
||||
```
|
||||
|
||||
### GPIO Control
|
||||
|
||||
```berry
|
||||
import gpio
|
||||
|
||||
# GPIO detection and control
|
||||
gpio.pin_used(gpio.REL1) # Check if GPIO is used
|
||||
gpio.pin(gpio.REL1) # Get physical GPIO number
|
||||
gpio.digital_write(pin, gpio.HIGH) # Set GPIO state
|
||||
gpio.digital_read(pin) # Read GPIO state
|
||||
gpio.pin_mode(pin, gpio.OUTPUT) # Set GPIO mode
|
||||
|
||||
# PWM control
|
||||
gpio.set_pwm(pin, duty, phase) # Set PWM value
|
||||
gpio.set_pwm_freq(pin, freq) # Set PWM frequency
|
||||
|
||||
# DAC (ESP32 GPIO 25-26, ESP32-S2 GPIO 17-18)
|
||||
gpio.dac_voltage(pin, voltage_mv) # Set DAC voltage
|
||||
|
||||
# Counters
|
||||
gpio.counter_read(counter) # Read counter value
|
||||
gpio.counter_set(counter, value) # Set counter value
|
||||
```
|
||||
|
||||
### I²C Communication
|
||||
|
||||
```berry
|
||||
# Use wire1 or wire2 for I²C buses
|
||||
wire1.scan() # Scan for devices
|
||||
wire1.detect(addr) # Check if device present
|
||||
wire1.read(addr, reg, size) # Read from device
|
||||
wire1.write(addr, reg, val, size) # Write to device
|
||||
wire1.read_bytes(addr, reg, size) # Read as bytes
|
||||
wire1.write_bytes(addr, reg, bytes) # Write bytes
|
||||
|
||||
# Find device on any bus
|
||||
wire = tasmota.wire_scan(addr, i2c_index)
|
||||
```
|
||||
|
||||
### MQTT Integration
|
||||
|
||||
```berry
|
||||
import mqtt
|
||||
|
||||
# MQTT operations
|
||||
mqtt.publish(topic, payload, retain)
|
||||
mqtt.subscribe(topic, function) # Subscribe with callback
|
||||
mqtt.unsubscribe(topic)
|
||||
mqtt.connected() # Check connection status
|
||||
|
||||
# Callback function signature
|
||||
def mqtt_callback(topic, idx, payload_s, payload_b)
|
||||
# topic: full topic, payload_s: string, payload_b: bytes
|
||||
return true # Return true if handled
|
||||
end
|
||||
```
|
||||
|
||||
### Web Server Extensions
|
||||
|
||||
```berry
|
||||
import webserver
|
||||
|
||||
# In driver's web_add_handler() method
|
||||
webserver.on("/my_page", def()
|
||||
webserver.content_send("<html>My Page</html>")
|
||||
end)
|
||||
|
||||
# Request handling
|
||||
webserver.has_arg("param") # Check parameter exists
|
||||
webserver.arg("param") # Get parameter value
|
||||
webserver.arg_size() # Number of parameters
|
||||
|
||||
# Response functions
|
||||
webserver.content_send(html) # Send HTML content
|
||||
webserver.content_button() # Standard button
|
||||
webserver.html_escape(str) # Escape HTML
|
||||
```
|
||||
|
||||
### Persistence
|
||||
|
||||
```berry
|
||||
import persist
|
||||
|
||||
# Automatic persistence to _persist.json
|
||||
persist.my_value = 123
|
||||
persist.save() # Force save to flash
|
||||
persist.has("key") # Check if key exists
|
||||
persist.remove("key") # Remove key
|
||||
persist.find("key", default) # Get with default
|
||||
```
|
||||
|
||||
### Network Clients
|
||||
|
||||
#### HTTP/HTTPS Client
|
||||
|
||||
```berry
|
||||
cl = webclient()
|
||||
cl.begin("https://example.com/api")
|
||||
cl.set_auth("user", "pass")
|
||||
cl.add_header("Content-Type", "application/json")
|
||||
|
||||
result = cl.GET() # or POST(payload)
|
||||
if result == 200
|
||||
response = cl.get_string()
|
||||
# or cl.write_file("filename") for binary
|
||||
end
|
||||
cl.close()
|
||||
```
|
||||
|
||||
#### TCP Client
|
||||
|
||||
```berry
|
||||
tcp = tcpclient()
|
||||
tcp.connect("192.168.1.100", 80)
|
||||
tcp.write("GET / HTTP/1.0\r\n\r\n")
|
||||
response = tcp.read()
|
||||
tcp.close()
|
||||
```
|
||||
|
||||
#### UDP Communication
|
||||
|
||||
```berry
|
||||
u = udp()
|
||||
u.begin("", 2000) # Listen on port 2000
|
||||
u.send("192.168.1.10", 2000, bytes("Hello"))
|
||||
|
||||
# Receive (polling)
|
||||
packet = u.read() # Returns bytes or nil
|
||||
if packet
|
||||
print("From:", u.remote_ip, u.remote_port)
|
||||
end
|
||||
```
|
||||
|
||||
### Serial Communication
|
||||
|
||||
```berry
|
||||
ser = serial(rx_gpio, tx_gpio, baud, serial.SERIAL_8N1)
|
||||
ser.write(bytes("Hello")) # Send data
|
||||
data = ser.read() # Read available data
|
||||
ser.available() # Check bytes available
|
||||
ser.flush() # Flush buffers
|
||||
ser.close() # Close port
|
||||
```
|
||||
|
||||
### Cryptography
|
||||
|
||||
```berry
|
||||
import crypto
|
||||
|
||||
# AES encryption
|
||||
aes = crypto.AES_GCM(key_32_bytes, iv_12_bytes)
|
||||
encrypted = aes.encrypt(plaintext)
|
||||
tag = aes.tag()
|
||||
|
||||
# Hashing
|
||||
crypto.SHA256().update(data).finish() # SHA256 hash
|
||||
crypto.MD5().update(data).finish() # MD5 hash
|
||||
|
||||
# HMAC
|
||||
crypto.HMAC_SHA256(key).update(data).finish()
|
||||
```
|
||||
|
||||
### File System Operations
|
||||
|
||||
```berry
|
||||
import path
|
||||
|
||||
path.exists("filename") # Check file exists
|
||||
path.listdir("/") # List directory
|
||||
path.remove("filename") # Delete file
|
||||
path.mkdir("dirname") # Create directory
|
||||
path.last_modified("file") # File timestamp
|
||||
```
|
||||
|
||||
### Regular Expressions
|
||||
|
||||
```berry
|
||||
import re
|
||||
|
||||
# Pattern matching
|
||||
matches = re.search("a.*?b(z+)", "aaaabbbzzz") # Returns matches array
|
||||
all_matches = re.searchall('<([a-zA-Z]+)>', html) # All matches
|
||||
parts = re.split('/', "path/to/file") # Split string
|
||||
|
||||
# Compiled patterns (faster for reuse)
|
||||
pattern = re.compilebytes("\\d+")
|
||||
matches = re.search(pattern, "abc123def")
|
||||
```
|
||||
|
||||
### Energy Monitoring
|
||||
|
||||
```berry
|
||||
# Read energy values
|
||||
energy.voltage # Main phase voltage
|
||||
energy.current # Main phase current
|
||||
energy.active_power # Active power (W)
|
||||
energy.total # Total energy (kWh)
|
||||
|
||||
# Multi-phase access
|
||||
energy.voltage_phases[0] # Phase 0 voltage
|
||||
energy.current_phases[1] # Phase 1 current
|
||||
|
||||
# Berry energy driver (with OPTION_A 9 GPIO)
|
||||
if energy.driver_enabled()
|
||||
energy.voltage = 240
|
||||
energy.current = 1.5
|
||||
energy.active_power = 360 # This drives energy calculation
|
||||
end
|
||||
```
|
||||
|
||||
### Display Integration
|
||||
|
||||
```berry
|
||||
import display
|
||||
|
||||
# Initialize display driver
|
||||
display.start(display_ini_string)
|
||||
display.started() # Check if initialized
|
||||
display.dimmer(50) # Set brightness 0-100
|
||||
display.driver_name() # Get driver name
|
||||
|
||||
# Touch screen updates
|
||||
display.touch_update(touches, x, y, gesture)
|
||||
```
|
||||
|
||||
### Advanced Features
|
||||
|
||||
#### ULP (Ultra Low Power) Coprocessor
|
||||
|
||||
```berry
|
||||
import ULP
|
||||
|
||||
ULP.wake_period(0, 500000) # Configure wake timer
|
||||
ULP.load(bytecode) # Load ULP program
|
||||
ULP.run() # Execute ULP program
|
||||
ULP.set_mem(addr, value) # Set RTC memory
|
||||
ULP.get_mem(addr) # Get RTC memory
|
||||
```
|
||||
|
||||
#### mDNS Support
|
||||
|
||||
```berry
|
||||
import mdns
|
||||
|
||||
mdns.start("hostname") # Start mDNS
|
||||
mdns.add_service("_http", "_tcp", 80, {"path": "/"})
|
||||
mdns.stop() # Stop mDNS
|
||||
```
|
||||
|
||||
### Error Handling Patterns
|
||||
|
||||
Many Tasmota functions return `nil` for errors rather than raising exceptions:
|
||||
|
||||
```berry
|
||||
# Check return values
|
||||
data = json.load(json_string)
|
||||
if data == nil
|
||||
print("Invalid JSON")
|
||||
end
|
||||
|
||||
# Wire operations
|
||||
result = wire1.read(addr, reg, 1)
|
||||
if result == nil
|
||||
print("I2C read failed")
|
||||
end
|
||||
```
|
||||
|
||||
### Best Practices for Tasmota
|
||||
|
||||
1. **Memory Management**: Use `tasmota.gc()` to monitor memory usage
|
||||
2. **Non-blocking**: Use timers instead of `delay()` for long waits
|
||||
3. **Error Handling**: Always check return values for `nil`
|
||||
4. **Persistence**: Use `persist` module for settings that survive reboots
|
||||
5. **Performance**: Use fast_loop sparingly, prefer regular driver events
|
||||
6. **Debugging**: Enable `#define USE_BERRY_DEBUG` for development
|
||||
|
||||
### Tasmota Extensions to Standard Modules
|
||||
|
||||
#### `bytes` class extensions
|
||||
```berry
|
||||
b = bytes("1122AA") # From hex string
|
||||
b = bytes(-8) # Fixed size buffer
|
||||
b.tohex() # To hex string
|
||||
b.tob64() # To base64
|
||||
b.fromhex("AABBCC") # Load from hex
|
||||
b.fromb64("SGVsbG8=") # Load from base64
|
||||
b.asstring() # To raw string
|
||||
```
|
||||
|
||||
## Common Tasmota Berry Patterns
|
||||
|
||||
### Simple Sensor Driver
|
||||
|
||||
```berry
|
||||
class MySensor
|
||||
var wire, addr
|
||||
|
||||
def init()
|
||||
self.addr = 0x48
|
||||
self.wire = tasmota.wire_scan(self.addr, 99) # I2C index 99
|
||||
if self.wire
|
||||
print("MySensor found on bus", self.wire.bus)
|
||||
end
|
||||
end
|
||||
|
||||
def every_second()
|
||||
if !self.wire return end
|
||||
var temp = self.wire.read(self.addr, 0x00, 2) # Read temperature
|
||||
self.temperature = temp / 256.0 # Convert to Celsius
|
||||
end
|
||||
|
||||
def web_sensor()
|
||||
if !self.wire return end
|
||||
import string
|
||||
var msg = string.format("{s}MySensor Temp{m}%.1f °C{e}", self.temperature)
|
||||
tasmota.web_send_decimal(msg)
|
||||
end
|
||||
|
||||
def json_append()
|
||||
if !self.wire return end
|
||||
import string
|
||||
var msg = string.format(',"MySensor":{"Temperature":%.1f}', self.temperature)
|
||||
tasmota.response_append(msg)
|
||||
end
|
||||
end
|
||||
|
||||
sensor = MySensor()
|
||||
tasmota.add_driver(sensor)
|
||||
```
|
||||
|
||||
### Custom Command with JSON Response
|
||||
|
||||
```berry
|
||||
def my_status_cmd(cmd, idx, payload, payload_json)
|
||||
import string
|
||||
var response = {
|
||||
"Uptime": tasmota.millis(),
|
||||
"FreeHeap": tasmota.get_free_heap(),
|
||||
"WiFi": tasmota.wifi("rssi")
|
||||
}
|
||||
tasmota.resp_cmnd(json.dump(response))
|
||||
end
|
||||
|
||||
tasmota.add_cmd("MyStatus", my_status_cmd)
|
||||
```
|
||||
|
||||
### MQTT Automation
|
||||
|
||||
```berry
|
||||
import mqtt
|
||||
|
||||
def handle_sensor_data(topic, idx, payload_s, payload_b)
|
||||
var data = json.load(payload_s)
|
||||
if data && data.find("temperature")
|
||||
var temp = data["temperature"]
|
||||
if temp > 25
|
||||
tasmota.cmd("Power1 ON") # Turn on fan
|
||||
elif temp < 20
|
||||
tasmota.cmd("Power1 OFF") # Turn off fan
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
mqtt.subscribe("sensors/+/temperature", handle_sensor_data)
|
||||
```
|
||||
|
||||
### Web UI Button with Action
|
||||
|
||||
```berry
|
||||
class WebButton
|
||||
def web_add_main_button()
|
||||
import webserver
|
||||
webserver.content_send("<p><button onclick='la(\"&toggle_led=1\");'>Toggle LED</button></p>")
|
||||
end
|
||||
|
||||
def web_sensor()
|
||||
import webserver
|
||||
if webserver.has_arg("toggle_led")
|
||||
# Toggle GPIO2 (built-in LED on many ESP32 boards)
|
||||
var pin = 2
|
||||
var current = gpio.digital_read(pin)
|
||||
gpio.digital_write(pin, !current)
|
||||
print("LED toggled to", !current)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
button = WebButton()
|
||||
tasmota.add_driver(button)
|
||||
```
|
||||
|
||||
### Scheduled Task with Persistence
|
||||
|
||||
```berry
|
||||
import persist
|
||||
|
||||
class ScheduledTask
|
||||
def init()
|
||||
if !persist.has("task_count")
|
||||
persist.task_count = 0
|
||||
end
|
||||
# Run every 5 minutes
|
||||
tasmota.add_cron("0 */5 * * * *", /-> self.run_task(), "my_task")
|
||||
end
|
||||
|
||||
def run_task()
|
||||
persist.task_count += 1
|
||||
print("Task executed", persist.task_count, "times")
|
||||
|
||||
# Do something useful
|
||||
var sensors = tasmota.read_sensors()
|
||||
print("Current sensors:", sensors)
|
||||
|
||||
persist.save() # Save counter to flash
|
||||
end
|
||||
end
|
||||
|
||||
task = ScheduledTask()
|
||||
```
|
||||
|
||||
### HTTP API Client
|
||||
|
||||
```berry
|
||||
class WeatherAPI
|
||||
var api_key, city
|
||||
|
||||
def init(key, city_name)
|
||||
self.api_key = key
|
||||
self.city = city_name
|
||||
tasmota.add_cron("0 0 * * * *", /-> self.fetch_weather(), "weather")
|
||||
end
|
||||
|
||||
def fetch_weather()
|
||||
var cl = webclient()
|
||||
var url = f"http://api.openweathermap.org/data/2.5/weather?q={self.city}&appid={self.api_key}"
|
||||
|
||||
cl.begin(url)
|
||||
var result = cl.GET()
|
||||
|
||||
if result == 200
|
||||
var response = cl.get_string()
|
||||
var data = json.load(response)
|
||||
if data
|
||||
var temp = data["main"]["temp"] - 273.15 # Kelvin to Celsius
|
||||
print(f"Weather in {self.city}: {temp:.1f}°C")
|
||||
|
||||
# Store in global for other scripts to use
|
||||
import global
|
||||
global.weather_temp = temp
|
||||
end
|
||||
end
|
||||
cl.close()
|
||||
end
|
||||
end
|
||||
|
||||
# weather = WeatherAPI("your_api_key", "London")
|
||||
```
|
||||
|
||||
### Rule-based Automation
|
||||
|
||||
```berry
|
||||
# Advanced rule that combines multiple conditions
|
||||
tasmota.add_rule(["ANALOG#A0>500", "Switch1#State=1"],
|
||||
def(values, triggers)
|
||||
print("Both conditions met:")
|
||||
print("ADC value:", values[0])
|
||||
print("Switch state:", values[1])
|
||||
tasmota.cmd("Power2 ON") # Activate something
|
||||
end
|
||||
)
|
||||
|
||||
# Time-based rule
|
||||
tasmota.add_rule("Time#Minute=30",
|
||||
def()
|
||||
if tasmota.rtc()["hour"] == 18 # 6:30 PM
|
||||
tasmota.cmd("Dimmer 20") # Dim lights for evening
|
||||
end
|
||||
end
|
||||
)
|
||||
```
|
||||
|
||||
## Best Practices and Tips
|
||||
|
||||
1. **Always check for nil returns** from Tasmota functions
|
||||
2. **Use timers instead of delay()** to avoid blocking Tasmota
|
||||
3. **Implement proper error handling** in I²C and network operations
|
||||
4. **Use persist module** for settings that should survive reboots
|
||||
5. **Test memory usage** with `tasmota.gc()` during development
|
||||
6. **Use fast_loop sparingly** - it runs 200 times per second
|
||||
7. **Prefer driver events** over polling when possible
|
||||
8. **Use f-strings** for readable string formatting
|
||||
9. **Import modules only when needed** to save memory
|
||||
10. **Use `tasmota.wire_scan()`** instead of manual I²C bus detection
|
||||
|
||||
|
||||
|
717
.doc_for_ai/BERRY_TASMOTA.md
Normal file
717
.doc_for_ai/BERRY_TASMOTA.md
Normal file
@ -0,0 +1,717 @@
|
||||
# Berry for Tasmota
|
||||
|
||||
This document covers Tasmota-specific Berry features and extensions, complementing the general Berry language reference.
|
||||
|
||||
## Introduction
|
||||
|
||||
Berry is the next generation scripting language for Tasmota, embedded by default in all ESP32 based firmwares (NOT supported on ESP8266). It is used for advanced scripting, superseding Rules, and enables building drivers, automations, and UI extensions.
|
||||
|
||||
## Tasmota-Specific Modules
|
||||
|
||||
Beyond standard Berry modules, Tasmota provides additional modules:
|
||||
|
||||
| Module | Description | Import |
|
||||
|--------|-------------|--------|
|
||||
| `tasmota` | Core integration module | Automatically imported |
|
||||
| `light` | Light control | Automatically imported |
|
||||
| `mqtt` | MQTT operations | `import mqtt` |
|
||||
| `webserver` | Web server extensions | `import webserver` |
|
||||
| `gpio` | GPIO control | `import gpio` |
|
||||
| `persist` | Data persistence | `import persist` |
|
||||
| `path` | File system operations | `import path` |
|
||||
| `energy` | Energy monitoring | Automatically imported |
|
||||
| `display` | Display driver integration | `import display` |
|
||||
| `crypto` | Cryptographic functions | `import crypto` |
|
||||
| `re` | Regular expressions | `import re` |
|
||||
| `mdns` | mDNS/Bonjour support | `import mdns` |
|
||||
| `ULP` | Ultra Low Power coprocessor | `import ULP` |
|
||||
| `uuid` | UUID generation | `import uuid` |
|
||||
| `crc` | CRC calculations | `import crc` |
|
||||
|
||||
## Additional Resources
|
||||
|
||||
For Tasmota-specific Berry features and extensions, please refer to the companion document `BERRY_TASMOTA.md`.
|
||||
|
||||
### Tasmota Constants and Enums
|
||||
|
||||
```berry
|
||||
# GPIO constants (gpio module)
|
||||
gpio.INPUT, gpio.OUTPUT, gpio.PULLUP, gpio.PULLDOWN
|
||||
gpio.HIGH, gpio.LOW
|
||||
gpio.REL1, gpio.KEY1, gpio.LED1, gpio.I2C_SCL, gpio.I2C_SDA
|
||||
# ... many more GPIO function constants
|
||||
|
||||
# Serial constants
|
||||
serial.SERIAL_8N1, serial.SERIAL_7E1, etc.
|
||||
|
||||
# Webserver constants
|
||||
webserver.HTTP_GET, webserver.HTTP_POST, webserver.HTTP_OPTIONS, webserver.HTTP_ANY
|
||||
webserver.HTTP_OFF, webserver.HTTP_USER, webserver.HTTP_ADMIN, webserver.HTTP_MANAGER
|
||||
webserver.HTTP_MANAGER_RESET_ONLY
|
||||
webserver.BUTTON_MAIN, webserver.BUTTON_CONFIGURATION, webserver.BUTTON_INFORMATION
|
||||
webserver.BUTTON_MANAGEMENT, webserver.BUTTON_MODULE
|
||||
```
|
||||
|
||||
### Console and REPL
|
||||
|
||||
Access Berry console via *Configuration* → *Berry Scripting Console*. The console supports:
|
||||
- Multi-line input (press Enter twice or click "Run")
|
||||
- Command history (arrow keys)
|
||||
- Colorful syntax highlighting
|
||||
- Berry VM restart with `BrRestart` command
|
||||
|
||||
### File System and Loading
|
||||
|
||||
Berry files can be source (`.be`) or pre-compiled bytecode (`.bec`):
|
||||
|
||||
```berry
|
||||
load("filename") # Loads .be or .bec file
|
||||
tasmota.compile("file.be") # Compiles .be to .bec
|
||||
```
|
||||
|
||||
**Autostart**: Place `autoexec.be` in filesystem to run Berry code at boot.
|
||||
|
||||
### Tasmota Integration Functions
|
||||
|
||||
#### Core Tasmota Functions
|
||||
|
||||
```berry
|
||||
# System information
|
||||
tasmota.get_free_heap() # Free heap bytes
|
||||
tasmota.memory() # Memory stats map
|
||||
tasmota.arch() # Architecture: "esp32", "esp32s2", etc.
|
||||
tasmota.millis() # Milliseconds since boot
|
||||
tasmota.yield() # Give time to low-level functions
|
||||
tasmota.delay(ms) # Block execution for ms milliseconds
|
||||
|
||||
# Commands and responses
|
||||
tasmota.cmd("command") # Execute Tasmota command
|
||||
tasmota.resp_cmnd_done() # Respond "Done"
|
||||
tasmota.resp_cmnd_error() # Respond "Error"
|
||||
tasmota.resp_cmnd_str(msg) # Custom response string
|
||||
tasmota.resp_cmnd(json) # Custom JSON response
|
||||
|
||||
# Configuration
|
||||
tasmota.get_option(index) # Get SetOption value
|
||||
tasmota.read_sensors() # Get sensor JSON string
|
||||
tasmota.wifi() # WiFi connection info
|
||||
tasmota.eth() # Ethernet connection info
|
||||
```
|
||||
|
||||
#### Rules and Events
|
||||
|
||||
```berry
|
||||
# Add rules (similar to Tasmota Rules but more powerful)
|
||||
tasmota.add_rule("trigger", function)
|
||||
tasmota.add_rule(["trigger1", "trigger2"], function) # AND logic
|
||||
tasmota.remove_rule("trigger")
|
||||
|
||||
# Rule function signature
|
||||
def rule_function(value, trigger, msg)
|
||||
# value: trigger value (%value% equivalent)
|
||||
# trigger: full trigger string
|
||||
# msg: parsed JSON map or original string
|
||||
end
|
||||
|
||||
# Examples
|
||||
tasmota.add_rule("Dimmer>50", def() print("Bright!") end)
|
||||
tasmota.add_rule("ANALOG#A1>300", def(val) print("ADC:", val) end)
|
||||
```
|
||||
|
||||
#### Timers and Scheduling
|
||||
|
||||
```berry
|
||||
# Timers (50ms resolution)
|
||||
tasmota.set_timer(delay_ms, function)
|
||||
tasmota.remove_timer(id)
|
||||
tasmota.defer(function) # Run in next millisecond
|
||||
|
||||
# Cron scheduling
|
||||
tasmota.add_cron("*/15 * * * * *", function, "id")
|
||||
tasmota.remove_cron("id")
|
||||
tasmota.next_cron("id") # Next execution timestamp
|
||||
|
||||
# Time functions
|
||||
tasmota.rtc() # Current time info
|
||||
tasmota.time_dump(timestamp) # Decompose timestamp
|
||||
tasmota.time_str(timestamp) # ISO 8601 string
|
||||
tasmota.strftime(format, timestamp)
|
||||
tasmota.strptime(time_str, format)
|
||||
```
|
||||
|
||||
#### Device Control
|
||||
|
||||
```berry
|
||||
# Relays and Power
|
||||
tasmota.get_power() # Array of relay states
|
||||
tasmota.set_power(idx, state) # Set relay state
|
||||
|
||||
# Lights (use light module)
|
||||
light.get() # Current light status
|
||||
light.set({"power": true, "bri": 128, "hue": 120})
|
||||
|
||||
# Light attributes: power, bri (0-255), hue (0-360), sat (0-255),
|
||||
# ct (153-500), rgb (hex string), channels (array)
|
||||
```
|
||||
|
||||
#### Custom Commands
|
||||
|
||||
```berry
|
||||
# Add custom Tasmota commands
|
||||
def my_command(cmd, idx, payload, payload_json)
|
||||
# cmd: command name, idx: command index
|
||||
# payload: raw string, payload_json: parsed JSON
|
||||
tasmota.resp_cmnd_done()
|
||||
end
|
||||
|
||||
tasmota.add_cmd("MyCmd", my_command)
|
||||
tasmota.remove_cmd("MyCmd")
|
||||
```
|
||||
|
||||
### Tasmota Drivers
|
||||
|
||||
Create complete Tasmota drivers by implementing event methods:
|
||||
|
||||
```berry
|
||||
class MyDriver
|
||||
def every_second() # Called every second
|
||||
end
|
||||
|
||||
def every_50ms() # Called every 50ms
|
||||
end
|
||||
|
||||
def web_sensor() # Add to web UI
|
||||
tasmota.web_send("{s}Sensor{m}Value{e}")
|
||||
end
|
||||
|
||||
def json_append() # Add to JSON teleperiod
|
||||
tasmota.response_append(',"MySensor":{"Value":123}')
|
||||
end
|
||||
|
||||
def web_add_main_button() # Add button to main page
|
||||
import webserver
|
||||
webserver.content_send("<button onclick='la(\"&myaction=1\");'>My Button</button>")
|
||||
end
|
||||
|
||||
def button_pressed() # Handle button press
|
||||
end
|
||||
|
||||
def mqtt_data(topic, idx, data, databytes) # Handle MQTT
|
||||
end
|
||||
|
||||
def save_before_restart() # Before restart
|
||||
end
|
||||
end
|
||||
|
||||
# Register driver
|
||||
driver = MyDriver()
|
||||
tasmota.add_driver(driver)
|
||||
```
|
||||
|
||||
### Fast Loop
|
||||
|
||||
For near real-time events (200Hz, 5ms intervals):
|
||||
|
||||
```berry
|
||||
def fast_function()
|
||||
# High-frequency processing
|
||||
end
|
||||
|
||||
tasmota.add_fast_loop(fast_function)
|
||||
tasmota.remove_fast_loop(fast_function)
|
||||
```
|
||||
|
||||
### GPIO Control
|
||||
|
||||
```berry
|
||||
import gpio
|
||||
|
||||
# GPIO detection and control
|
||||
gpio.pin_used(gpio.REL1) # Check if GPIO is used
|
||||
gpio.pin(gpio.REL1) # Get physical GPIO number
|
||||
gpio.digital_write(pin, gpio.HIGH) # Set GPIO state
|
||||
gpio.digital_read(pin) # Read GPIO state
|
||||
gpio.pin_mode(pin, gpio.OUTPUT) # Set GPIO mode
|
||||
|
||||
# PWM control
|
||||
gpio.set_pwm(pin, duty, phase) # Set PWM value
|
||||
gpio.set_pwm_freq(pin, freq) # Set PWM frequency
|
||||
|
||||
# DAC (ESP32 GPIO 25-26, ESP32-S2 GPIO 17-18)
|
||||
gpio.dac_voltage(pin, voltage_mv) # Set DAC voltage
|
||||
|
||||
# Counters
|
||||
gpio.counter_read(counter) # Read counter value
|
||||
gpio.counter_set(counter, value) # Set counter value
|
||||
```
|
||||
|
||||
### I²C Communication
|
||||
|
||||
```berry
|
||||
# Use wire1 or wire2 for I²C buses
|
||||
wire1.scan() # Scan for devices
|
||||
wire1.detect(addr) # Check if device present
|
||||
wire1.read(addr, reg, size) # Read from device
|
||||
wire1.write(addr, reg, val, size) # Write to device
|
||||
wire1.read_bytes(addr, reg, size) # Read as bytes
|
||||
wire1.write_bytes(addr, reg, bytes) # Write bytes
|
||||
|
||||
# Find device on any bus
|
||||
wire = tasmota.wire_scan(addr, i2c_index)
|
||||
```
|
||||
|
||||
### MQTT Integration
|
||||
|
||||
```berry
|
||||
import mqtt
|
||||
|
||||
# MQTT operations
|
||||
mqtt.publish(topic, payload, retain)
|
||||
mqtt.subscribe(topic, function) # Subscribe with callback
|
||||
mqtt.unsubscribe(topic)
|
||||
mqtt.connected() # Check connection status
|
||||
|
||||
# Callback function signature
|
||||
def mqtt_callback(topic, idx, payload_s, payload_b)
|
||||
# topic: full topic, payload_s: string, payload_b: bytes
|
||||
return true # Return true if handled
|
||||
end
|
||||
```
|
||||
|
||||
### Web Server Extensions
|
||||
|
||||
```berry
|
||||
import webserver
|
||||
|
||||
# In driver's web_add_handler() method
|
||||
webserver.on("/my_page", def()
|
||||
webserver.content_send("<html>My Page</html>")
|
||||
end)
|
||||
|
||||
# Request handling
|
||||
webserver.has_arg("param") # Check parameter exists
|
||||
webserver.arg("param") # Get parameter value
|
||||
webserver.arg_size() # Number of parameters
|
||||
|
||||
# Response functions
|
||||
webserver.content_send(html) # Send HTML content
|
||||
webserver.content_button() # Standard button
|
||||
webserver.html_escape(str) # Escape HTML
|
||||
```
|
||||
|
||||
### Persistence
|
||||
|
||||
```berry
|
||||
import persist
|
||||
|
||||
# Automatic persistence to _persist.json
|
||||
persist.my_value = 123
|
||||
persist.save() # Force save to flash
|
||||
persist.has("key") # Check if key exists
|
||||
persist.remove("key") # Remove key
|
||||
persist.find("key", default) # Get with default
|
||||
```
|
||||
|
||||
### Network Clients
|
||||
|
||||
#### HTTP/HTTPS Client
|
||||
|
||||
```berry
|
||||
cl = webclient()
|
||||
cl.begin("https://example.com/api")
|
||||
cl.set_auth("user", "pass")
|
||||
cl.add_header("Content-Type", "application/json")
|
||||
|
||||
result = cl.GET() # or POST(payload)
|
||||
if result == 200
|
||||
response = cl.get_string()
|
||||
# or cl.write_file("filename") for binary
|
||||
end
|
||||
cl.close()
|
||||
```
|
||||
|
||||
#### TCP Client
|
||||
|
||||
```berry
|
||||
tcp = tcpclient()
|
||||
tcp.connect("192.168.1.100", 80)
|
||||
tcp.write("GET / HTTP/1.0\r\n\r\n")
|
||||
response = tcp.read()
|
||||
tcp.close()
|
||||
```
|
||||
|
||||
#### UDP Communication
|
||||
|
||||
```berry
|
||||
u = udp()
|
||||
u.begin("", 2000) # Listen on port 2000
|
||||
u.send("192.168.1.10", 2000, bytes("Hello"))
|
||||
|
||||
# Receive (polling)
|
||||
packet = u.read() # Returns bytes or nil
|
||||
if packet
|
||||
print("From:", u.remote_ip, u.remote_port)
|
||||
end
|
||||
```
|
||||
|
||||
### Serial Communication
|
||||
|
||||
```berry
|
||||
ser = serial(rx_gpio, tx_gpio, baud, serial.SERIAL_8N1)
|
||||
ser.write(bytes("Hello")) # Send data
|
||||
data = ser.read() # Read available data
|
||||
ser.available() # Check bytes available
|
||||
ser.flush() # Flush buffers
|
||||
ser.close() # Close port
|
||||
```
|
||||
|
||||
### Cryptography
|
||||
|
||||
```berry
|
||||
import crypto
|
||||
|
||||
# AES encryption
|
||||
aes = crypto.AES_GCM(key_32_bytes, iv_12_bytes)
|
||||
encrypted = aes.encrypt(plaintext)
|
||||
tag = aes.tag()
|
||||
|
||||
# Hashing
|
||||
crypto.SHA256().update(data).finish() # SHA256 hash
|
||||
crypto.MD5().update(data).finish() # MD5 hash
|
||||
|
||||
# HMAC
|
||||
crypto.HMAC_SHA256(key).update(data).finish()
|
||||
```
|
||||
|
||||
### File System Operations
|
||||
|
||||
```berry
|
||||
import path
|
||||
|
||||
path.exists("filename") # Check file exists
|
||||
path.listdir("/") # List directory
|
||||
path.remove("filename") # Delete file
|
||||
path.mkdir("dirname") # Create directory
|
||||
path.last_modified("file") # File timestamp
|
||||
```
|
||||
|
||||
### Regular Expressions
|
||||
|
||||
```berry
|
||||
import re
|
||||
|
||||
# Pattern matching
|
||||
matches = re.search("a.*?b(z+)", "aaaabbbzzz") # Returns matches array
|
||||
all_matches = re.searchall('<([a-zA-Z]+)>', html) # All matches
|
||||
parts = re.split('/', "path/to/file") # Split string
|
||||
|
||||
# Compiled patterns (faster for reuse)
|
||||
pattern = re.compilebytes("\\d+")
|
||||
matches = re.search(pattern, "abc123def")
|
||||
```
|
||||
|
||||
### Energy Monitoring
|
||||
|
||||
```berry
|
||||
# Read energy values
|
||||
energy.voltage # Main phase voltage
|
||||
energy.current # Main phase current
|
||||
energy.active_power # Active power (W)
|
||||
energy.total # Total energy (kWh)
|
||||
|
||||
# Multi-phase access
|
||||
energy.voltage_phases[0] # Phase 0 voltage
|
||||
energy.current_phases[1] # Phase 1 current
|
||||
|
||||
# Berry energy driver (with OPTION_A 9 GPIO)
|
||||
if energy.driver_enabled()
|
||||
energy.voltage = 240
|
||||
energy.current = 1.5
|
||||
energy.active_power = 360 # This drives energy calculation
|
||||
end
|
||||
```
|
||||
|
||||
### Display Integration
|
||||
|
||||
```berry
|
||||
import display
|
||||
|
||||
# Initialize display driver
|
||||
display.start(display_ini_string)
|
||||
display.started() # Check if initialized
|
||||
display.dimmer(50) # Set brightness 0-100
|
||||
display.driver_name() # Get driver name
|
||||
|
||||
# Touch screen updates
|
||||
display.touch_update(touches, x, y, gesture)
|
||||
```
|
||||
|
||||
### Advanced Features
|
||||
|
||||
#### ULP (Ultra Low Power) Coprocessor
|
||||
|
||||
```berry
|
||||
import ULP
|
||||
|
||||
ULP.wake_period(0, 500000) # Configure wake timer
|
||||
ULP.load(bytecode) # Load ULP program
|
||||
ULP.run() # Execute ULP program
|
||||
ULP.set_mem(addr, value) # Set RTC memory
|
||||
ULP.get_mem(addr) # Get RTC memory
|
||||
```
|
||||
|
||||
#### mDNS Support
|
||||
|
||||
```berry
|
||||
import mdns
|
||||
|
||||
mdns.start("hostname") # Start mDNS
|
||||
mdns.add_service("_http", "_tcp", 80, {"path": "/"})
|
||||
mdns.stop() # Stop mDNS
|
||||
```
|
||||
|
||||
### Error Handling Patterns
|
||||
|
||||
Many Tasmota functions return `nil` for errors rather than raising exceptions:
|
||||
|
||||
```berry
|
||||
# Check return values
|
||||
data = json.load(json_string)
|
||||
if data == nil
|
||||
print("Invalid JSON")
|
||||
end
|
||||
|
||||
# Wire operations
|
||||
result = wire1.read(addr, reg, 1)
|
||||
if result == nil
|
||||
print("I2C read failed")
|
||||
end
|
||||
```
|
||||
|
||||
### Best Practices for Tasmota
|
||||
|
||||
1. **Memory Management**: Use `tasmota.gc()` to monitor memory usage
|
||||
2. **Non-blocking**: Use timers instead of `delay()` for long waits
|
||||
3. **Error Handling**: Always check return values for `nil`
|
||||
4. **Persistence**: Use `persist` module for settings that survive reboots
|
||||
5. **Performance**: Use fast_loop sparingly, prefer regular driver events
|
||||
6. **Debugging**: Enable `#define USE_BERRY_DEBUG` for development
|
||||
|
||||
### Tasmota Extensions to Standard Modules
|
||||
|
||||
#### `bytes` class extensions
|
||||
```berry
|
||||
b = bytes("1122AA") # From hex string
|
||||
b = bytes(-8) # Fixed size buffer
|
||||
b.tohex() # To hex string
|
||||
b.tob64() # To base64
|
||||
b.fromhex("AABBCC") # Load from hex
|
||||
b.fromb64("SGVsbG8=") # Load from base64
|
||||
b.asstring() # To raw string
|
||||
```
|
||||
|
||||
## Common Tasmota Berry Patterns
|
||||
|
||||
### Simple Sensor Driver
|
||||
|
||||
```berry
|
||||
class MySensor
|
||||
var wire, addr
|
||||
|
||||
def init()
|
||||
self.addr = 0x48
|
||||
self.wire = tasmota.wire_scan(self.addr, 99) # I2C index 99
|
||||
if self.wire
|
||||
print("MySensor found on bus", self.wire.bus)
|
||||
end
|
||||
end
|
||||
|
||||
def every_second()
|
||||
if !self.wire return end
|
||||
var temp = self.wire.read(self.addr, 0x00, 2) # Read temperature
|
||||
self.temperature = temp / 256.0 # Convert to Celsius
|
||||
end
|
||||
|
||||
def web_sensor()
|
||||
if !self.wire return end
|
||||
import string
|
||||
var msg = string.format("{s}MySensor Temp{m}%.1f °C{e}", self.temperature)
|
||||
tasmota.web_send_decimal(msg)
|
||||
end
|
||||
|
||||
def json_append()
|
||||
if !self.wire return end
|
||||
import string
|
||||
var msg = string.format(',"MySensor":{"Temperature":%.1f}', self.temperature)
|
||||
tasmota.response_append(msg)
|
||||
end
|
||||
end
|
||||
|
||||
sensor = MySensor()
|
||||
tasmota.add_driver(sensor)
|
||||
```
|
||||
|
||||
### Custom Command with JSON Response
|
||||
|
||||
```berry
|
||||
def my_status_cmd(cmd, idx, payload, payload_json)
|
||||
import string
|
||||
var response = {
|
||||
"Uptime": tasmota.millis(),
|
||||
"FreeHeap": tasmota.get_free_heap(),
|
||||
"WiFi": tasmota.wifi("rssi")
|
||||
}
|
||||
tasmota.resp_cmnd(json.dump(response))
|
||||
end
|
||||
|
||||
tasmota.add_cmd("MyStatus", my_status_cmd)
|
||||
```
|
||||
|
||||
### MQTT Automation
|
||||
|
||||
```berry
|
||||
import mqtt
|
||||
|
||||
def handle_sensor_data(topic, idx, payload_s, payload_b)
|
||||
var data = json.load(payload_s)
|
||||
if data && data.find("temperature")
|
||||
var temp = data["temperature"]
|
||||
if temp > 25
|
||||
tasmota.cmd("Power1 ON") # Turn on fan
|
||||
elif temp < 20
|
||||
tasmota.cmd("Power1 OFF") # Turn off fan
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
mqtt.subscribe("sensors/+/temperature", handle_sensor_data)
|
||||
```
|
||||
|
||||
### Web UI Button with Action
|
||||
|
||||
```berry
|
||||
class WebButton
|
||||
def web_add_main_button()
|
||||
import webserver
|
||||
webserver.content_send("<p><button onclick='la(\"&toggle_led=1\");'>Toggle LED</button></p>")
|
||||
end
|
||||
|
||||
def web_sensor()
|
||||
import webserver
|
||||
if webserver.has_arg("toggle_led")
|
||||
# Toggle GPIO2 (built-in LED on many ESP32 boards)
|
||||
var pin = 2
|
||||
var current = gpio.digital_read(pin)
|
||||
gpio.digital_write(pin, !current)
|
||||
print("LED toggled to", !current)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
button = WebButton()
|
||||
tasmota.add_driver(button)
|
||||
```
|
||||
|
||||
### Scheduled Task with Persistence
|
||||
|
||||
```berry
|
||||
import persist
|
||||
|
||||
class ScheduledTask
|
||||
def init()
|
||||
if !persist.has("task_count")
|
||||
persist.task_count = 0
|
||||
end
|
||||
# Run every 5 minutes
|
||||
tasmota.add_cron("0 */5 * * * *", /-> self.run_task(), "my_task")
|
||||
end
|
||||
|
||||
def run_task()
|
||||
persist.task_count += 1
|
||||
print("Task executed", persist.task_count, "times")
|
||||
|
||||
# Do something useful
|
||||
var sensors = tasmota.read_sensors()
|
||||
print("Current sensors:", sensors)
|
||||
|
||||
persist.save() # Save counter to flash
|
||||
end
|
||||
end
|
||||
|
||||
task = ScheduledTask()
|
||||
```
|
||||
|
||||
### HTTP API Client
|
||||
|
||||
```berry
|
||||
class WeatherAPI
|
||||
var api_key, city
|
||||
|
||||
def init(key, city_name)
|
||||
self.api_key = key
|
||||
self.city = city_name
|
||||
tasmota.add_cron("0 0 * * * *", /-> self.fetch_weather(), "weather")
|
||||
end
|
||||
|
||||
def fetch_weather()
|
||||
var cl = webclient()
|
||||
var url = f"http://api.openweathermap.org/data/2.5/weather?q={self.city}&appid={self.api_key}"
|
||||
|
||||
cl.begin(url)
|
||||
var result = cl.GET()
|
||||
|
||||
if result == 200
|
||||
var response = cl.get_string()
|
||||
var data = json.load(response)
|
||||
if data
|
||||
var temp = data["main"]["temp"] - 273.15 # Kelvin to Celsius
|
||||
print(f"Weather in {self.city}: {temp:.1f}°C")
|
||||
|
||||
# Store in global for other scripts to use
|
||||
import global
|
||||
global.weather_temp = temp
|
||||
end
|
||||
end
|
||||
cl.close()
|
||||
end
|
||||
end
|
||||
|
||||
# weather = WeatherAPI("your_api_key", "London")
|
||||
```
|
||||
|
||||
### Rule-based Automation
|
||||
|
||||
```berry
|
||||
# Advanced rule that combines multiple conditions
|
||||
tasmota.add_rule(["ANALOG#A0>500", "Switch1#State=1"],
|
||||
def(values, triggers)
|
||||
print("Both conditions met:")
|
||||
print("ADC value:", values[0])
|
||||
print("Switch state:", values[1])
|
||||
tasmota.cmd("Power2 ON") # Activate something
|
||||
end
|
||||
)
|
||||
|
||||
# Time-based rule
|
||||
tasmota.add_rule("Time#Minute=30",
|
||||
def()
|
||||
if tasmota.rtc()["hour"] == 18 # 6:30 PM
|
||||
tasmota.cmd("Dimmer 20") # Dim lights for evening
|
||||
end
|
||||
end
|
||||
)
|
||||
```
|
||||
|
||||
## Best Practices and Tips
|
||||
|
||||
1. **Always check for nil returns** from Tasmota functions
|
||||
2. **Use timers instead of delay()** to avoid blocking Tasmota
|
||||
3. **Implement proper error handling** in I²C and network operations
|
||||
4. **Use persist module** for settings that should survive reboots
|
||||
5. **Test memory usage** with `tasmota.gc()` during development
|
||||
6. **Use fast_loop sparingly** - it runs 200 times per second
|
||||
7. **Prefer driver events** over polling when possible
|
||||
8. **Use f-strings** for readable string formatting
|
||||
9. **Import modules only when needed** to save memory
|
||||
10. **Use `tasmota.wire_scan()`** instead of manual I²C bus detection
|
Loading…
x
Reference in New Issue
Block a user