Skip to content

Berry Scripting Language ~

Berry Scripting is included in all tasmota32 builds. It is NOT supported on ESP82xx

If you plan to code in Berry, you should enable #define USE_BERRY_DEBUG which will give you much more details when coding

Berry logo

Useful resources:

If you're new to Berry, have a look at Berry Introduction (in 20 minutes of less)

Introduction to Berry~

Berry is the next generation scripting for Tasmota. It is based on the open-source Berry project, delivering an ultra-lightweight dynamically typed scripting language designed for lower-performance embedded devices.

Github Manual

Reference sheet

Download Berry Short Manual to get a list of basic functions and capabilities of Berry language

Berry Scripting allows simple and also advanced extensions of Tasmota, for example:

  • simple scripting
  • advanced rules, beyond what is possible with native rules
  • advanced automations

Berry Scripting takes it one step further and allows to build dynamic extensions to Tasmota, that would previously require native code:

  • build light animations
  • build I2C drivers
  • build complete Tasmota drivers
  • integrate native libraries like lvgl see LVGL

About the Berry language~

Berry has the following advantages:

  • Lightweight: A well-optimized interpreter with very little resources. Ideal for use in microprocessors.
  • Fast: optimized one-pass bytecode compiler and register-based virtual machine.
  • Powerful: supports imperative programming, object-oriented programming, functional programming.
  • Flexible: Berry is a dynamic type script, and it's intended for embedding in applications. It can provide good dynamic scalability for the host system.
  • Simple: simple and natural MicroPython-eque syntax, supports garbage collection and easy to use FFI (foreign function interface).
  • RAM saving: With compile-time object construction, most of the constant objects are stored in read-only code data segments, so the RAM usage of the interpreter is very low when it starts.

Tasmota Port~

