!!! info "Berry Scripting is included in all Tasmota32 builds. It is not supported on ESP82xx"
!!! info "If you plan to code in Berry, you should enable #define USE_BERRY_DEBUG
which will give you much more details when coding"
See full examples in the Berry Cookbook
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.
!!! tip "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 I^2^C drivers
- build complete Tasmota drivers
- integrate native libraries like
lvgl
see LVGL
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.
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.
Click on Configuration then Berry Scripting Console and enjoy the colorful Berry console, also called REPL (Read-Eval-Print-Loop).
!!! tip "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.
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 ++arrow-up++ and ++arrow-down++ to navigate through history of previous commands.
> 1+1
2
> 2.0/3
0.666667
> 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
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.
!!! example "2 relays and 1 light"
```python
> tasmota.get_power()
[false, true, false]
> tasmota.set_power(0, true)
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
, channles
, 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]}
# chaning 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)
end
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 |
!!! example "Dimmer rule"
Define the function and add a rule to Tasmota where the function runs if Dimmer value is more than 50
```python
> def dimmer_over_50()
print("The light is bright")
end
tasmota.add_rule("Dimmer>50", dimmer_over_50)
```
```python
> 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)
i=string.find(trigger,"#A")
tr=string.split(trigger,i+2)
adc=number(tr[1])
print("value of adc",adc," is ",value)
end
tasmota.add_rule("ANALOG#A1",rule_adc)
tasmota.add_rule("ANALOG#A2",rule_adc)
Another way to address the same using anonymous functions created dynamically
def rule_adc(adc, value)
print("value of adc",adc," is ",value)
end
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()
9977038
!!! example "Sending a timer is as easy as tasmota.set_timer(<delay in ms>,<function>)
"
```python
> def t() print("Booh!") end
> tasmota.set_timer(5000, t)
[5 seconds later]
Booh!
```
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.
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.
!!! note "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("file.be")
.
You can easily create a complete Tasmota driver with Berry.
As a convenience, a skeleton class Driver
is provided. 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 meaninful to a small subset of events:
every_second()
: called every secondevery_100ms()
: called every 100ms (i.e. 10 times per second)every_50ms()
: called every 50ms (i.e. 20 times per second)web_sensor()
: display sensor information on the Web UIjson_append()
: display sensor information in JSON format for TelePeriod reportingweb_add_button()
: (deprecated) synonym ofweb_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 pageweb_add_handler()
: called when Tasmota web server started, and the right time to callwebserver.on()
to add handlersbutton_pressed()
: called when a button is pressedweb_sensor()
: send sensor information as JSON or HTMLsave_before_restart()
: called just before a restartset_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:
Method 1: create a sub-class
Define a sub-class of the Driver
class and override methods.
class MyDriver : Driver
def every_second()
# do something
end
end
d1 = MyDriver()
tasmota.add_driver(d1)
Method 2: redefine the attribute with a function
Just use the Driver
class and set the attribute to a new function:
d2 = Driver()
d2.every_second = def ()
# do something
end
tasmota.add_driver(d2)
Logs a message to the Tasmota console. Optional second argument is log_level (0..4), default is 2
LOG_LEVEL_INFO
.
!!! example
```
> log("A")
A
```
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.
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()
.
A root level object called tasmota
is created and contains numerous functions to interact with Tasmota.
See examples in the Berry-Cookbook
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.
Use with care and only if you know what you are doing.
The construct is to use tasmota.global
or tasmota.settings
to read or write attributes.
!!! warning "You can do bad things with these features"
Value | Details |
---|---|
tasmota.global.sleep | Current sleep value |
tasmota.settings.sleep | Sleep value stored in flash |
Module light
is automatically imported via a hidden import light
command.
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. gpio.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.
Any internal error or using unsupported GPIO yields an Berry exception.
??? note "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.VL53L0X_XSHUT1`, `gpio.MAX7219CLK`, `gpio.MAX7219DIN`, `gpio.MAX7219CS`, `gpio.TFMINIPLUS_TX`, `gpio.TFMINIPLUS_RX`, `gpio.ZEROCROSS`, `gpio.HALLEFFECT`, `gpio.EPD_DATA`, `gpio.INPUT`, `gpio.SENSOR_END`
DAC is limited to specific GPIOs:
- ESP32: only GPIO 25-26
- ESP32-S2: only GPIO 17-18
- ESP32-C3: not supported
!!! example
> gpio.pin_mode(25, gpio.DAC) # sets GPIO25 to a DAC pin > gpio.dac_voltage(25, 1250) # set voltage to 1250mV 1255
Function returns closes voltage found. In this case its 1255 for setting to 1250.
DAC can also be used via Esp8266Audio
through the ESP32 I2S -> DAC bridge.
??? example ```python class MP3_Player : Driver var audio_output, audio_mp3 def init() self.audio_output = AudioOutputI2S( gpio.pin(gpio.I2S_OUT_CLK), gpio.pin(gpio.I2S_OUT_SLCT), gpio.pin(gpio.I2S_OUT_DATA), 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() end
def play(mp3_fname)
if self.audio_mp3.isrunning()
self.audio_mp3.stop()
end
var audio_file = AudioFileSourceFS(mp3_fname)
self.audio_mp3.begin(audio_file, self.audio_output)
self.audio_mp3.loop() #- start playing now -#
end
def every_50ms()
if self.audio_mp3.isrunning()
self.audio_mp3.loop()
end
end
end
mp3_player = MP3_Player()
tasmota.add_driver(mp3_player)
mp3_player.play("/pno-cs.mp3")
```
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
0
> energy.active_power = 460
> energy.active_power
460
# 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 energy.read()
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 |
---|---|
energy.read() | () -> 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 |
---|---|---|
voltage voltage_2 voltage_3 |
float | Voltage (V) for main phase or 3 phases |
current current_2 current_3 |
float | Current (A) for main phase or 3 phases |
active_power active_power_2 active_power_3 |
float | Active Power (W) for main phase or 3 phases |
reactive_power reactive_power_2 reactive_power_3 |
float | Reactive Power (W) for main phase or 3 phases |
power_factor power_factor_2 power_factor_3 |
float | Power Factor (no unit) for main phase or 3 phases |
frequency frequency_2 frequency_3 |
float | Frequency (Hz) for main phase or 3 phases |
export_active export_active_2 export_active_3 |
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 | |
data_valid data_valid_2 data_valid_3 |
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 | |
Below if for Energy Margin Detection | ||
power_history_0 power_history_0_2 power_history_0_3 power_history_1 power_history_1_2 power_history_1_3 power_history_2 power_history_2_2 power_history_2_3 |
uint16 | |
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 | |
Below if for Energy Power Limit | ||
mplh_counter | uint16 | |
mplw_counter | uint16 | |
mplr_counter | uint8 | |
max_energy_state | uint8 |
Berry Scripting provides 2 objects: wire1
and wire2
to communicate with both I^2^C buses.
Use wire1.scan()
and wire2.scan()
to scan both buses:
> wire1.scan()
[]
> wire2.scan()
[140]
You generally use tasmota.wire_scan()
to find a device and the corresponding I^2^C bus.
!!! example "MPU6886 on bus 2"
```
> mpuwire = tasmota.wire_scan(0x68, 58)
> mpuwire
<instance: Wire()>
```
Low-level commands if you need finer control:
A simplified version of os.path
module of standard Berry which is disabled in Tasmota because we don't have a full OS.
Easy way to persist simple values in Berry and read/write any attribute. Values are written in JSON format in _persist.json
file.
!!! example
> import persist
> persist.a = 1
> persist.b = "foobar"
> print(persist)
<instance: Persist({'a': 1, 'b': 'foobar'})>
> persist.save() # save to _persist.json
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")
foo
> introspect.set(ins, "a", "bar")
bar
> ins.a
bar
Class webclient
provides an implementation of an HTTP/HTTPS web client and make requests on the LAN or over the Internet.
Features:
- 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 mbetTLS)
- 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
!!! example
``` python
> cl = webclient()
> cl.begin("http://ota.tasmota.com/tasmota32/release/")
<instance: webclient()>
> r = cl.GET()
> print(r)
200
> s = cl.get_string()
> print(s)
<pre>
<b></b>Alternative firmware for ESP32 based devices with web UI,
[.../...]
```
!!! example of downloading a file from Github
``` python
> cl = webclient()
> cl.begin("https://raw.githubusercontent.com/tasmota/autoconf/main/esp32/M5Stack_Fire_autoconf.zip")
<instance: webclient()>
> r = cl.GET()
> print(r)
200
> cl.write_file("M5Stack_Fire_autoconf.zip")
950
```
Main functions:
Request customization:
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.
Low-level functions if you want to display custom pages and content:
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.
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. Returnstrue
if the connection succeeded. Optionaltimeout
in milliseconds. The default timeout isUSE_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 you responsibility to resend the missing bytes - check if bytes are available for reading
tcp.available() -> int
. Returns0
if nothing was received. This is the call you should make in loops for polling. - read incoming content as string
tcp.read() -> string
or as bytestcp.readbytes() -> bytes
. It is best to calltcp.available()
first to avoid creating empty response objects when not needed - close the socket with
tcp.close()
tcp = tcpclient()
tcp.connect("192.168.2.204", 80)
print("connected:", tcp.connected())
s= "GET / HTTP/1.0\r\n\r\n"
tcp.write(s)
print("available1:", tcp.available())
tasmota.delay(100)
print("available1:", tcp.available())
r = tcp.read()
tcp.close()
print(r)
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).
> u = udp()
> u.begin("", 2000) # listen on all interfaces, port 2000
true
> u.send("192.168.1.10", 2000, bytes("414243")) # send 'ABC' to 192.168.1.10:2000, source port is 2000
true
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
true
> u.read() # if no packet received, returns `nil`
>
> u.read() # if no packet received, returns `nil`
bytes("414243") # received packet as `bytes()`
> u = udp()
> u.begin_multicast("224.3.0.1", 3000) # connect to multicast 224.3.0.1:3000 on all interfaces
true
> client.send_multicast(bytes("33303030"))
> u.read() # if no packet received, returns `nil`
> u.read()
bytes("414243") # received packet as `bytes()`
There is native support for adressable leds via NeoPixelBus, with support for animations. Currently supported: WS2812, SK6812.
Details are in Berry leds
The serial
class provides a low-level interface to hardware UART. The serial GPIOs don't need to be configured in the template.
!!! example
```
# 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 = ser.read() # read bytes from serial as bytes
print(msg.asstring()) # print the message as string
```
Supported serial message formats: SERIAL_5N1
, SERIAL_6N1
, SERIAL_7N1
, SERIAL_8N1
, SERIAL_5N2
, SERIAL_6N2
, SERIAL_7N2
, SERIAL_8N2
, SERIAL_5E1
, SERIAL_6E1
, SERIAL_7E1
, SERIAL_8E1
, SERIAL_5E2
, SERIAL_6E2
, SERIAL_7E2
, SERIAL_8E2
, SERIAL_5O1
, SERIAL_6O1
, SERIAL_7O1
, SERIAL_8O1
, SERIAL_5O2
, SERIAL_6O2
, SERIAL_7O2
, SERIAL_8O2
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.
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. |
Find complete examples and use scenarios of Berry in the Berry Cookbook