# 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. ## 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. ## Basic Information ### Comments ```berry # This is a line comment #- This is a block comment -# ``` ### Literals #### Numerical Literals ```berry 40 # Integer literal 0x80 # Hexadecimal literal (integer) 3.14 # Real literal 1.1e-6 # Real literal with scientific notation ``` #### Boolean Literals ```berry true # Boolean true false # Boolean false ``` #### String Literals ```berry 'this is a string' "this is a string" ``` String literals can be concatenated without operators: ```berry s = "a" "b" "c" # s == "abc" s = "a" # Multi-line strings "b" "c" # s == "abc" ``` Escape sequences: - `\a` - Bell - `\b` - Backspace - `\f` - Form feed - `\n` - Newline - `\r` - Carriage return - `\t` - Horizontal tab - `\v` - Vertical tab - `\\` - Backslash - `\'` - Single quote - `\"` - Double quote - `\?` - Question mark - `\0` - Null character - `\ooo` - Character represented by octal number - `\xhh` - Character represented by hexadecimal number - `\uXXXX` - Unicode character (UTF-8 encoded) #### Nil Literal ```berry nil # Represents no value ``` ### Identifiers An identifier starts with an underscore or letter, followed by any combination of underscores, letters, or numbers. Berry is case-sensitive. ```berry a TestVariable Test_Var _init baseClass _ ``` ### Keywords ``` if elif else while for def end class break continue return true false nil var do import as try except raise static ``` ## Types and Variables ### Built-in Types #### Simple Types - **nil**: Represents no value - **Integer**: Signed integer (typically 32-bit) - **Real**: Floating-point number (typically 32-bit) - **Boolean**: `true` or `false` - **String**: Sequence of characters - **Function**: First-class value that can be called - **Class**: Template for instances - **Instance**: Object constructed from a class #### Class Types - **list**: Ordered collection of elements - **map**: Key-value pairs collection - **range**: Integer range - **bytes**: Byte buffer ### Variables Variables are dynamically typed in Berry. They can be defined in two ways: ```berry # Direct assignment (creates variable if it doesn't exist) a = 1 # Using the var keyword var a # Defines a with nil value var a = 1 # Defines a with value 1 var a, b # Defines multiple variables var a = 1, b = 2 # Defines multiple variables with values ``` ### Scope and Lifecycle Variables defined in the outermost block have global scope. Variables defined in inner blocks have local scope. ```berry var i = 0 # Global scope do var j = 'str' # Local scope print(i, j) # Both i and j are accessible end print(i) # Only i is accessible here ``` ## Expressions ### Operators #### Arithmetic Operators - `-` (unary): Negation - `+`: Addition or string concatenation - `-`: Subtraction - `*`: Multiplication - `/`: Division - `%`: Modulo (remainder) #### Relational Operators - `<`: Less than - `<=`: Less than or equal to - `==`: Equal to - `!=`: Not equal to - `>=`: Greater than or equal to - `>`: Greater than #### Logical Operators - `&&`: Logical AND (short-circuit) - `||`: Logical OR (short-circuit) - `!`: Logical NOT #### Bitwise Operators - `~`: Bitwise NOT - `&`: Bitwise AND - `|`: Bitwise OR - `^`: Bitwise XOR - `<<`: Left shift - `>>`: Right shift #### Assignment Operators - `=`: Simple assignment - `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `<<=`, `>>=`: Compound assignment - `:=`: Walrus assignment (assigns and returns value) #### Domain and Subscript Operators - `.`: Member access - `[]`: Subscript access #### Conditional Operator - `? :`: Ternary conditional #### Concatenation Operators - `+`: String or list concatenation - `..`: String concatenation or range creation ### Operator Precedence (highest to lowest) 1. `()` (grouping) 2. `()` (function call), `[]` (subscript), `.` (member access) 3. `-` (unary), `!`, `~` 4. `*`, `/`, `%` 5. `+`, `-` 6. `<<`, `>>` 7. `&` 8. `^` 9. `|` 10. `..` 11. `<`, `<=`, `>`, `>=` 12. `==`, `!=` 13. `&&` 14. `||` 15. `? :` 16. `=`, `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `<<=`, `>>=` 17. `:=` ## Statements ### Expression Statements ```berry a = 1 # Assignment statement print(a) # Function call statement ``` ### Block A block is a collection of statements. It defines a scope. ```berry do # This is a block var a = 1 print(a) end ``` ### Conditional Statements ```berry # Simple if if condition # code executed if condition is true end # if-else if condition # code executed if condition is true else # code executed if condition is false end # if-elif-else if condition1 # code executed if condition1 is true elif condition2 # code executed if condition1 is false and condition2 is true else # code executed if both condition1 and condition2 are false end ``` ### Iteration Statements ```berry # while loop while condition # code executed repeatedly while condition is true end # for loop (iterating over a container) for variable: expression # code executed for each element in the container end # Examples for i: 0..5 print(i) # Prints 0, 1, 2, 3, 4, 5 end for item: ['a', 'b', 'c'] print(item) # Prints a, b, c end for key: map.keys() print(key, map[key]) # Iterates over map keys end ``` ### Jump Statements ```berry # break - exits the loop while true if condition break end end # continue - skips to the next iteration for i: 0..5 if i == 3 continue end print(i) # Prints 0, 1, 2, 4, 5 end ``` ### Import Statement ```berry import math # Import math module import hardware as hw # Import hardware module as hw ``` ### Exception Handling ```berry # Raising exceptions raise 'my_error' # Raise an exception raise 'my_error', 'error message' # Raise with message # Catching exceptions try # code that might raise an exception except 'my_error' # code executed if 'my_error' is raised end # Catching with exception variable try # code that might raise an exception except 'my_error' as e # e contains the exception value end # Catching with exception and message variables try # code that might raise an exception except 'my_error' as e, msg # e contains the exception value, msg contains the message end # Catching any exception try # code that might raise an exception except .. # code executed for any exception end # Catching multiple exception types try # code that might raise an exception except 'error1', 'error2' as e, msg # code executed if either 'error1' or 'error2' is raised end ``` ## Functions ### Function Definition ```berry # Named function def add(a, b) return a + b end # Anonymous function add = def (a, b) return a + b end # Lambda expression (compact form) add = / a, b -> a + b ``` ### Function with Variable Arguments ```berry def print_all(a, b, *args) print(a, b) for arg: args print(arg) end end print_all(1, 2, 3, 4, 5) # args will be [3, 4, 5] ``` ### Calling Functions with Dynamic Arguments ```berry def sum(a, b, c) return a + b + c end call(sum, 1, 2, 3) # Calls sum(1, 2, 3) call(sum, 1, [2, 3]) # Calls sum(1, 2, 3) ``` ### Closures ```berry def counter(start) var count = start return def() count += 1 return count end end c = counter(0) print(c()) # 1 print(c()) # 2 ``` ## Object-Oriented Programming ### Class Declaration ```berry class Person var name, age def init(name, age) self.name = name self.age = age end def greet() print("Hello, my name is", self.name) end end ``` ### Static Members ```berry class MathUtils static var PI = 3.14159 static def square(x) return x * x end end print(MathUtils.PI) # Access static variable print(MathUtils.square(5)) # Call static method ``` ### Inheritance ```berry class Student : Person var school def init(name, age, school) super(self).init(name, age) # Call parent constructor self.school = school end def greet() super(self).greet() # Call parent method print("I study at", self.school) end end ``` ### Instantiation and Method Calls ```berry person = Person("John", 30) person.greet() # Hello, my name is John student = Student("Alice", 20, "University") student.greet() # Hello, my name is Alice # I study at University ``` ### Operator Overloading ```berry class Vector var x, y def init(x, y) self.x = x self.y = y end def +(other) return Vector(self.x + other.x, self.y + other.y) end def tostring() return "Vector(" + str(self.x) + ", " + str(self.y) + ")" end end v1 = Vector(1, 2) v2 = Vector(3, 4) v3 = v1 + v2 print(v3) # Vector(4, 6) ``` ## Built-in Classes ### List ```berry # Creating lists l1 = [] # Empty list l2 = [1, 2, 3] # List with elements l3 = list() # Empty list using constructor l4 = list(1, 2, 3) # List with elements using constructor # Accessing elements l2[0] # First element (1) l2[-1] # Last element (3) # Range-based access (slicing) l2[1..3] # Elements from index 1 to 3 inclusive [2, 3] l2[1..] # Elements from index 1 to end [2, 3] l2[1..-1] # Elements from index 1 to last element [2, 3] l2[0..-2] # All elements except the last one [1, 2] l2[-2..-1] # Last two elements [2, 3] # Modifying lists l2.push(4) # Add element to end l2.pop() # Remove and return last element l2.pop(1) # Remove and return element at index 1 l2.insert(1, 5) # Insert 5 at index 1 l2.remove(1) # Remove element at index 1 l2.resize(5) # Resize list to 5 elements (fills with nil) l2.clear() # Remove all elements # Negative indices for modification l2[-1] = 10 # Set last element to 10 l2[-2] += 5 # Add 5 to second-to-last element # Other operations l2.size() # Number of elements l2.concat() # Join elements as string (no separator) l2.concat(", ") # Join elements with separator l2.reverse() # Reverse the list l2.copy() # Create a shallow copy l2 + [4, 5] # Concatenate lists (new list) l2 .. 4 # Append element (modifies list) # Finding elements l2.find(3) # Returns index of first occurrence or nil if not found # Iterating over indices for i: l2.keys() # Iterate over list indices print(i, l2[i]) end # Comparing lists [1, 2] == [1, 2] # true [1, 2] != [1, 3] # true ``` ### Map ```berry # Creating maps m1 = {} # Empty map m2 = {"key": "value"} # Map with key-value pair m3 = map() # Empty map using constructor # Accessing elements m2["key"] # Get value by key m2.find("key") # Get value by key (returns nil if not found) m2.find("key", "default") # Get value with default if not found # Modifying maps m2["new_key"] = "new_value" # Add or update key-value pair m2.insert("key", "value") # Insert key-value pair (returns true if inserted, false if key exists) m2.remove("key") # Remove key-value pair # Other operations m2.size() # Number of key-value pairs m2.contains("key") # Check if key exists for k: m2.keys() # Iterate over keys print(k, m2[k]) end ``` ### Range ```berry # Creating ranges r1 = 0..5 # Range from 0 to 5 (inclusive) r2 = range(0, 5) # Same using constructor r3 = 10.. # Range from 10 to MAXINT # Accessing properties r1.lower() # Lower bound (0) r1.upper() # Upper bound (5) r1.incr() # Increment (default 1) # Modifying ranges r1.setrange(1, 6) # Change range bounds r1.setrange(1, 10, 2) # Change range bounds and increment # Using in for loops for i: 0..5 print(i) # Prints 0, 1, 2, 3, 4, 5 end ``` ### String Operations ```berry # String indexing and slicing s = "hello" s[0] # "h" s[1] # "e" s[-1] # "o" (last character) s[1..3] # "ell" (characters from index 1 to 3) s[1..] # "ello" (characters from index 1 to end) s[1..-1] # "ello" (characters from index 1 to last) s[0..-2] # "hell" (all characters except the last one) ``` ### Bytes ```berry # Creating bytes objects b1 = bytes() # Empty bytes b2 = bytes("1122AA") # From hex string b3 = bytes(10) # Pre-allocated 10 bytes b4 = bytes(-8) # Fixed size 8 bytes # Accessing bytes b2[0] # First byte (0x11) b2[1..2] # Bytes from index 1 to 2 # Modifying bytes b2[0] = 0xFF # Set byte at index 0 b2.resize(10) # Resize buffer b2.clear() # Clear all bytes # Reading/writing structured data b2.get(0, 2) # Read 2 bytes as unsigned int (little endian) b2.get(0, -2) # Read 2 bytes as unsigned int (big endian) b2.geti(0, 2) # Read 2 bytes as signed int b2.set(0, 0x1234, 2) # Write 2-byte value b2.add(0x1234, 2) # Append 2-byte value # Conversion b2.tohex() # Convert to hex string b2.asstring() # Convert to raw string b2.tob64() # Convert to base64 string b2.fromhex("AABBCC") # Load from hex string b2.fromstring("Hello") # Load from raw string b2.fromb64("SGVsbG8=") # Load from base64 string ``` ### File ```berry # Opening files f = open("test.txt", "w") # Open for writing f = open("test.txt", "r") # Open for reading # Writing to files f.write("Hello, world!") # Write string f.write(bytes("AABBCC")) # Write bytes # Reading from files content = f.read() # Read entire file line = f.readline() # Read one line raw_data = f.readbytes() # Read as bytes # File positioning f.seek(10) # Move to position 10 pos = f.tell() # Get current position size = f.size() # Get file size # Closing files f.flush() # Flush buffers f.close() # Close file ``` ## Standard Libraries ### String Module ```berry import string # String operations string.count("hello", "l") # Count occurrences (2) string.find("hello", "lo") # Find substring (3), returns -1 if not found string.split("a,b,c", ",") # Split by separator (["a", "b", "c"]) string.split("hello", 2) # Split at position (["he", "llo"]) # Character operations string.byte("A") # Get byte value (65) string.char(65) # Get character from byte ('A') # Case conversion string.toupper("hello") # Convert to uppercase ("HELLO") string.tolower("HELLO") # Convert to lowercase ("hello") # String transformation string.tr("hello", "el", "ip") # Replace characters ("hippo") string.replace("hello", "ll", "xx") # Replace substring ("hexxo") string.escape("hello\n") # Escape for C strings # Checking string.startswith("hello", "he") # Check prefix (true) string.startswith("hello", "HE", true) # Case-insensitive check (true) string.endswith("hello", "lo") # Check suffix (true) string.endswith("hello", "LO", true) # Case-insensitive check (true) # Formatting string.format("Value: %d", 42) # Format string format("Value: %.2f", 3.14159) # Format (global function) f"Value: {x}" # f-string format f"Value: {x:.2f}" # f-string with format specifier f"{x=}" # f-string debug format ``` ### Math Module ```berry import math # Constants math.pi # Pi (3.14159...) math.inf # Infinity math.nan # Not a Number math.imin # Smallest possible integer math.imax # Largest possible integer # Basic functions math.abs(-5) # Absolute value (5) math.floor(3.7) # Round down (3) math.ceil(3.2) # Round up (4) math.round(3.5) # Round to nearest (4) math.min(1, 2, 3) # Minimum value (1) math.max(1, 2, 3) # Maximum value (3) # Exponential and logarithmic math.sqrt(16) # Square root (4) math.pow(2, 3) # Power (8) math.exp(1) # e^x (2.71828...) math.log(2.71828) # Natural logarithm (1) math.log10(100) # Base-10 logarithm (2) # Trigonometric math.sin(math.pi/2) # Sine (1) math.cos(0) # Cosine (1) math.tan(math.pi/4) # Tangent (1) math.asin(1) # Arc sine (pi/2) math.acos(1) # Arc cosine (0) math.atan(1) # Arc tangent (pi/4) math.atan2(1, 1) # Arc tangent of y/x (pi/4) # Angle conversion math.deg(math.pi) # Radians to degrees (180) math.rad(180) # Degrees to radians (pi) # Random numbers math.srand(42) # Seed random generator math.rand() # Random integer # Special checks math.isinf(math.inf) # Check if value is infinity (true) math.isnan(math.nan) # Check if value is NaN (true) ``` ### JSON Module ```berry import json # Parsing JSON data = json.load('{"name": "John", "age": 30}') print(data.name) # John # Error handling with json.load data = json.load('{"invalid": }') # Returns nil on parsing error if data == nil print("Invalid JSON") end # Generating JSON person = { "name": "Alice", "age": 25, "hobbies": ["reading", "swimming"] } json_str = json.dump(person) # Compact JSON json_formatted = json.dump(person, "format") # Formatted JSON ``` ### OS Module ```berry import os # Directory operations os.getcwd() # Get current directory os.chdir("/path/to/dir") # Change directory os.mkdir("/path/to/new/dir") # Create directory os.remove("/path/to/file") # Delete file or directory os.listdir() # List current directory os.listdir("/path") # List specific directory # Path operations os.path.isdir("/path") # Check if path is directory os.path.isfile("/path/file.txt") # Check if path is file os.path.exists("/path") # Check if path exists os.path.split("/path/file.txt") # Split into ["/path", "file.txt"] os.path.splitext("file.txt") # Split into ["file", ".txt"] os.path.join("path", "file.txt") # Join into "path/file.txt" # System operations os.system("command") # Execute system command os.exit() # Exit interpreter ``` ### Global Module ```berry import global # Accessing globals global_vars = global() # List of all global variables global.contains("var_name") # Check if global exists value = global.var_name # Get global value global.var_name = 42 # Set global value value = global.("dynamic_name") # Dynamic access by name ``` ### Introspect Module ```berry import introspect # Inspecting objects members = introspect.members(obj) # List of object members value = introspect.get(obj, "attr") # Get attribute value introspect.set(obj, "attr", value) # Set attribute value name = introspect.name(obj) # Get object name is_method = introspect.ismethod(fn) # Check if function is method # Module operations mod = introspect.module("math") # Import module dynamically # Pointer operations (advanced) ptr = introspect.toptr(addr) # Convert int to pointer addr = introspect.fromptr(ptr) # Convert pointer to int ``` ## Error Handling ### Standard Exceptions - `assert_failed`: Assertion failed - `index_error`: Index out of bounds - `io_error`: IO malfunction - `key_error`: Key error - `runtime_error`: VM runtime exception - `stop_iteration`: End of iterator - `syntax_error`: Syntax error - `unrealized_error`: Unrealized function - `type_error`: Type error ### Raising Exceptions ```berry raise "my_error" # Raise exception raise "my_error", "message" # Raise with message ``` ### Catching Exceptions ```berry try # Code that might raise an exception except "my_error" # Handle specific exception end try # Code that might raise an exception except "error1", "error2" # Handle multiple exceptions end try # Code that might raise an exception except "my_error" as e # e contains the exception value end try # Code that might raise an exception except "my_error" as e, msg # e contains exception, msg contains message end try # Code that might raise an exception except .. # Catch all exceptions end ``` ### Error Handling Patterns Many functions in Berry return `nil` to indicate errors rather than raising exceptions. This is common in functions that parse data or perform operations that might fail: ```berry # JSON parsing data = json.load('{"invalid": }') # Returns nil on parsing error if data == nil print("Invalid JSON") end # Map access value = map.find("key") # Returns nil if key doesn't exist if value == nil print("Key not found") end # String operations index = string.find("hello", "z") # Returns -1 if substring not found if index == -1 print("Substring not found") end ``` ### Assertions ```berry assert(condition) # Raises assert_failed if condition is false assert(condition, "message") # Raises with custom message ``` ## Best Practices ### Variable Naming - Use descriptive names for variables and functions - Use camelCase or snake_case consistently - Prefix private members with underscore (convention only) ### Code Organization - Group related functions and classes - Use modules for logical separation - Keep functions small and focused ### Memory Management - Be mindful of memory usage on embedded systems - Release resources when no longer needed - Use fixed-size buffers when appropriate ### Error Handling - Use exceptions for exceptional conditions - Check return values for expected errors - Provide meaningful error messages ### Performance - 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("") 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("My Page") 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("

") 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