Berry Scripting in only supported on Tasmota32 for ESP32. The RAM usage starts at ~10kb and will be later optimized. Berry uses PSRAM on ESP32 if available (PSRAM is external RAM attached to ESP32 via SPI, it is slower but larger than internal RAM.

Quick Start~

Click on Configuration then Berry Scripting Console and enjoy the colorful Berry console, also called REPL (Read-Eval-Print-Loop).

Berry console

Drag the bottom corner of each screen to change its size

The console is not designed for big coding tasks but it's recommended to use a code editor when dealing with many, many lines of code. An extension for Visual Studio Code exists to make writing Berry scripts even easier with colored syntax. Download the entire folder and copy to VSCode extensions folder.

REPL Console~

Try typing simple commands in the REPL. Since the input can be multi-lines, press Enter twice or click "Run" button to run the code. Use Up and Down to navigate through history of previous commands.

> 1+1
> 2.0/3
> print('Hello Tasmota!')
Hello Tasmota!

Note: Berry's native print() command displays text in the Berry Console and in the Tasmota logs. To log with finer control, you can also use the log() function which will not display in the Berry Console.

> print('Hello Tasmota!')
  log('Hello again')
Hello Tasmota!

Meanwhile the Tasmota log shows:

> tasmota.cmd("Dimmer 60")
{'POWER': 'ON', 'Dimmer': 60, 'Color': '996245', 'HSBColor': '21,55,60', 'Channel': [60, 38, 27]}
The light is bright

Lights and Relays~

Berry provides complete support for Relays and Lights.

You can control individual Relays or lights with tasmota.get_power() and tasmota.set_power().

tasmota.get_power() returns an array of booleans representing the state of each relays and light (light comes last).

tasmota.set_power(relay, onoff) changes the state of a single relay/light.

2 relays and 1 light

> tasmota.get_power()
[false, true, false]

> tasmota.set_power(0, true)

> tasmota.get_power()
[true, true, false]

For light control, light.get() and light.set accept a structured object containing the following arguments:

Attributes Details
power boolean
Turns the light off or on. Equivalent to tasmota.set_power(). When brightness is set to 0, power is automatically set to off. On the contrary, you need to specify power:true to turn the light on.
bri int range 0..255
Set the overall brightness. Be aware that the range is 0..255 and not 0..100 as Dimmer.
hue int 0..360
Set the color Hue in degree, range 0..360 (0=red).
sat int 0..255
Set the color Saturation (0 is grey).
ct int 153..500
Set the white color temperature in mired, ranging from 153 (cold white) to 500 (warm white)
rgb string 6 hex digits
Set the color as hex RRGGBB, changing color and brightness.
channels array of int, ranges 0..255
Set the value for each channel, as an array of numbers

When setting attributes, they are evaluated in the following order, the latter overriding the previous: power, ct, hue, sat, rgb, channels, bri.

  # set to yellow, 25% brightness
> light.set({"power": true, "hue":60, "bri":64, "sat":255})
{'bri': 64, 'hue': 60, 'power': true, 'sat': 255, 'rgb': '404000', 'channels': [64, 64, 0]}

  # set to RGB 000080 (blue 50%)
> light.set({"rgb": "000080"})
{'bri': 128, 'hue': 240, 'power': true, 'sat': 255, 'rgb': '000080', 'channels': [0, 0, 128]}

  # set bri to zero, also powers off
> light.set({"bri": 0})
{'bri': 0, 'hue': 240, 'power': false, 'sat': 255, 'rgb': '000000', 'channels': [0, 0, 0]}

  # changing bri doesn't automatically power
> light.set({"bri": 32, "power":true})
{'bri': 32, 'hue': 240, 'power': true, 'sat': 255, 'rgb': '000020', 'channels': [0, 0, 32]}

  # set channels as numbers (purple 12%)
> light.set({"channels": [32,0,32]})
{'bri': 32, 'hue': 300, 'power': true, 'sat': 255, 'rgb': '200020', 'channels': [32, 0, 32]}


The rule function have the general form below where parameters are optional:

def function_name(value, trigger, msg)
Parameter Description
value The value of the trigger. Similar to %value% in native rules.
trigger string of the trigger with all levels. Can be used if the same function is used with multiple triggers.
msg map Berry structured object of the message, decoded from JSON. If JSON was invalid, it contains the original string

Dimmer rule

Define the function and add a rule to Tasmota where the function runs if Dimmer value is more than 50

> def dimmer_over_50()
    print("The light is bright")
  tasmota.add_rule("Dimmer>50", dimmer_over_50)

> tasmota.cmd("Dimmer 30")
{'POWER': 'ON', 'Dimmer': 30, 'Color': '4D3223', 'HSBColor': '21,55,30', 'Channel': [30, 20, 14]}

> tasmota.cmd("Dimmer 60")
{'POWER': 'ON', 'Dimmer': 60, 'Color': '996245', 'HSBColor': '21,55,60', 'Channel': [60, 38, 27]}
The light is bright

The same function can be used with multiple triggers.

If the function to process an ADC input should be triggered both by the tele/SENSOR message and the result of a Status 10 command:

tasmota.add_rule("ANALOG#A1", rule_adc_1)
tasmota.add_rule("StatusSNS#ANALOG#A1", rule_adc_1)

Or if the same function is used to process similar triggers:

import string

def rule_adc(value, trigger)
  var i=string.find(trigger,"#A")
  var tr=string.split(trigger,i+2)
  var adc=number(tr[1])
  print("value of adc",adc," is ",value)


Another way to address the same using anonymous functions created dynamically

def rule_adc(adc, value)
  print("value of adc",adc," is ",value)
tasmota.add_rule("ANALOG#A1",def (value) rule_adc(1,value) end )
tasmota.add_rule("ANALOG#A2",def (value) rule_adc(2,value) end )

Teleperiod rules

Teleperiod rules are supported with a different syntax from Tasmota rules. Instead of using Tele- prefix, you must use Tele#. For example Tele#ANALOG#Temperature1 instead of Tele-ANALOG#Temperature1


Berry code, when it is running, blocks the rest of Tasmota. This means that you should not block for too long, or you may encounter problems. As a rule of thumb, try to never block more than 50ms. If you need to wait longer before the next action, use timers. As you will see, timers are very easy to create thanks to Berry's functional nature.

All times are in milliseconds. You can know the current running time in milliseconds since the last boot:

> tasmota.millis()

Sending a timer is as easy as tasmota.set_timer(<delay in ms>,<function>)

> def t() print("Booh!") end

> tasmota.set_timer(5000, t)
[5 seconds later]

A word on functions and closure~

Berry is a functional language, and includes the very powerful concept of a closure. In a nutshell, it means that when you create a function, it can capture the values of variables when the function was created. This roughly means that it does what intuitively you would expect it to do.

When using Rules or Timers, you always pass Berry functions.

cron recurrent calls~

You can choose to run some function/closure at regular intervals specified as cron type format. Crontab Guru is an easy way to create and test your cron format.

> def f() print("Hi") end
> tasmota.add_cron("*/15 * * * * *", f, "every_15_s")
Hi      # added every 15 seconds
> tasmota.remove_cron("every_15_s")     # cron stops

Like timers, you need to create a closure if you want to register a method of an instance. Example:

class A
    var name
    def init(name) = name
    def p()
> bob = A("bob")
> bob.p()
Hi, bob
> tasmota.add_cron("*/15 * * * * *", /-> bob.p(), "hi_bob")
Hi, bob
Hi, bob
Hi, bob
> tasmota.remove_cron("hi_bob")     # cron stops

You can get the timestamp for the next event by using tasmota.next_cron(id) which returns an epoch in seconds.

Loading Filesystem~

You can upload Berry code in the filesystem using the Consoles - Manage File system menu and load them at runtime. Make careful to use *.be extension for those files.

To load a Berry file, use the load(filename) function where filename is the name of the file with .be or .bec extension; if the file has no extension '.be' is automatically appended.

You don't need to prefix with /. A leading / will be added automatically if it is not present.

When loading a Berry script, the compiled bytecode is automatically saved to the filesystem, with the extension .bec (this is similar to Python's .py/.pyc mechanism). The save(filename,closure) function is used internally to save the bytecode.

If a precompiled bytecode (extension .bec) is present of more recent than the Berry source file, the bytecode is directly loaded which is faster than compiling code. You can eventually remove the *.be file and keep only *.bec file (even with load("").

Creating a Tasmota Driver~

You can easily create a complete Tasmota driver with Berry.

A Driver responds to messages from Tasmota. For each message type, the method with the same name is called. Actually you can register any class as a driver, it does not need to inherit from Driver; the call mechanism is based on names of methods that must match the name of the event to be called.

Driver methods are called with the following parameters: f(cmd, idx, payload, raw). cmd is a string, idx an integer, payload a Berry object representation of the JSON in payload (if any) or nil, raw is a string. These parameters are meaningful to a small subset of events:

  • every_second(): called every second
  • every_50ms(): called every 50ms (i.e. 20 times per second)
  • every_100ms(): called every 100ms (i.e. 10 times per second)
  • every_200ms(): called every 200ms (i.e. 5 times per second)
  • every_250ms(): called every 250ms (i.e. 4 times per second)
  • web_sensor(): display sensor information on the Web UI
  • json_append(): display sensor information in JSON format for TelePeriod reporting
  • web_add_button(): (deprecated) synonym of web_add_console_button()
  • web_add_main_button(), web_add_management_button(), web_add_console_button(), web_add_config_button(): add a button to Tasmotas Web UI on a specific page
  • web_add_handler(): called when Tasmota web server started, and the right time to call webserver.on() to add handlers
  • button_pressed(): called when a button is pressed
  • save_before_restart(): called just before a restart
  • mqtt_data(topic, idx, data, databytes): called for MQTT payloads matching mqtt.subscribe. idx is zero, and data is normally unparsed JSON.
  • set_power_handler(cmd, idx): called whenever a Power command is made. idx contains the index of the relay or light. cmd can be ignored.
  • display(): called by display driver with the following subtypes: init_driver, model, dim, power.

Then register the driver with tasmota.add_driver(<driver>).

There are basically two ways to respond to an event:


Define a class and implement methods with the same name as the events you want to respond to.

class MyDriver
  def every_second()
    # do something

d1 = MyDriver()


Fast Loop~

Beyond the events above, a specific mechanism is available for near-real-time events or fast loops (above 50 times per second).

Special attention is made so that there is no or very little impact on performance. Until a first callback is registered, performance is not impacted and Berry is not called. This protects any current use from any performance impact.

Once a callback is registered, it is called separately from Berry drivers to ensure minimal overhead.

tasmota.add_fast_loop(cl:function) -> nil registers a callback to be called in fast loop mode.

The callback is called without any parameter and does not need to return anything. The callback is called at each iteration of Tasmota event loop. The frequency is tightly linked to the Speed <x> command. By default, the sleep period is 50ms, hence fast_loop is called every 50ms. You can reduce the time with Sleep 10 (10ms) hence calling 100 times per second. If you set Sleep 0, the callback is called as frequently as possible (discouraged unless you have a good reason).

tasmota.remove_fast_loop(cl:function) -> nil removes a previously registered function or closure. You need to pass the exact same closure reference.

Warning, if you need to register a method from an instance, you need a closure:

class my_driver
  def every_100ms()
    # called every 100ms via normal way

  def fast_loop()
    # called at each iteration, and needs to be registered separately and explicitly

  def init()
    # register fast_loop method
    tasmota.add_fast_loop(/-> self.fast_loop())
    # variant:
    # tasmota.add_fast_loop(def () self.fast_loop() end)

tasmota.add_driver(my_driver())                     # register driver
tasmota.add_fast_loop(/-> my_driver.fast_loop())    # register a closure to capture the instance of the class as well as the method

Tasmota Only Extensions~

log(msg:string [, level:int = 3]) -> string~

Logs a message to the Tasmota console. Optional second argument is log_level (0..4), default is 2 LOG_LEVEL_INFO.


> log("A")

load(filename:string) -> bool~

Loads a Berry script from the filesystem, and returns true if loaded successfully, false if file not found, or raises an exception in runtime. Filename does not need to start with /, but needs to end with .be (Berry source code) or .bec (precompiled bytecode).

When loading a source file, the precompiled bytecode is saved to filesystem using the .bec extension.

save(filename:string, f:closure) -> nil~

Internally used function to save bytecode. It's a wrapper to the Berry's internal API be_savecode(). There is no check made on the filename.

There is generally no need to use this function, it is used internally by load().

tasmota object~

A root level object called tasmota is created and contains numerous functions to interact with Tasmota.

Tasmota Function Parameters and details
tasmota.get_free_heap () -> int
Returns the number of free bytes on the Tasmota heap.
tasmota.publish (topic:string, payload:string[, retain:bool, start:int, len:int]) -> nil
Deprecated see mqtt.publish
tasmota.publish_result (payload:string, subtopic:string) -> nil
Publishes a JSON result and triggers any associated rule. payload is expected to be a JSON string, and subtopic the subtopic used to publish the payload.
tasmota.publish_rule (payload:string) -> handled:bool
sends a JSON stringified message to the rule engine, without actually publishing a message to MQTT. Returns true if the message was handled by a rule.
tasmota.cmd (command:string) -> map
Sends any command to Tasmota, like it was type in the console. It returns the result of the command if any, as a map parsed from the command output JSON.
tasmota.memory () -> map
Returns memory stats similar to the Information page.
Example: {'iram_free': 41, 'frag': 51, 'program_free': 1856, 'flash': 4096, 'heap_free': 226, 'program': 1679}
or when PSRAM {'psram_free': 3703, 'flash': 16384, 'program_free': 3008, 'program': 1854, 'psram': 4086, 'frag': 27, 'heap_free': 150}
tasmota.add_rule (pattern:string, f:function [, id:any]) ->nil
Adds a rule to the rule engine. See above for rule patterns.
Optional id to remove selectively rules.
tasmota.remove_rule (pattern:string [, id:any]) ->nil
Removes a rule to the rule engine. Silently ignores the pattern if no rule matches. Optional id to remove selectively some rules.
tasmota.add_driver (instance) ->nil
Registers an instance as a driver
tasmota.remove_driver (instance) ->nil
Removes a driver
tasmota.gc () -> int
Triggers garbage collection of Berry objects and returns the bytes currently allocated. This is for debug only and shouldn't be normally used. gc is otherwise automatically triggered when necessary.
tasmota.urlfetch (url:string [, filename:string]) -> bytes:int
Download a url (http or https) and store the content in the filesystem
filename is optional, needed if you want to change the name of the file from the url suffix. Returns the number of bytes downloaded or -1 if failed.

Functions used to retrieve Tasmota configuration~

Tasmota Function Parameters and details
tasmota.get_option (index:int) -> int
Returns the value of SetOption <index>
tasmota.wire_scan (addr:int [, index:int]) -> wire instance or nil
Scan both I2C buses for a device of address addr, optionally taking into account disabled devices via I2CDevice. Returns a wire object corresponding to the bus where the device is, or nil if device is not connected or disabled.
tasmota.i2c_enabled (index:int) -> bool
Returns true if the I2C module is enabled, see I2C page.
tasmota.arch () -> string
Returns the name of the architecture. Currently can be esp32, esp32s2, esp32s3, esp32c3
tasmota.read_sensors ([show_sensor:bool]) -> string
Returns the value of sensors as a JSON string similar to the teleperiod. The response is a string, not a JSON object. The reason is that some sensors might produce invalid JSON. It's your code's responsibility to try parsing as JSON.
An optional boolean parameter (false by default) can be set to trigger a display of the new values (i.e. sends a FUNC_SHOW_SENSOR` event to drivers).
tasmota.wifi () -> map
Retrieves Wi-Fi connection info or empty map.
Example: {'mac': 'aa:bb:cc:22:11:03', 'quality': 100, 'rssi': -47, 'ip': ''}
tasmota.eth () -> map
Retrieves Ethernet connection info or empty map.
Example: {'mac': 'aa:bb:cc:22:11:00', 'ip': ''}

Functions for time, timers or cron~

Tasmota Function Parameters and details
tasmota.millis ([delay:int]) -> int
Returns the number of milliseconds since last reboot. The optional parameter lets you specify the number of milliseconds in the future; useful for timers.
tasmota.time_reached (timer:int) -> bool
Checks whether the timer (in milliseconds) has been reached or not. Always use this function and don't do compares between millis() and timers, because of potential sign and overflow issues.
tasmota.rtc () -> map
Returns clockwall time with variants.
Example: {'local': 1619560407, 'utc': 1619556807, 'timezone': 60, 'restart': 1619556779}
tasmota.time_dump (timestamp:int) -> map
Decompose a timestamp value (in seconds) to its components
Example: tasmota.time_dump(1619560407) -> {'min': 53, 'weekday': 2, 'sec': 27, 'month': 4, 'year': 2021, 'day': 27, 'epoch': 1619560407, 'hour': 21}
tasmota.time_str (timestamp:int) -> string
Converts a timestamp value (in seconds) to an ISO 8601 string
Example: tasmota.time_str(1619560407) -> 2021-04-27T21:53:27
tasmota.set_timer (delay:int, f:function [, id:any]) -> nil
Runs the closure or function f after delay milliseconds, optional id can be used to remove the timer.
tasmota.remove_timer (id:string) -> nil
Removes the timer with the id used on tasmota.set_timer.
tasmota.strftime (format:string, timestamp:int) -> string
Converts a timestamp value (in seconds) to a string using the format conversion specifiers
Example: tasmota.strftime("%d %B %Y %H:%M:%S", 1619560407) -> 27 April 2021 21:53:27
tasmota.strptime (time:string, format:string) -> map or nil
Converts a string to a date, according to a time format following the C strptime format. Returns a map similar to tasmota.time_dump() or nil if parsing failed. An additional unparsed attribute reports the unparsed string, or empty string if everything was parsed.
Example: tasmota.strptime("2001-11-12 18:31:01", "%Y-%m-%d %H:%M:%S") -> {'unparsed': '', 'weekday': 1, 'day': 12, 'epoch': 1005589861, 'min': 31, 'year': 2001, 'month': 11, 'sec': 1, 'hour': 18}
tasmota.yield () -> nil
Calls Arduino framework yield() function to give back some time to low-level functions, like Wifi. Prevents WDT watchdog from happening.
tasmota.delay ([delay:int]) -> int
Waits and blocks execution for delay milliseconds. Should ideally never wait more than 10ms and absolute max 50ms. Otherwise use set_timer.
tasmota.add_cron (pattern:string, f:function [, id:any]) -> nil
Adds a cron-type timer, with a cron pattern and a function/closure to call. An optional id can be added to retrieve or delete the cron timer
tasmota.remove_cron (id:any) -> nil
Remove a cron timer.
tasmota.next_cron (id:any) -> int
returns the next timestamp for the cron timer. The timestamp is second epoch in local time. You can use tasmota.tasmota.time_str() to convert to a time string.

Functions to create custom Tasmota command~

Tasmota Function Parameters and details
tasmota.add_cmd (name:string, f:function) -> nil
Adds a command to Tasmota commands. Command names are case-insensitive. Command names are analyzed after native commands and after most commands, so you can't override a native command.
tasmota.resp_cmnd_str (message:string) -> nil
Sets the output for the command to message.
tasmota.resp_cmnd_done () -> nil
Sets the output for the command to "Done" (localized message).
tasmota.resp_cmnd_error () -> nil
Sets the output for the command to "Error" (localized message).
tasmota.resp_cmnd_failed () -> nil
Sets the output for the command to "Fail" (localized message).
tasmota.resp_cmnd (message:string) -> nil
Overrides the entire command response. Should be a valid JSON string.
tasmota.remove_cmd (name:string) -> nil
Remove a command to Tasmota commands. Removing an non-existing command is skipped silently.

Functions to add custom responses to JSON and Web UI to sensors~

Tasmota Function Parameters and details
tasmota.response_append (name:string) -> nil
Adds JSON fragment to the current response. Used for example for sensors to add JSON to teleperiod.
Can be called only in json_append() method of a registered driver (see cookbook). It is called at least at each teleperiod, or when reading sensor data in JSON.
tasmota.web_send (message:string) -> nil
Adds an HTML fragment to the Web output.
Can be called only in web_sensor() method of a registered driver (see cookbook). It is called at each main page refresh.
tasmota.web_send_decimal (message:string) -> nil
Adds an HTML fragment to the Web output, similar to web_send but converts decimal dot . to the locale decimal separator.
Can be called only in web_sensor() method of a registered driver (see cookbook). It is called at each main page refresh.

See examples in the Berry-Cookbook

Functions to manage Relays and Lights~

Tasmota Function Parameters and details
tasmota.get_power () -> list[bool]
Returns the state On/Off of each Relay and Light as a list of bool.
tasmota.set_power (index:int, onoff:bool) -> bool
Sets the on/off state of a Relay/Light. Returns the previous status of the Relay/Light of nil if index is invalid.
> tasmota.get_power()
tasmota.get_light deprecated use light.get
tasmota.set_light deprecated use light.set
tasmota.get_switches () -> list(bool)
Returns as many values as switches are present. true means PRESSED and false means NOT_PRESSED. (Warning: this is the opposite of the internal representation where PRESSED=0)
Note: if there are holes in the switch definition, the values will be skipped. I.e. if you define SWITCH1 and SWITCH3, the array will return the two consecutive values for switches 1/3.

Low-level access to Tasmota globals and settings.~

Use with care and only if you know what you are doing.

The construct is to use or tasmota.settings to read or write attributes.

You can do bad things with these features

Value Details Current sleep value Number of Power channels, e.g. having virtual relays
tasmota.settings.sleep Sleep value stored in flash

mqtt module~

Use with import mqtt.

Since v11.1.0.1, there is an easier way than registering a driver, and listening to mqtt_data event. You can now just attach a function or closure to a MQTT topic, and it does the magic for you.

The function you attach to a topic pattern received only the matching MQTT messages, not all messages unlike mqtt_data() would.

The function takes the same parameters as mqtt_data(): - topic: full topic received from the broker - idx: not used - payload_s: payload as string, usually converted to JSON with import json json.load(payloas_s) - payload_b: payload as a binary payload, bytes() array - the function should return true if the event was parsed or if the event should not trigger a Tasmota command. If you return nil or nothing, it is considered as true which is the usual behavior you want (i.e. not trigger a Tasmota command from random MQTT messages).

Tasmota Function Parameters and details
mqtt.publish (topic:string, payload:string[, retain:bool, start:int, len:int]) -> nil
Equivalent of publish command, publishes a MQTT message on topic with payload. Optional retain parameter.
payload can be a string or a bytes() binary array
start and len allow to specify a sub-part of the string or bytes buffer, useful when sending only a portion of a larger buffer.
mqtt.subscribe mqtt.subscribe(topic:string [, function:closure]) -> nil
Subscribes to a topic (exact match or pattern). Contrary to Tasmota's Subscribe command, the topic is sent as-is and not appended with /#. You need to add wildcards yourself. Driver method mqtt_data is called for each matching payload.
If a function/closure is added, the function is called whenever and only if an incoming messages matches the pattern for this function. The function should return true if message was processed, false if not which will let the message flow to Tasmota eventually as a command.
mqtt.unsubscribe (topic:string) -> nil
Unubscribe to a topic (exact match).

light object~

Module light is automatically imported via a hidden import light command.

Tasmota Function Parameters and details
light.get (index:int) -> map
Get the current status if light number index (default:0).
> light.get
{'bri': 77, 'hue': 21, 'power': true, 'sat': 140, 'rgb': '4D3223', 'channels': [77, 50, 35]}
light.set (settings:map[, index:int]) -> map
Sets the current state for light index (default: 0.
> light.set({'hue':120,'bri':50,'power':true})
{'bri': 50, 'hue': 120, 'power': true, 'sat': 140, 'rgb': '173217', 'channels': [23, 50, 23]}
light.gamma10 (channel) -> int
Computes the gamma corrected value with 10 bits resolution for input and output. Note: Gamma is optimized for speed and smooth fading, and is not 100% mathematically accurate.
Input and output are in range 0..1023.
light.reverse_gamma10 (gamma) -> int
Computes the reverse gamma with 10 bits resolution for input and output.
Input and output are in range 0..1023.
light.gamma8 (channel) -> int
Computes the gamma corrected value with 8 bits resolution for input and output.
Input and output are in range 0..255.

gpio module~

This module allows to retrieve the GPIO configuration set in the templates. You need to distinguish between logical gpio (like PWM, or I2C) and physical gpio which represent the GPIO number of the physical pin. transforms a logical GPIO to a physical GPIO, or -1 if the logical GPIO is not set.

Currently there is limited support for GPIO: you can only read/write in digital mode and set the GPIO mode.

Tasmota Function Parameters and details
gpio.pin_used (gpio [,index]) -> bool
returns if a specific GPIO is used. index allows to iterate through GPIOs. Example: gpio.pin_used(gpio.REL1) to check Relay1, or gpio.pin_used(gpio.REL1,1) to check Relay2 (index is zero-based) (gpio [,index]) -> int
returns the physical GPIO number assigned to the Tasmota GPIO, or -1 if the GPIO is not assigned
gpio.digital_write (phy_gpio, val) -> nil needs the physical GPIO number
sets the GPIO to LOW/HIGH. val can be 0, 1, gpio.LOW or gpio.HIGH. Example: gpio.digital_write(, gpio.HIGH) sets Relay1 to High.
gpio.digital_read (phy_gpio) -> int needs the physical GPIO number
reads the value of a GPIO. Returns 0 or 1.
gpio.pin_mode (phy_gpio, mode) -> nil needs the physical GPIO number
Changes the GPIO mode. It should be called very cautiously. Normally Tasmota handles automatically GPIO modes.
mode can have the following values: gpio.INPUT, gpio.OUTPUT, gpio.PULLUP, gpio.INPUT_PULLUP, gpio.PULLDOWN, gpio.OPEN_DRAIN, gpio.OUTPUT_OPEN_DRAIN, gpio.DAC
gpio.dac_voltage (phy_gpio:int, voltage_mv:int) -> int
Sets the DAC voltage in mV. The resolution is 8 bits over a range of 0..3.3V, i.e. an increment of ~13mV, this function returns the actual voltage output rounded to the closest value. See below for constraints of DAC GPIOs.
gpio.set_pwm (phy_gpio:int, duty:int [, phase:int]) -> nil
Sets the value of a PWM output
phy_gpio: physical GPIO number
duty: analog value for the pwm, range is 0..1023 unless you change the PWM range
phase: (opt) set the starting point in time for this pulse from start of cycle. Range is 0..1023 unless you change PWM range. This allows to dephase pulses, for example for H-bridge.
Low-level this is a low-level function that bypasses all the Tasmota logic around PWM. Use with caution as a PWM command might overwrite your settings.

Any internal error or using unsupported GPIO yields a Berry exception.

Possible values for Tasmota GPIOs:

gpio.NONE, gpio.KEY1, gpio.KEY1_NP, gpio.KEY1_INV, gpio.KEY1_INV_NP, gpio.SWT1, gpio.SWT1_NP, gpio.REL1, gpio.REL1_INV, gpio.LED1, gpio.LED1_INV, gpio.CNTR1, gpio.CNTR1_NP, gpio.PWM1, gpio.PWM1_INV, gpio.BUZZER, gpio.BUZZER_INV, gpio.LEDLNK, gpio.LEDLNK_INV, gpio.I2C_SCL, gpio.I2C_SDA, gpio.SPI_MISO, gpio.SPI_MOSI, gpio.SPI_CLK, gpio.SPI_CS, gpio.SPI_DC, gpio.SSPI_MISO, gpio.SSPI_MOSI, gpio.SSPI_SCLK, gpio.SSPI_CS, gpio.SSPI_DC, gpio.BACKLIGHT, gpio.OLED_RESET, gpio.IRSEND, gpio.IRRECV, gpio.RFSEND, gpio.RFRECV, gpio.DHT11, gpio.DHT22, gpio.SI7021, gpio.DHT11_OUT, gpio.DSB, gpio.DSB_OUT, gpio.WS2812, gpio.MHZ_TXD, gpio.MHZ_RXD, gpio.PZEM0XX_TX, gpio.PZEM004_RX, gpio.PZEM016_RX, gpio.PZEM017_RX, gpio.SAIR_TX, gpio.SAIR_RX, gpio.PMS5003_TX, gpio.PMS5003_RX, gpio.SDS0X1_TX, gpio.SDS0X1_RX, gpio.SBR_TX, gpio.SBR_RX, gpio.SR04_TRIG, gpio.SR04_ECHO, gpio.SDM120_TX, gpio.SDM120_RX, gpio.SDM630_TX, gpio.SDM630_RX, gpio.TM1638CLK, gpio.TM1638DIO, gpio.TM1638STB, gpio.MP3_DFR562, gpio.HX711_SCK, gpio.HX711_DAT, gpio.TX2X_TXD_BLACK, gpio.TUYA_TX, gpio.TUYA_RX, gpio.MGC3130_XFER, gpio.MGC3130_RESET, gpio.RF_SENSOR, gpio.AZ_TXD, gpio.AZ_RXD, gpio.MAX31855CS, gpio.MAX31855CLK, gpio.MAX31855DO, gpio.NRG_SEL, gpio.NRG_SEL_INV, gpio.NRG_CF1, gpio.HLW_CF, gpio.HJL_CF, gpio.MCP39F5_TX, gpio.MCP39F5_RX, gpio.MCP39F5_RST, gpio.PN532_TXD, gpio.PN532_RXD, gpio.SM16716_CLK, gpio.SM16716_DAT, gpio.SM16716_SEL, gpio.DI, gpio.DCKI, gpio.CSE7766_TX, gpio.CSE7766_RX, gpio.ARIRFRCV, gpio.ARIRFSEL, gpio.TXD, gpio.RXD, gpio.ROT1A, gpio.ROT1B, gpio.ADC_JOY, gpio.SSPI_MAX31865_CS1, gpio.HRE_CLOCK, gpio.HRE_DATA, gpio.ADE7953_IRQ, gpio.SOLAXX1_TX, gpio.SOLAXX1_RX, gpio.ZIGBEE_TX, gpio.ZIGBEE_RX, gpio.RDM6300_RX, gpio.IBEACON_TX, gpio.IBEACON_RX, gpio.A4988_DIR, gpio.A4988_STP, gpio.A4988_ENA, gpio.A4988_MS1, gpio.OUTPUT_HI, gpio.OUTPUT_LO, gpio.DDS2382_TX, gpio.DDS2382_RX, gpio.DDSU666_TX, gpio.DDSU666_RX, gpio.SM2135_CLK, gpio.SM2135_DAT, gpio.DEEPSLEEP, gpio.EXS_ENABLE, gpio.TASMOTACLIENT_TXD, gpio.TASMOTACLIENT_RXD, gpio.TASMOTACLIENT_RST, gpio.TASMOTACLIENT_RST_INV, gpio.HPMA_RX, gpio.HPMA_TX, gpio.GPS_RX, gpio.GPS_TX, gpio.HM10_RX, gpio.HM10_TX, gpio.LE01MR_RX, gpio.LE01MR_TX, gpio.CC1101_GDO0, gpio.CC1101_GDO2, gpio.HRXL_RX, gpio.ELECTRIQ_MOODL_TX, gpio.AS3935, gpio.ADC_INPUT, gpio.ADC_TEMP, gpio.ADC_LIGHT, gpio.ADC_BUTTON, gpio.ADC_BUTTON_INV, gpio.ADC_RANGE, gpio.ADC_CT_POWER, gpio.WEBCAM_PWDN, gpio.WEBCAM_RESET, gpio.WEBCAM_XCLK, gpio.WEBCAM_SIOD, gpio.WEBCAM_SIOC, gpio.WEBCAM_DATA, gpio.WEBCAM_VSYNC, gpio.WEBCAM_HREF, gpio.WEBCAM_PCLK, gpio.WEBCAM_PSCLK, gpio.WEBCAM_HSD, gpio.WEBCAM_PSRCS, gpio.BOILER_OT_RX, gpio.BOILER_OT_TX, gpio.WINDMETER_SPEED, gpio.KEY1_TC, gpio.BL0940_RX, gpio.TCP_TX, gpio.TCP_RX, gpio.ETH_PHY_POWER, gpio.ETH_PHY_MDC, gpio.ETH_PHY_MDIO, gpio.TELEINFO_RX, gpio.TELEINFO_ENABLE, gpio.LMT01, gpio.IEM3000_TX, gpio.IEM3000_RX, gpio.ZIGBEE_RST, gpio.DYP_RX, gpio.MIEL_HVAC_TX, gpio.MIEL_HVAC_RX, gpio.WE517_TX, gpio.WE517_RX, gpio.AS608_TX, gpio.AS608_RX, gpio.SHELLY_DIMMER_BOOT0, gpio.SHELLY_DIMMER_RST_INV, gpio.RC522_RST, gpio.P9813_CLK, gpio.P9813_DAT, gpio.OPTION_A, gpio.FTC532, gpio.RC522_CS, gpio.NRF24_CS, gpio.NRF24_DC, gpio.ILI9341_CS, gpio.ILI9341_DC, gpio.ILI9488_CS, gpio.EPAPER29_CS, gpio.EPAPER42_CS, gpio.SSD1351_CS, gpio.RA8876_CS, gpio.ST7789_CS, gpio.ST7789_DC, gpio.SSD1331_CS, gpio.SSD1331_DC, gpio.SDCARD_CS, gpio.ROT1A_NP, gpio.ROT1B_NP, gpio.ADC_PH, gpio.BS814_CLK, gpio.BS814_DAT, gpio.WIEGAND_D0, gpio.WIEGAND_D1, gpio.NEOPOOL_TX, gpio.NEOPOOL_RX, gpio.SDM72_TX, gpio.SDM72_RX, gpio.TM1637CLK, gpio.TM1637DIO, gpio.PROJECTOR_CTRL_TX, gpio.PROJECTOR_CTRL_RX, gpio.SSD1351_DC, gpio.XPT2046_CS, gpio.CSE7761_TX, gpio.CSE7761_RX, gpio.VL53LXX_XSHUT1, gpio.MAX7219CLK, gpio.MAX7219DIN, gpio.MAX7219CS, gpio.TFMINIPLUS_TX, gpio.TFMINIPLUS_RX, gpio.ZEROCROSS, gpio.HALLEFFECT, gpio.EPD_DATA, gpio.INPUT, gpio.KEY1_PD, gpio.KEY1_INV_PD, gpio.SWT1_PD, gpio.I2S_OUT_DATA, gpio.I2S_OUT_CLK, gpio.I2S_OUT_SLCT, gpio.I2S_IN_DATA, gpio.I2S_IN_CLK, gpio.I2S_IN_SLCT, gpio.INTERRUPT, gpio.MCP2515_CS, gpio.HRG15_TX, gpio.VINDRIKTNING_RX, gpio.BL0939_RX, gpio.BL0942_RX, gpio.HM330X_SET, gpio.HEARTBEAT, gpio.HEARTBEAT_INV, gpio.SHIFT595_SRCLK, gpio.SHIFT595_RCLK, gpio.SHIFT595_OE, gpio.SHIFT595_SER, gpio.SOLAXX1_RTS, gpio.OPTION_E, gpio.SDM230_TX, gpio.SDM230_RX, gpio.ADC_MQ, gpio.CM11_TXD, gpio.CM11_RXD, gpio.BL6523_TX, gpio.BL6523_RX, gpio.ADE7880_IRQ, gpio.RESET, gpio.MS01, gpio.SDIO_CMD, gpio.SDIO_CLK, gpio.SDIO_D0, gpio.SDIO_D1, gpio.SDIO_D2, gpio.SDIO_D3, gpio.FLOWRATEMETER_SIGNAL, gpio.SENSOR_END

An H-bridge is an electronic circuit that switches the polarity of a voltage applied to a load. These circuits are often used in robotics and other applications to allow DC motors to run forwards or backwards.

See the Berry cookbook for H-bridge control


DAC is limited to specific GPIOs:

  • ESP32: only GPIO 25-26
  • ESP32-S2: only GPIO 17-18
  • ESP32-C3: not supported


> gpio.pin_mode(25, gpio.DAC)   # sets GPIO25 to a DAC pin
> gpio.dac_voltage(25, 1250)    # set voltage to 1250mV
Function returns closes voltage found. In this case it's 1255 for setting to 1250.


DAC can also be used via Esp8266Audio through the ESP32 I2S -> DAC bridge.

class MP3_Player : Driver
  var audio_output, audio_mp3
  def init()
    self.audio_output = AudioOutputI2S(,,,
      0,    #- I2S port -#
      64)    #- number of DMA buffers of 64 bytes each, this is the value required since we update every 50ms -#
    self.audio_mp3 = AudioGeneratorMP3()

  def play(mp3_fname)
    if self.audio_mp3.isrunning()
    var audio_file = AudioFileSourceFS(mp3_fname)
    self.audio_mp3.begin(audio_file, self.audio_output)
    self.audio_mp3.loop()    #- start playing now -#

  def every_50ms()
    if self.audio_mp3.isrunning()

mp3_player = MP3_Player()

energy module~

The energy module provides ways to read current energy counters and values (if you're creating your own automation) or updating the energy counters (if you're writing a driver).

It relies on a new Berry feature that provides a direct mapping between the internal C structure called struct Energy and the energy module in Berry.

For example, if you want to read or update an energy value:

> energy.active_power
> energy.active_power = 460
> energy.active_power

# internally it updates the C value `Energy.active_power[0]` (float)

You don't need to do import energy since Tasmota does it for you at boot.

The special function dumps all current values to a single map. Be aware that the object is very long. Prefer accessing individual attributes instead.

Tasmota Function Parameters and details () -> map
Returns all current values for the energy module. Some values may be unused by the current driver.

List of energy attributes that you can read or write:

Attribute Type Description
float Voltage (V) for main phase or 3 phases
float Current (A) for main phase or 3 phases
float Active Power (W) for main phase or 3 phases
float Reactive Power (W) for main phase or 3 phases
float Power Factor (no unit) for main phase or 3 phases
float Frequency (Hz) for main phase or 3 phases
float (kWh)
start_energy float Total previous energy (kWh)
daily float Daily energy (kWh)
total float Total energy (kWh)
today_delta_kwh uint32 (deca milli Watt hours)
5764 = 0.05764 kWh = 0.058 kWh
today_offset_kwh uint32 (deca milli Watt hours)
today_kwh uint32 (deca milli Watt hours)
period uint32 (deca milli Watt hours)
fifth_second uint8
command_code uint8
phase_count uint8 Number of phases (1,2 or 3)
voltage_common bool Use single voltage
frequency_common bool Use single frequency
use_overtemp bool Use global temperature as overtemp trigger on internal energy monitor hardware
today_offset_init_kwh bool
voltage_available bool Enable if voltage is measured
current_available bool Enable if current is measured
type_dc bool
power_on bool
power_steady_counter uint8 Allow for power on stabilization
min_power_flag bool
max_power_flag bool
min_voltage_flag bool
max_voltage_flag bool
min_current_flag bool
max_current_flag bool
mplh_counter uint16
mplw_counter uint16
mplr_counter uint8
max_energy_state uint8

wire object for I2C~

Berry Scripting provides 2 objects: wire1 and wire2 to communicate with both I2C buses.

Use wire1.scan() and wire2.scan() to scan both buses:

> wire1.scan()

> wire2.scan()

You generally use tasmota.wire_scan() to find a device and the corresponding I2C bus.

MPU6886 on bus 2

> mpuwire = tasmota.wire_scan(0x68, 58)
> mpuwire
<instance: Wire()>
Wire Function Parameters and details
bus read only attribute, 1 or 2
Bus number for this wire instance.
enabled () -> bool
Returns true is the I2C bus is initialized (i.e. GPIOs are defined)
scan () -> array of int
Scan the bus and return all responding addresses. Note: addresses are displayed as decimal ints, not hex.
scan () -> array of int
Scan the bus and return all responding addresses. Note: addresses are displayed as decimal ints, not hex.
detect (addr:int) -> bool
Returns true if the device of address addr is connected to this bus.
read (addr:int, reg:int, size:int) -> int or nil
Read a value of 1..4 bytes from address addr and register reg. Returns nil if no response.
write (addr:int, reg:int, val:int, size:int) -> bool
Writes a value of 1..4 bytes to address addr, register reg with value val. Returns true if successful, false if not.
read_bytes (addr:int, reg:int ,size:int) -> instance of bytes()
Reads a sequence of size bytes from address addr register reg. Result is a bytes() instance or bytes() if not successful.`
write_bytes (addr:int, reg:int, val:bytes) -> nil
Writes the val bytes sequence as bytes() to address addr register reg.

Low-level commands if you need finer control:

Wire Function Parameters and details
_begin_transmission (address:int) -> nil
_end_transmission ([stop:bool]) -> nil
Send stop if stop is true.
_request_from (addr:int, size:int [stop:bool = true]) -> nil
_available () -> bool
_read read() -> int
Reads a single byte.
_write (value:int or s:string) -> nil
Sends either single byte or an arbitrary string.

path module~

A simplified version of os.path module of standard Berry which is disabled in Tasmota because we don't have a full OS.

The default file-system is the ESP32 internal flash. If you have a SD card mounted, it is mapped to the /sd/ subdirectory.


import path
# outputs a list of filenames at the root dir of the SD card
Tasmota Function Parameters and details
path.exists (file_name:string) -> bool
Returns true if the file exists. You don't need to prefix with /, as it will automatically be added if the file does not start with /
path.last_modified (file_name:string) -> int
Returns the timestamp when the file was last modified, or nil if the file does not exist. You don't need to prefix with /, as it will automatically be added if the file does not start with /
path.listdir (dir_name:string) -> list(string)
List a directory, typically root dir "/" and returns a list of filenames in the directory. Returns an empty list if the directory is invalid
path.remove (file_name:string) -> bool
Deletes a file by name, return true if successful
path.format (true:bool) -> bool
Re-formats the LittleFS file system (internal ESP32 flash) and erases all content. The parameter needs to be true as to avoid unwanted calls. Returns true if reformatting was successful.
This is sometimes useful when the file-system becomes unstable or corrupt after multiple re-partitionings.

persist module~

Easy way to persist simple values in Berry and read/write any attribute. Values are written in JSON format in _persist.json file.


import persist
persist.a = 1
persist.b = "foobar"
print(persist) # save to _persist.json

Tasmota Function Parameters and details ()
triggers saving to file system. It is called automatically before a restart but you might want to call it yourself to prevent losing data in case of power loss or crash. writes to flash, so be careful of not calling it too often, or it will cause wearing of flash and reduce its lifetime.
persist.has (param:string) -> bool
returns true or false if the key exists
persist.remove (param:string) -> bool
removes a key or ignores if key doesn't exist
persist.find my_param:string [, "default value"] -> string | bool
returns the value for a key, or nil or the default value. Similar to map.find

introspect module~

Allows to do introspection on instances and modules, to programmatically list attributes, set and get them.

> class A var a,b def f() return 1 end end
> ins=A()
> ins.a = "foo"
> import introspect

> introspect.members(ins)
['b', 'a', 'f']

> introspect.get(ins, "a")

> introspect.set(ins, "a", "bar")

> ins.a
Tasmota Function Parameters and details
introspect.members (nil | instance | module | class) -> list
Returns the list of members of the object. If nil is passed, it returns the list of globals (similar to global module). Note: virtual dynamic members are not listed.
introspect.get (instance | module, name:string) -> any
Returns the member of name name or nil if it does not exist. Note: virtual dynamic members are not yet supported. Note2: classes are not yet supported.
introspect.set (instance | module, name:string, value:any) -> any
Sets the member of name name to value or ignores the call if the member does not exist. Note: virtual dynamic members are not yet supported.
introspect.module (name:string) -> module or nil
Loads a module by name or return nil if not found. The import command works only for static predefined names, this addition makes it dynamic. Contrary to import command, this function does not create an entry in the current scope (i.e. does not either create a global variable with the module's name).
introspect.toptr (any) -> comptr
Converts an int to a comptr pointer. This is sage in Tasmota since pointers and ints are both 32 bits in size.
If argument is a general object, this returns a pointer to the object, and can be converted back to the original object with introspect.fromptr.
introspect.fromptr (comptr) -> any
Converts a comptr pointer to its original object.
Warning: this operation is considered dagerous and should be used with extreme care. If the pointer is invalid or the object was garbage collected, Tasmota will crash.
introspect.ismethod (function or closure) -> bool
Returns true if the function passed as argument is a method of a class, or false if the argument is a simple function or a static method.
This is typically used to check callbacks and make sure that you don't pass a method as argument; methods typically need to be wrapped in a closure to capture the target object.

webclient class~

Class webclient provides an implementation of an HTTP/HTTPS web client and make requests on the LAN or over the Internet.


  • Support HTTP and HTTPS requests to IPv4 addresses and domain names, to arbitrary ports, via a full URL.
  • Support for HTTPS and TLS via BearSSL (which is much lighter than default mbedTLS)
  • HTTPS (TLS) only supports cipher ECDHE_RSA_WITH_AES_128_GCM_SHA256 which is both secure and widely supported
  • Support for URL redirections (tbc)
  • Ability to set custom User-Agent
  • Ability to set custom headers
  • Ability to set Authentication header
  • Support for Chunked encoding response (so works well with Tasmota devices)

The current implementation is based on a fork of Arduino's HttpClient customized to use BearSSL

Current limitations (if you need extra features please open a feature request on GitHub):

  • Only supports text responses (html, json...) but not binary content yet (no NULL char allowed)
  • Maximum response size is 32KB, requests are dropped if larger
  • HTTPS (TLS) is in 'insecure' mode and does not check the server's certificate; it is subject to Man-in-the-Middle attack
  • No access to response headers
  • No support for compressed response


> cl = webclient()
> cl.begin("")
<instance: webclient()>

> r = cl.GET()
> print(r)

> s = cl.get_string()
> print(s)
<b></b>Alternative firmware for ESP32 based devices with web UI,


> cl = webclient()
> cl.begin("")
<instance: webclient()>

> r = cl.GET()
> print(r)

> cl.write_file("")

Main functions:

WebClient Function Parameters and details
begin (url:string) -> self
Set the complete URL, including protocol (http or https), IPv4 or domain name, port... This should be the first call. The connection is not established at this point.
GET () -> result_code:int
Establish a connection to server, send GET request and wait for response header.
Returns the HTTP result code or an error code if negative, 200 means OK.
POST (payload:string or bytes) -> result_code:string
Establish a connection to server, send POST request with payload and wait for response header.
Returns the HTTP result code or an error code if negative, 200 means OK.
read (addr:int, reg:int, size:int) -> int or nil
Read a value of 1..4 bytes from address addr and register reg. Returns nil if no response.
get_size () -> int
Once a connection succeeded (GET or POST), reads the size of the response as returned by the server in headers (before actually reading the content). A value -1 means that the response size is unknown until you read it.
get_string () -> string
Once a connection succeeded (GET or POST), reads the content of the response in a string. The response max size is 32KB, any response larger is dropped. Connection is closed and resources are freed after this call completes.
close () -> nil
Closes the connection and frees buffers. close can be called after GET or POST and is implicitly called by get_string. You don't usually need to use close unless you are only retrieving the result_code for a request and not interested in the content.
write_file (file_name:string) -> int
Downloads the binary content of the resource and stores it on the file system. Returns the number of bytes downloaded or -1 if an error occurred

Request customization:

webclient Function Parameters and details
add_header (name:string, value:string [, first:bool=false [, replace:bool=true]]) -> nil
Sets an arbitrary header for name:value.
first moves the header in the first place, replace replaces a header with the same name or adds one line if false.
set_timeouts (req_timeout:int [, tcp_timeout:int]) -> self
Sets the request timeout in ms and optionally the TCP connection timeout in ms.
set_useragent (useragent:string) -> self
Sets the User-Agent header used in request.
set_auth (auth:string) or (user:string, password:string) -> self
Sets the authentication header, either using pre-encoded string, or standard user/password encoding.

webserver module~

Module webserver provides functions to enrich Tasmota's Web UI. It is tightly linked to Tasmota page layout.

Functions used to add UI elements like buttons to Tasmota pages, and analyze the current request. See above Driver to add buttons to Tasmota UI.

General Function Parameters and details
arg_size () -> int
Returns the number of arguments in the request
arg (arg_name:string or arg_index:int): -> string
Returns the value of the argument either by name or by position number [0..arg_size()-1]. If an argument has multiple values, you need to iterate using ints to get all values
arg_name (arg_index:int) -> string
Returns the name of argument by index [0..arg_size()-1]
has_arg (arg_name:string): -> bool
Checks if an argument with this name exists
check_privileged_access () -> bool
Returns true if the page needs privileged access
content_send (string) -> nil
Sends the HTML content to the client. Tasmota uses Chunked encoding, which means than the content is regularly sent to the client and not buffered in Tasmota's memory
content_button ([button:int]) -> nil
Displays a standard button by code, using Tasmota localization. Possible values are webserver.BUTTON_CONFIGURATION, webserver.BUTTON_INFORMATION, webserver.BUTTON_MAIN, webserver.BUTTON_MANAGEMENT, webserver.BUTTON_MODULE. Default is webserver.BUTTON_MAIN.

Low-level functions if you want to display custom pages and content:

General Function Parameters and details
on (prefix:string, callback:closure [, method:int]) -> nil
Attaches a handler (any closure or function) to a prefix. An optional method argument (defaults to webserver.HTTP_ANY specifies the HTTP methods to be received (ANY, GET, POST, OPTIONS, POST)
WARNING - this should be called only when receiving web_add_handler event. If called before the WebServer is set up and Wi-Fi on, it will crash. For debug purpose, it can be called later when you are sure that Wi-Fi or Ethernet is up.
state () -> int
Returns the internal state of Tasmota web server. Possible values are webserver.HTTP_OFF, webserver.HTTP_USER, webserver.HTTP_ADMIN, webserver.HTTP_MANAGER, webserver.HTTP_MANAGER_RESET_ONLY.
content_start () -> nil
Start response page
content_response (string) -> nil
Sends a response to a XMLHttpRequest
content_send_style () -> nil
Sends the standard Tasmota style
content_flush () -> nil
Flush the buffer and send any buffered content to the client
content_stop () -> nil
End of the response, closes the connection

Module webserver also defines the following constants:

  • Tasmota's web server states: webserver.HTTP_OFF, webserver.HTTP_USER, webserver.HTTP_ADMIN, webserver.HTTP_MANAGER, webserver.HTTP_MANAGER_RESET_ONLY
  • Tasmota's pages: webserver.BUTTON_CONFIGURATION, webserver.BUTTON_INFORMATION, webserver.BUTTON_MAIN, webserver.BUTTON_MANAGEMENT, webserver.BUTTON_MODULE
  • Methods received by handler: webserver.HTTP_ANY, webserver.HTTP_GET, webserver.HTTP_OPTIONS, webserver.HTTP_POST

See the Berry Cookbook for examples.

tcpclient class~

Simple tcp client supporting string and binary transfers:

  • create an instance of the client with var tcp = tcpclient()
  • connect to the server tcp.connect(address:string, port:int [, timeout_ms:int]) -> bool Address can be numerical IPv4 or domain name. Returns true if the connection succeeded. Optional timeout in milliseconds. The default timeout is USE_BERRY_WEBCLIENT_TIMEOUT (2 seconds).
  • check if the socket is connected with tcp.connected()
  • send content with tcp.write(content:string or bytes) -> int. Accepts either a string or a bytes buffer, returns the number of bytes sent. It's your responsibility to resend the missing bytes
  • check if bytes are available for reading tcp.available() -> int. Returns 0 if nothing was received. This is the call you should make in loops for polling.
  • read incoming content as string -> string or as bytes tcp.readbytes() -> bytes. It is best to call tcp.available() first to avoid creating empty response objects when not needed
  • close the socket with tcp.close()
tcpclient Function Parameters and details
connect connect(address:string, port:int [, timeout_ms:int]) -> bool
Connect to addr:port with optional timeout in milliseconds (default 2s).
Returns true if connection was successful, the call is blocking until the connection succeeded to the timeout expired.
connected connected() -> bool
Returns true if the connection was successful and is still valid (not dropped by server or closed by client)
close close() -> nil
Drops the current connection.
write content:string or bytes) -> int
Accepts either a string or a bytes buffer, returns the number of bytes sent. It's you responsibility to resend the missing bytes.
Returns 0 if something went wrong.
available available() -> int
Returns the number of bytes received in buffer and ready to be read.
read read([max_len:int]) -> string
Returns all the bytes received in Rx buffer as string.
Optional max_len parameter limits the number of characters returned, or read as much as possible by default.
readbytes read([max_bytes:int]) -> bytes()
Returns all the bytes received in Rx buffer as bytes().
Optional max_bytes parameter limits the number of bytes returned, or read as much as possible by default.

Full example:

tcp = tcpclient()
tcp.connect("", 80)
print("connected:", tcp.connected())
s= "GET / HTTP/1.0\r\n\r\n"
print("available1:", tcp.available())
print("available1:", tcp.available())
r =

tcpserver class~

Simple tcp server (socket) listening for incoming connection on any port.

  • create an instance of the tcpserver on a specific port with s = tcpserver(8888)
  • periodically call s.hasclient() to know if a new client has connected
  • if the previous returned true, call var c = s.accept() to accept the connection. It returns an instance of tcpclient as above; it responds to the same APIs as outgoing TCP connection and allows text and binary transfers.
  • you can call c.close() to close the connection, or call c.connected() to know if it's still connected (i.e. the client hasn't closed the connection on their side)
  • close the server with s.close(). This will prevent the server from receinving any new connection, but existing connections are kept alive.
tcpserver Function Parameters and details
constructor tcpserver(port:int) -> nit
Opens a socket on port and starts lisenting to new incoming connections. If the server can't open the socket (ex: it is already in use) an exception is raised
hasclient hasclient() -> bool
Returns true if a new client connected to the socket, in such case you should call accept(). You need to call this method regularly (ex: in event loop or fast_loop)
accept accept() -> instance:tcpclient or nil
Returns an instance of tcpclient for the new incoming connection, or raise an exception if no connection is available. You should call hasclient() returning true before calling accept().

Full example:

> s = tcpserver(8888)    # listen on port 8888
> s.hasclient()

# in parallel connect on this port with `nc <ip_address> 8888`

> s.hasclient()
true               # we have an incoming connection
> c = s.accept()
> c
<instance: tcpclient()>

# send 'foobar' from the client

# send 'foobar2' again from the client
> c.readbytes()

> c.close()
# this closes the connection

udp class~

Class udp provides ability to send and received UDP packets, including multicast addresses.

You need to create an object of class udp. Such object can send packets and listen to local ports. If you don't specify a local port, the client will take a random source port. Otherwise the local port is used as source port.

When creating a local port, you need to use udp->begin(<ip>, <port)>. If <ip> is empty string "" then the port is open on all interfaces (wifi and ethernet).

General Function Parameters and details
udp() udp() -> <instance udp>
Creates an instance of udp class.
begin begin(interface:string, port:int) -> bool
Create a UDP listener and sender on a specific interface (IP address) or on all interfaces if interface is an empty string
Listen on a specific port number, or set 0 to choose a random free port for sending only
Returns true if successful.
begin_multicast begin(ip:string, port:int) -> bool
Create a UDP listener and sender on interface ip and port. ip must be a multicast address.
Returns true if successful.
close close() -> bil
Closes UDP listener and sender, and frees resources. You can't send or receive anymore with this instance.
send send(addr:string, port:int, payload:bytes) -> bool
Sends a packet to address addr, port port and message as bytes() buffer.
Returns true if successful.
send_multicast send(payload:bytes) -> bool
Sends a payload as bytes() buffer to the multicast address. begin_multicast() must have been previously called.
Returns true if successful.You can also send a multicast packet with send if you specify the multicast address and port.
read read() -> bytes() or nil
Reads any received udp packet as bytes() buffer, or nil if no packet was received.
remote_ip remote_ip (string or nil)
Instance variable containing the remote ip (as string) from the last successful read() command.
remote_port remote_port (int or nil)
Instance variable containing the remote port (as int) from the last successful read() command.

Sending udp packets~

> u = udp()
> u.begin("", 2000)    # listen on all interfaces, port 2000
> u.send("", 2000, bytes("414243"))   # send 'ABC' to, source port is 2000

Receive udp packets~

You need to do polling on udp->read(). If no packet was received, the call immediately returns nil.

> u = udp()
> u.begin("", 2000)    # listen on all interfaces, port 2000
>     # if no packet received, returns `nil`

>     # if no packet received, returns `nil`
bytes("414243")    # received packet as `bytes()`

Simple UDP server printing received packets~

class udp_listener
  var u
  def init(ip, port)
    self.u = udp()
    print(self.u.begin_multicast(ip, port))
  def every_50ms()
    import string
    var packet =
    while packet != nil
      tasmota.log(string.format(">>> Received packet ([%s]:%i): %s", self.u.remote_ip, self.u.remote_port, packet.tohex()), 2)
      packet =

# listen on port 2000 for all interfaces
# udp_listener("", 2000)

Send and receive multicast~

IPv4 example, using the udp_listener listener above.

On receiver side:

udp_listener("", 2000)

On sender side:

u = udp()
u.begin_multicast("", 2000)

# alternatively
u = udp()
u.begin("", 0)      # send on all interfaces, choose random port number
u.send("", 2000, bytes().fromstring("world"))

The receiver will show:

>>> Received packet ([192.168.x.x]:2000): 68656C6C6F
>>> Received packet ([192.168.x.x]:64882): 776F726C64

This works the same with IPv6 using an address like "FF35:0040:FD00::AABB"

mdns module~

Module import mdns support for mdns (Multicast DNS, aka Bonjour protocol) announces. This is needed for Matter Wifi support.

This feature requires #define USE_DISCOVERY compile option (not included in standard builds).

Example (announce of a Matter Wifi device):

import mdns
mdns.add_service("_matterc","_udp", 5540, {"VP":"65521+32768", "SII":5000, "SAI":300, "T":1, "D":3840, "CM":1, "PH":33, "PI":""})
General Function Parameters and details
start mdns.start([hostname: string]) -> nil
Start or restart mdns, specify a new hostname if needed or implicitly use tasmota.hostname() if none provided (default)
stop mdns.stop() -> nil
Free all mdns resources
set_hostname mdsn.set_hostname(hostname:string) -> nil
Change the hostname
add_service mdns.add_service(service:string, proto:string, port:int, txt:map) -> nil
Add a service declaration using the current hostname as instance name, and specify TXT fields as a map

Addressable leds (WS2812, SK6812)~

There is native support for addressable leds via NeoPixelBus, with support for animations. Currently supported: WS2812, SK6812.

Details are in Berry leds

serial class~

The serial class provides a low-level interface to hardware UART. The serial GPIOs don't need to be configured in the template.


# gpio_rx:4 gpio_tx:5
ser = serial(4, 5, 9600, serial.SERIAL_7E1)

ser.write(bytes(203132))   # send binary 203132
ser.write(bytes().fromstring("Hello))   # send string "Hello"

msg =   # read bytes from serial as bytes
print(msg.asstring())   # print the message as string
Tasmota Function Parameters and details
serial (constructor) serial(gpio_rx:int, gpio_tx:int, baud:int [, mode:int])
Creates a serial object
gpio_rx receive GPIO (or -1 if transmit only)
gpio_tx transmit GPIO (or -1 if receive only)
baud speed, ex: 9600, 115200
mode serial message format, default is serial.SERIAL_8N1 (8 bits, no parity, 1 stop bit).
Other mode values are described below.
write write(val:int || bytes()) -> bytes_sent:int
Send either a single byte if argument is int, or send a binary message from a bytes() object.
The methods blocks until all messages are sent to the UART hardware buffer; they may not all have been sent over the wire
read read(void) -> bytes()
Read all bytes received in the incoming buffer. If the buffer is empty, returns an empty bytes() object
flush flush(void) -> void
Flushes all buffers. Waits for all outgoing messages to be sent over the wire and clear the incoming buffer.
available available(void) -> int
Returns the number of incoming bytes in the incoming buffer, 0 in none.


display module~

The display module provides a simple API to initialize the Universal Display Driver with data provided as a string. It is used by autoconf mechanism.

Tasmota Function Parameters and details
start display.start(displayini:string) -> nil
Initializes the Universal Display Driver with the string provided as argument, similar to content in display.ini. It is typically read from a file in the file-system.
started display.started() -> bool
Returns true if display is already initialized, false if not started.
dimmer display.started([dim:int]) -> int
Sets the dimmer of display, value 0..100. If 0 then turn off display. If no arg, read the current value.
driver_name display.driver_name() -> string
Returns the Display driver name as specified in display.ini
touch_update display.touch_update(touches:int, raw_x:int, raw_y:int, gesture:int) -> nil
Sets the last Touch Screen update values to be passed to LVGL. This allows an external touchscreen driver to periodically update the touch information.
touches: number of touches (0 = no touch, 1 = screen touched). Multiple touch is not supported
raw_x and raw_y = coordinates before conversion (resistive touch screens need conversion)
gesture: type of gesture. 0 = no gesture, 16 = move up, 17 = move down, 18 = move left, 19 = move right, 32 = zoom in, 33 = zoom out.

uuid module~

The uuid module allows to generate uuid4 random ids.

> import uuid
> uuid.uuid4()
Tasmota Function Parameters and details
uuid4 uuid.uuid4() -> string
Generates a uuid4 random id as string.

crc module~

The crc module allows to compute crc32/16/8 from bytes() arrays.

> import crc
> crc.crc32(0xFFFFFFFF, bytes("AABBCC"))
> crc.crc16(0xFFFF, bytes("AABBCC"))
> crc.crc8(0xFF, bytes("AABBCC"))
Tasmota Function Parameters and details
crc32 crc.crc32(crc:int, payload:bytes) -> int
Compute crc32 from an initial value and a bytes() buffer
crc16 crc.crc16(crc:int, payload:bytes) -> int
Compute crc16 from an initial value and a bytes() buffer
crc8 crc.crc8(crc:int, payload:bytes) -> int
Compute crc8 from an initial value and a bytes() buffer

log_reader class~

The log_reader class allows you to read and potentially parse the Tasmota logs. It keeps track of what logs were already read in the past and feeds you with new log lines if some are available. It is for example used by the LVGL tasmota_log widget to display logs on a display.

Note: calling log_reader can be expensive in string allocations, and adds pressure on the garbage collector. Use wisely.


var lr = log_reader()

# do this regularly
var ret = lr.get_log(2)    # read at log level 2
if ret != nil
  var lines = r.split('\n')  # extract as a list of lines
  # do whatever you need
Tasmota Function Parameters and details
log_reader() log_reader(void) -> instance(log_reader)
Instantiate a new log_reader. Multiple readers can coexist and they each keep track of already read log lines
get_log get_log(log_level:int) -> string or nil
Returns new log lines as a big string object. Lines are separated by \n. Returns nil if no new logs are available.
log_level can be 0..4 and specifies the highest log level that we be reported (it is usually wise to start with 2). Higher log level will be reported only if they are recorded, i.e. there is at least one logger that asks for it. This class does not cause log-level 4 to be recorded if none other loggers are recording them (weblog, mqttlog or seriallog).

ULP module~

The ULP module exposes the third computing unit of the ESP32, which is a simple finite state machine (FSM) that is designed to perform measurements using the ADC, temperature sensor and even external I2C sensors. This small ultra low power coprocessor can run in parallel to the main cores and in deep sleep mode, where it is capable to wake up the system, i.e. in reaction to sensor measurements. The binding to Berry consists of some lightweight wrapper functions and the communication with the main cores works by accessing the RTC_SLOW_MEM from both sides, which is the same way as in any other ESP32 ULP project.

# simple LED blink example
import ULP
ULP.wake_period(0,500000) # off time
ULP.wake_period(1,200000) # on time 
c = bytes("756c70000c006c00000000001000008000000000000000000000000010008072010000d0e5af2c72340040802705cc190005681d10008072e1af8c720100006821008072040000d0120080720800207004000068010005825c0000800405681d00000092680000800505681d0100009268000080000000b0")
Tasmota Function Parameters and details
run -> nil
Execute ULP prgramm
load ULP.load(code:bytes) -> nil
Load ULP code from a bytes() buffer into memory
set_mem ULP.set_mem(addr:int, value:int) -> nil
Set memory position in RTC_SLOW_MEM to value. Address and Value are 32-bit!!
get_mem ULP.set_mem(addr:int) -> int16_t
Get value from memory position in RTC_SLOW_MEM. By hardware design only the lower 16-bit are usable, so this function already masks out the upper 16-bit
gpio_init ULP.gpio_init(pin:int, mode:int) -> pin:int
Makes a valid GPIO pin accessible to the ULP and sets the mode according to the enum 'rtc_gpio_mode_t', returns the same pin, but translated to the RTC system, which is the numbering scheme in the assembly code
adc_config ULP.adc_config(channel:int, attenuation:int, width:int) -> nil
Configures ADC pin usage for the ULP according to the enums ' adc1_channel_t', 'adc_atten_t' and 'adc_bits_width_t'
wake_period ULP.wake_period(register:int, time:int) -> nil
Configures one of 5 (0..4) wake timer registers with the time value in microseconds
sleep ULP.wake_period([time:int]) -> nil
Starts deep sleep mode and allow wake up by the ULP, with an optional time value in seconds an additional wake up timer gets started

More infos (including suggestions for a toolchain) on the ULP page.

re regex module~

Use with import re.

There are two ways to use regex, first is to call directly the module which triggers a compilation of the regex at each call. The second one is to pre-compile the regex once into an object which is much more efficient if you need to use the regex multiple times. Any error in the compilation of the regex pattern yields an exception.

> import re
>"a.*?b(z+)", "zaaaabbbccbbzzzee")
['aaaabbbccbbzzz', 'zzz']
> re.match("a.*?b(z+)", "aaaabbbccbbzzzee")
['aaaabbbccbbzzz', 'zzz']

> rr = re.compile("/")
> rr
<instance: re_pattern()>

> rr.split("foo/bar//baz")
> rr.split("/b")
Tasmota Function Parameters and details
search, payload:string) -> list of strings
Returns the list of matches, or empty list of no match
match re.match(pattern:string, payload:string) -> list of strings
Returns the list of matches, or empty list of no match. The difference with search is that match must match the entire string (from beginning to end).
split, payload:string) -> list of strings
Returns the list of strings from split, or a list with a single element containing the entire string if no match
compile re.compile(pattern:string) -> instance of <re_pattern>
Compiles the regex into a reusable faster bytecode. You can then call the following methods:
search(), match(), split() similarly to the module's functions.

Note: for match and search, the first element in the list contains the global match of the pattern. Additional elements correspond to the sub-groups (in parenthesis).

The regex engine is based on re1.5 also used in Micropython.

crypto module~

Module import crypto support for common cryptographic algorithms.

Currently supported algorithms:

  • AES CTR 256 bits - requires #define USE_BERRY_CRYPTO_AES_CTR
  • AES GCM 256 bits
  • Elliptic Curve C25519 - requires #define USE_BERRY_CRYPTO_EC_C25519
  • Elliptic Curve P256 (secp256r1) - requires #define USE_BERRY_CRYPTO_EC_P256
  • HKDF key derivation with HMAC SHA256 - requires #define USE_BERRY_CRYPTO_HKDF_SHA256
  • HMAC SHA256
  • MD5
  • PKKDF2 with HMAC SHA256 key derivation - requires #define USE_BERRY_CRYPTO_PBKDF2_HMAC_SHA256
  • SHA256

crypto.AES_CTR class~

Encrypt and decrypt, using AES CTR (Counter mode) with 256 bits keys.

General Function Parameters and details
init AES_CTR.init(secret_key:bytes(32)) -> instance
Initialise AES CTR instance with secret_key (256 bits) and iv (initialization vector or nonce, 96 bits)
encrypt encrypt(ciphertext:bytes, iv:bytes(12), cc:int) -> bytes
Encrypt the ciphertext. The iv (Initialization Vector) must be 12 bytes, it can be the concatenation of 4 bytes Nonce and 8 bytes iv. cc is the counter (4 bytes) incremented for each block of 16 bytes.
Note: the last counter value is not returned, so it is advised to encrypt all data at once.
decrypt decrypt(ciphertext:bytes, iv:bytes(12), cc:int) -> bytes
Identical to encrypt above.

Test vectors from

# Test case from
import crypto
key = bytes("F6D66D6BD52D59BB0796365879EFF886C66DD51A5B6A99744B50590C87A23884")
iv = bytes("00FAAC24C1585EF15A43D875")
cc = 0x000001
aes = crypto.AES_CTR(key)
plain = bytes("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F")
cipher = aes.encrypt(plain, iv, cc)
assert(cipher == bytes("F05E231B3894612C49EE000B804EB2A9B8306B508F839D6A5530831D9344AF1C"))
plain2 = aes.decrypt(cipher, iv, cc)
assert(plain == plain2)

crypto.AES_GCM class~

Encrypt, decrypt and verify, using AES GCM (Gallois Counter Mode) with 256 bits keys.

General Function Parameters and details
init AES_GCM.init(secret_key:bytes(32), iv:bytes(12)) -> instance
Initialise AES GCM instance with secret_key (256 bits) and iv (initialization vector or nonce, 96 bits)
encrypt encrypt(ciphertext:bytes) -> bytes
Encrypt the ciphertext. Can be called multiple times, the tag is updated accordingly
decrypt decrypt(ciphertext:bytes) -> bytes
Decrypt the ciphertext. Can be called multiple times, the tag is updated accordingly
tag tag() -> bytes
Compute the verification tag for the object encrypted or decrypted (128 bits).

Example taken from

import crypto

key = bytes('233f8ce4ac6aa125927ccd98af5750d08c9c61d98a3f5d43cbf096b4caaebe80')
ciphertext = bytes('1334cd5d487f7f47924187c94424a2079656838e063e5521e7779e441aa513de268550a89917fbfb0492fc')
iv = bytes('2f3849399c60cb04b923bd33265b81c7')
authTag = bytes('af453a410d142bc6f926c0f3bc776390')

# decrypt ciphertext with key and iv
aes = crypto.AES_GCM(key, iv)
plaintext = aes.decrypt(ciphertext)
# 'Message for AES-256-GCM + Scrypt encryption'

tag = aes.tag()
print(tag == authTag)
# true

crypto.EC_C25519 class~

Provides Elliptic Curve C25519 Diffie-Hellman key agreement. Requires #define USE_BERRY_CRYPTO_EC_C25519

General Function Parameters and details
public_key crypto.EC_C25519().public_key(secret_key:bytes(32)) -> bytes(32)
Computes the public key given a random private key.
shared_key crypto.EC_C25519().shared_key(our_private_key:bytes(32), their_public_key:bytes(32)) -> bytes(32)
Compute a shared key (Diffie-Hellman) using our private key and the other party's public key. The other party will compute the same shared key using their private key and our pubic key.

Example from test vectors

import crypto

# alice side
alice_priv_key = bytes("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a")
alice_pub_key = bytes("8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a")
assert(crypto.EC_C25519().public_key(alice_priv_key) == alice_pub_key)

# bob side
bob_priv_key = bytes("5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb")
bob_pub_key = bytes("de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f")
assert(crypto.EC_C25519().public_key(bob_priv_key) == bob_pub_key)

# shared key computed by alice
ref_shared_key = bytes("4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742")
alice_shared_key = crypto.EC_C25519().shared_key(alice_priv_key, bob_pub_key)
bob_shared_key = crypto.EC_C25519().shared_key(bob_priv_key, alice_pub_key)
assert(alice_shared_key == ref_shared_key)
assert(bob_shared_key == ref_shared_key)

crypto.EC_P256 class~

Provides Elliptic Curve Prime256 (secp256r1) Diffie-Hellman key agreement and various functions on P256 curve. Requires #define USE_BERRY_CRYPTO_EC_P256

General Function Parameters and details
public_key crypto.EC_P256().public_key(secret_key:bytes(32)) -> bytes(65)
Computes the public key given a random private key. The result is uncompressed point coordinates starting with 0x04 (65 bytes in total)
shared_key crypto.EC_P256().shared_key(our_private_key:bytes(32), their_public_key:bytes(65)) -> bytes(32)
Compute a shared key (Diffie-Hellman) using our private key and the other party's public key. The other party will compute the same shared key using their private key and our pubic key.
The result is actually the X coordinate of the multiplication of the points coordinates of the public key, and a large number (private key)
Specific Functions Parameters and details
mod crypto.EC_P256().mod(data:bytes()) -> bytes(32)
Computes the modulus of an arbitrary large number. The modulus is done towards the order of the curve.
neg crypto.EC_P256().neg(data:bytes(32)) -> bytes(32)
-x mod p or p - x if x is lower than p
Computes the opposite (negate) of a number modulus the order of the curve (it's actuall modulus - data).
mul crypto.EC_P256().mul(x:bytes(), A:bytes(65)) -> bytes(65)
x * A
Computes multiplication of a number and a point on the curve.
x needs to be smaller than p, use mod() if not sure
The function checks that the point A is on the curve, or raises an error
muladd crypto.EC_P256().muladd(x:bytes(), A:bytes(65), y:bytes(), B:bytes(65)) -> bytes(65)
x * A + y * B
x and y need to be smaller than p, use mod() if not sure
The function checks that the points A and B are on the curve, or raises an error
If B is empty bytes(), the Generator P of the curve is used instead.


import crypto
priv = bytes("f502fb911d746b77f4438c674e1c43650b68285dfcc0583c49cd6ed88f0fbb58")
p = crypto.EC_P256()
pub = p.public_key(priv)
assert(pub == bytes("04F94C20D682DA29B7E99985D8DBA6ABEA9051D16508742899835098B1113D3D749466644C47B559DB184556C1733C33E5788AE250B8FB45F29D4CF48FF752C1ED"))

import crypto
priv = bytes("4E832960415F2B5FA2B1FDA75C1A8F3C84BAEB189EDC47211EF6D27A21FC0ED8")
p = crypto.EC_P256()
pub = p.public_key(priv)
assert(pub == bytes("042166AE4F89981472B7589B8D79B8F1244E2EEE6E0A737FFBFED2981DA3E193D6643317E054D2A924F2F56F1BF4BECA13192B27D8566AF379FBBF8615A223D899"))

import crypto
p = crypto.EC_P256()
priv_A = bytes("f502fb911d746b77f4438c674e1c43650b68285dfcc0583c49cd6ed88f0fbb58")
pub_A = bytes("04F94C20D682DA29B7E99985D8DBA6ABEA9051D16508742899835098B1113D3D749466644C47B559DB184556C1733C33E5788AE250B8FB45F29D4CF48FF752C1ED")
priv_B = bytes("4E832960415F2B5FA2B1FDA75C1A8F3C84BAEB189EDC47211EF6D27A21FC0ED8")
pub_B = bytes("042166AE4F89981472B7589B8D79B8F1244E2EEE6E0A737FFBFED2981DA3E193D6643317E054D2A924F2F56F1BF4BECA13192B27D8566AF379FBBF8615A223D899")

shared_1 = p.shared_key(priv_A, pub_B)
shared_2 = p.shared_key(priv_B, pub_A)
assert(shared_1 == shared_2)

crypto.HKDF_SHA256 class~

Provides HKDF using HMAC SHA256 key derivation. Turns 'ikm' (input keying material) of low entropy and creates a pseudo random key. Requires #define USE_BERRY_CRYPTO_HKDF_SHA256

General Function Parameters and details
derive crypto.HKDF_SHA256().derive(ikm:bytes(), salt:bytes(), info:bytes(), out_bytes:int) -> bytes(out_bytes)
Computes a key derivation function
ikm is the input keying material, typically a password
salt can be empty
info can be empty and is used to create multiple derived keys
out_bytes indicates the number of bytes to generate (between 1 and 256)

Test vectors from

import crypto

# Test Case 1
hk = crypto.HKDF_SHA256()
ikm = bytes("0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B")
salt = bytes("000102030405060708090A0B0C")
info = bytes("F0F1F2F3F4F5F6F7F8F9")
k = hk.derive(ikm, salt, info, 42)
assert(k == bytes("3CB25F25FAACD57A90434F64D0362F2A2D2D0A90CF1A5A4C5DB02D56ECC4C5BF34007208D5B887185865"))

# Test Case 2
hk = crypto.HKDF_SHA256()
ikm  = bytes("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f")
salt = bytes("606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf")
info = bytes("b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff")
k = hk.derive(ikm, salt, info, 82)
assert(k == bytes("b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87"))

# Test Case 3
hk = crypto.HKDF_SHA256()
ikm  = bytes("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b")
salt = bytes()
info = bytes()
k = hk.derive(ikm, salt, info, 42)
assert(k == bytes("8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8"))

crypto.PBKDF2_HMAC_SHA256 class~

Provides PBKDF2 using HMAC SHA256 key derivation. Turns a password into a hash.

General Function Parameters and details
derive crypto.PBKDF2_HMAC_SHA256().derive(password:bytes(), salt:bytes(), iterations:int, out_bytes:int) -> bytes(out_bytes)
Computes a key derivation function
password is the input keying material
salt can be empty bytes()
iterations counts the number of iterations of HMAC, limited to 10000 to make computation short enough for ESP32
out_bytes indicates the number of bytes to generate (between 1 and 256)

Test vectors from

import crypto
pb = crypto.PBKDF2_HMAC_SHA256()

assert(pb.derive("password", "salt", 1, 20) == bytes('120fb6cffcf8b32c43e7225256c4f837a86548c9'))

assert(pb.derive("password", "salt", 2, 20) == bytes('ae4d0c95af6b46d32d0adff928f06dd02a303f8e'))

assert(pb.derive("password", "salt", 3, 20) == bytes('ad35240ac683febfaf3cd49d845473fbbbaa2437'))

assert(pb.derive("password", "salt", 4096, 20) == bytes('c5e478d59288c841aa530db6845c4c8d962893a0'))

assert(pb.derive("passwd", "salt", 1, 128) == bytes('55AC046E56E3089FEC1691C22544B605F94185216DDE0465E68B9D57C20DACBC49CA9CCCF179B645991664B39D77EF317C71B845B1E30BD509112041D3A19783C294E850150390E1160C34D62E9665D659AE49D314510FC98274CC79681968104B8F89237E69B2D549111868658BE62F59BD715CAC44A1147ED5317C9BAE6B2A'))

crypto.SHA256 class~

Provides SHA256 hashing function

General Function Parameters and details
init HMAC_SHA256.init() -> instance
Initialise SHA256 hashing function
update update(data:bytes) -> self
Add content to the hash. Calls can be chained.
out out() -> bytes(32)
Output the value of the hash

Example test vectors from

import crypto
h = crypto.SHA256()

# SHA256 of empty message
assert(h.out() == bytes("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"))

# (first 16 bytes of RC4 keystream where the key = 0)
assert(h.out() == bytes("067c531269735ca7f541fdaca8f0dc76305d3cada140f89372a410fe5eff6e4d"))

crypto.HMAC_SHA256 class~

Provides HMAC SHA256 hashing function

General Function Parameters and details
init HMAC_SHA256.init(key:bytes) -> instance
Initialise HMAC_SHA256 hashing function with a provided key
update update(data:bytes) -> self
Add content to the hash. Calls can be chained
out out() -> bytes(32)
Output the value of the hash

Test case from

import crypto
key = bytes("4a656665")
msg = bytes("7768617420646f2079612077616e7420666f72206e6f7468696e673f")
h = crypto.HMAC_SHA256(key)
hmac = h.out()
assert(hmac == bytes("5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"))

crypto.MD5 class~

Provides MD5 hashing function.

General Function Parameters and details
init MD5.init() -> instance
Initialise MD5 hashing function
update update(data:bytes) -> self
Add content to the hash. Calls can be chained.
finish finish() -> bytes(16)
Finish the MD5 calculation and output the result (16 bytes)

Test vector:

import crypto
h = crypto.MD5()
t = bytes().fromstring("The quick brown fox jumps over the lazy dog")
m = h.finish()
assert(m == bytes("9e107d9d372bb6826bd81d3542a419d6"))

Compiling Berry~

Berry is included if the following is defined in user_config_override.h:

#define USE_BERRY

Other options that can be changed:

Option Description
#define USE_BERRY_PSRAM Use PSRAM to allocate memory instead of main RAM. If no PSRAM is connected, this option has no effect.
Enabled by default
#define USE_BERRY_DEBUG Provide additional information in case of a Berry exception, adding line number in the call chain. This feature adds ~8% of memory consumption to Berry compiled code.
Disabled by default
#define USE_WEBCLIENT Enable the webclient module allowing to do HTTP requests.
Enabled by default
#define USE_WEBCLIENT_HTTPS Adds support for HTTPS to webclient. This feature adds ~45KB of Flash space for TLS support.
Disabled by default
#define USE_BERRY_WEBCLIENT_USERAGENT "TasmotaClient" Specifies the default User-Agent field sent by webclient. Can be changed on a per request basis.
#define USE_BERRY_WEBCLIENT_TIMEOUT 5000 Specifies the default timeout in millisecond for webclient. Can be changed on a per request basis.

Berry Cookbook~

Find complete examples and use scenarios of Berry in the Berry Cookbook