{"slug": "berry", "title": "Berry", "summary": "Berry Scripting Language is now included in all Tasmota32 builds for ESP32 microcontrollers, offering an ultra-lightweight dynamically typed scripting language for embedded devices. The language supports advanced automations, drivers, and animations, with a REPL console and Visual Studio Code extension for development. Berry is optimized for low RAM usage and supports PSRAM on ESP32.", "body_md": "# Berry Scripting Language [~](#berry-scripting-language)\n\nBerry Scripting is included in all `tasmota32`\n\nbuilds. It is **NOT** supported on ESP82xx\n\nIf you plan to code in Berry, you should enable `#define USE_BERRY_DEBUG`\n\nwhich will give you much more details when coding\n\nIf you want to use Generative AI to generate Berry code, it is highly suggested to use [claude.ai](https://claude.ai/) and use the [Markdown Language Reference](https://tasmota.github.io/docs/_media/BERRY_LANGUAGE_REFERENCE.txt). This files consumes ~6000 input tokens.\n\nUseful resources:\n\n- First time user of Berry:\n[Berry Introduction (in 20 minutes of less)](../Berry-Introduction/) - Language fast reference PDF (7 pages)\n[Berry Short manual](https://tasmota.github.io/docs/_media/berry_short_manual.pdf) - Full language documentation\n[The Berry Script Language Reference Manual](https://berry.readthedocs.io/en/latest/source/en/Reference.html) - Tasmota extension of Berry, see below\n- Full examples in the\n[Berry Cookbook](../Berry-Cookbook/)\n\nIf you're new to Berry, have a look at [Berry Introduction (in 20 minutes of less)](../Berry-Introduction/)\n\n## Introduction to Berry[~](#introduction-to-berry)\n\nBerry 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.\n\nReference sheet\n\nDownload [Berry Short Manual](https://tasmota.github.io/docs/_media/berry_short_manual.pdf) to get a list of basic functions and capabilities of Berry language\n\nBerry Scripting allows simple and also advanced extensions of Tasmota, for example:\n\n- simple scripting\n- advanced rules, beyond what is possible with native rules\n- advanced automations\n\nBerry Scripting takes it one step further and allows to build dynamic extensions to Tasmota, that would previously require native code:\n\n- build light animations\n- build I\n2C drivers - build complete Tasmota drivers\n- integrate native libraries like\n`lvgl`\n\nsee[LVGL](../LVGL/)\n\n### About the Berry language[~](#about-the-berry-language)\n\nBerry has the following advantages:\n\n- Lightweight: A well-optimized interpreter with very little resources. Ideal for use in microprocessors.\n- Fast: optimized one-pass bytecode compiler and register-based virtual machine.\n- Powerful: supports imperative programming, object-oriented programming, functional programming.\n- 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.\n- Simple: simple and natural MicroPython-eque syntax, supports garbage collection and easy to use FFI (foreign function interface).\n- 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.\n\n## Tasmota Port[~](#tasmota-port)\n\nBerry 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.\n\n### Quick Start[~](#quick-start)\n\nClick on *Configuration* then *Berry Scripting Console* and enjoy the colorful Berry console, also called REPL (Read-Eval-Print-Loop).\n\nDrag the bottom corner of each screen to change its size\n\nThe 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](https://github.com/berry-lang/berry/tree/master/tools/plugins/vscode/) and copy to VSCode extensions folder.\n\n### REPL Console[~](#repl-console)\n\nTry 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.\n\n```\n> 1+1\n2\n> 2.0/3\n0.666667\n> print('Hello Tasmota!')\nHello Tasmota!\n```\n\nNote: Berry's native `print()`\n\ncommand displays text in the Berry Console and in the Tasmota logs. To log with finer control, you can also use the `log()`\n\nfunction which will not display in the Berry Console.\n\n```\n> print('Hello Tasmota!')\n  log('Hello again')\nHello Tasmota!\n```\n\nMeanwhile the Tasmota log shows:\n\n```\n> tasmota.cmd(\"Dimmer 60\")\n{'POWER': 'ON', 'Dimmer': 60, 'Color': '996245', 'HSBColor': '21,55,60', 'Channel': [60, 38, 27]}\nThe light is bright\n```\n\n## Save your Scripts[~](#save-your-scripts)\n\nBerry can autostart your scripts. See a short description in the Section about the filesystem: https://tasmota.github.io/docs/UFS/#autoexecbe Your can use the Filemanager to edit or save files with your berry scripts.\n\n## Iterate without rebooting[~](#iterate-without-rebooting)\n\nSince v13.0.0.1 you can restart the entire Berry VM with a click in the Berry console. This feature requires to compile with `#define USE_BERRY_DEBUG`\n\nwhich is anyways highly recommended when coding in Berry. Be aware that restarting the Berry VM loses all context, and may generate negative side effects that we haven't yet identified. When restarting the VM, `autoexec.be`\n\nis ran again.\n\nInstead of using the Web UI, you can also use the `BrRestart`\n\ncommand which does not require `#define USE_BERRY_DEBUG`\n\n.\n\n## Lights and Relays[~](#lights-and-relays)\n\nBerry provides complete support for Relays and Lights.\n\nYou can control individual Relays or lights with `tasmota.get_power()`\n\nand `tasmota.set_power()`\n\n.\n\n`tasmota.get_power()`\n\nreturns an array of booleans representing the state of each relays and light (light comes last).\n\n`tasmota.set_power(relay, onoff)`\n\nchanges the state of a single relay/light.\n\n2 relays and 1 light\n\n```\n> tasmota.get_power()\n[false, true, false]\n\n> tasmota.set_power(0, true)\ntrue\n\n> tasmota.get_power()\n[true, true, false]\n```\n\nFor light control, `light.get()`\n\nand `light.set`\n\naccept a structured object containing the following arguments:\n\n| Attributes | Details |\n|---|---|\n| 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. |\n| bri | `int range 0..255` Set the overall brightness. Be aware that the range is `0..255` and not `0..100` as Dimmer. |\n| hue | `int 0..360` Set the color Hue in degree, range 0..360 (0=red). |\n| sat | `int 0..255` Set the color Saturation (0 is gray). |\n| ct | `int 153..500` Set the white color temperature in mired, ranging from 153 (cold white) to 500 (warm white) |\n| rgb | `string 6 hex digits` Set the color as hex `RRGGBB` , changing color and brightness. |\n| channels | `array of int, ranges 0..255` Set the value for each channel, as an array of numbers |\n\nWhen setting attributes, they are evaluated in the following order, the latter overriding the previous: `power`\n\n, `ct`\n\n, `hue`\n\n, `sat`\n\n, `rgb`\n\n, `channels`\n\n, `bri`\n\n.\n\n```\n  # set to yellow, 25% brightness\n> light.set({\"power\": true, \"hue\":60, \"bri\":64, \"sat\":255})\n{'bri': 64, 'hue': 60, 'power': true, 'sat': 255, 'rgb': '404000', 'channels': [64, 64, 0]}\n\n  # set to RGB 000080 (blue 50%)\n> light.set({\"rgb\": \"000080\"})\n{'bri': 128, 'hue': 240, 'power': true, 'sat': 255, 'rgb': '000080', 'channels': [0, 0, 128]}\n\n  # set bri to zero, also powers off\n> light.set({\"bri\": 0})\n{'bri': 0, 'hue': 240, 'power': false, 'sat': 255, 'rgb': '000000', 'channels': [0, 0, 0]}\n\n  # changing bri doesn't automatically power\n> light.set({\"bri\": 32, \"power\":true})\n{'bri': 32, 'hue': 240, 'power': true, 'sat': 255, 'rgb': '000020', 'channels': [0, 0, 32]}\n\n  # set channels as numbers (purple 12%)\n> light.set({\"channels\": [32,0,32]})\n{'bri': 32, 'hue': 300, 'power': true, 'sat': 255, 'rgb': '200020', 'channels': [32, 0, 32]}\n```\n\n## Rules[~](#rules)\n\nThe rule function have the general form below where parameters are optional:\n\n``` python\ndef function_name(value, trigger, msg)\nend\n```\n\n| Parameter | Description |\n|---|---|\n`value` | The value of the trigger. Similar to `%value%` in native rules. |\n`trigger` | `string` of the trigger with all levels. Can be used if the same function is used with multiple triggers. |\n`msg` | `map` Berry structured object of the message, decoded from JSON. If JSON was invalid, it contains the original string |\n\nDimmer rule\n\nDefine the function and add a rule to Tasmota where the function runs if Dimmer value is more than 50\n\n``` python\n> def dimmer_over_50()\n    print(\"The light is bright\")\n  end\n  tasmota.add_rule(\"Dimmer>50\", dimmer_over_50)\n> tasmota.cmd(\"Dimmer 30\")\n{'POWER': 'ON', 'Dimmer': 30, 'Color': '4D3223', 'HSBColor': '21,55,30', 'Channel': [30, 20, 14]}\n\n> tasmota.cmd(\"Dimmer 60\")\n{'POWER': 'ON', 'Dimmer': 60, 'Color': '996245', 'HSBColor': '21,55,60', 'Channel': [60, 38, 27]}\nThe light is bright\n```\n\nThe same function can be used with multiple triggers.\n\nIf the function to process an ADC input should be triggered both by the `tele/SENSOR`\n\nmessage and the result of a `Status 10`\n\ncommand:\n\n```\ntasmota.add_rule(\"ANALOG#A1\", rule_adc_1)\ntasmota.add_rule(\"StatusSNS#ANALOG#A1\", rule_adc_1)\n```\n\nOr if the same function is used to process similar triggers:\n\n``` python\nimport string\n\ndef rule_adc(value, trigger)\n  var i=string.find(trigger,\"#A\")\n  var tr=string.split(trigger,i+2)\n  var adc=number(tr[1])\n  print(\"value of adc\",adc,\" is \",value)\nend\n\ntasmota.add_rule(\"ANALOG#A1\",rule_adc)\ntasmota.add_rule(\"ANALOG#A2\",rule_adc)\n```\n\nAnother way to address the same using anonymous functions created dynamically\n\n``` python\ndef rule_adc(adc, value)\n  print(\"value of adc\",adc,\" is \",value)\nend\ntasmota.add_rule(\"ANALOG#A1\", def (value) rule_adc(1,value) end )\ntasmota.add_rule(\"ANALOG#A2\", def (value) rule_adc(2,value) end )\n```\n\n### Multiple triggers AND logic[~](#multiple-triggers-and-logic)\n\nIt is possible to combine multiple triggers in a AND logic as an array:\n\n```\ntasmota.add_rule([\"ANALOG#A1>300\",\"ANALOG#A1<500\"], def (values) rule_adc_in_range(1,values) end )\n```\n\n`300 < ANALOG#A1 < 500`\n\nTriggers can be of different types too:\n\n```\ntasmota.add_rule([\"ANALOG#A1>300\",\"BME280#Temperature>28.0\"], def (values) rule_adc_and_temp(1,values) end )\n```\n\n`ANALOG#A1>300`\n\nAND `BME280#Temperature>28.0`\n\nIn that case, the value and trigger arguments passed to the rule function are also lists:\n\n``` python\ndef function_name(values:list_of_string, triggers:list_of_string, msg)\nend\n```\n\n`msg`\n\nremains unchanged. ### Teleperiod rules[~](#teleperiod-rules)\n\nTeleperiod rules are supported with a different syntax from Tasmota rules. Instead of using `Tele-`\n\nprefix, you must use `Tele#`\n\n. For example `Tele#ANALOG#Temperature1`\n\ninstead of `Tele-ANALOG#Temperature1`\n\n### Rules operators[~](#rules-operators)\n\n| Operator | Function |\n|---|---|\nString Operators | |\n`=` | equal to (used for string comparison) |\n`!==` | not equal to (used for string comparison) |\n`$<` | string starts with |\n`$>` | string ends with |\n`$\\|` | string contains |\n`$!` | string is not equal to |\n`$^` | string do not contains |\nNumerical Operators | |\n`==` | equal to (used for numerical comparison) |\n`>` | greater than |\n`<` | lesser than |\n`!=` | number not equal to |\n`>=` | greater than or equal to |\n`<=` | lesser than or equal to |\n`\\|` | Modulo division to this number is `0` (remainder=0) |\n\n## Timers[~](#timers)\n\nBerry 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.\n\nAll times are in milliseconds. You can know the current running time in milliseconds since the last boot:\n\n```\n> tasmota.millis()\n9977038\n```\n\nSending a timer is as easy as `tasmota.set_timer(<delay in ms>,<function>)`\n\n``` python\n> def t() print(\"Booh!\") end\n\n> tasmota.set_timer(5000, t)\n[5 seconds later]\nBooh!\n```\n\nTimers are scheduled roughly within 50 milliseconds ticks. This means that you cannot have better than 50 ms resolution, and `set_timer(0, <function>`\n\n) will schedule the function 50 ms later. In certain cases, you need to defer a function to an immediate future; for such case use `tasmota.defer(<function>)`\n\nwhich will run the function typically within the next millisecond.\n\n#### A word on functions and closure[~](#a-word-on-functions-and-closure)\n\nBerry 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.\n\nWhen using Rules or Timers, you always pass Berry functions.\n\n`cron`\n\nrecurrent calls[~](#cron-recurrent-calls)\n\nYou can choose to run some function/closure at regular intervals specified as `cron`\n\nstyle format with the first field representing seconds.\n\n``` python\n> def f() print(\"Hi\") end\n> tasmota.add_cron(\"*/15 * * * * *\", f, \"every_15_s\")\nHi\nHi      # added every 15 seconds\n> tasmota.remove_cron(\"every_15_s\")     # cron stops\n```\n\nLike timers, you need to create a closure if you want to register a method of an instance. Example:\n\n``` python\nclass A\n    var name\n    def init(name)\n        self.name = name\n    end\n    def p()\n        print(\"Hi,\", self.name)\n    end\nend\nphp\n> bob = A(\"bob\")\n> bob.p()\nHi, bob\n> tasmota.add_cron(\"*/15 * * * * *\", /-> bob.p(), \"hi_bob\")\nHi, bob\nHi, bob\nHi, bob\n> tasmota.remove_cron(\"hi_bob\")     # cron stops\n```\n\nYou can get the timestamp for the next event by using `tasmota.next_cron(id)`\n\nwhich returns an epoch in seconds.\n\n## Loading Filesystem[~](#loading-filesystem)\n\nBerry files can exist in 2 forms, either a source file (extension `.be`\n\n) or a pre-compiled bytecode (extension `.bec`\n\n). Pre-compiled are usually smaller and load slightly faster (although compilation is fast enough in most use cases). It's usually more flexible and simpler to use source code (`.be`\n\n).\n\nYou can upload Berry code in the filesystem using the ** Consoles - Manage File system** menu and load them at runtime. Make careful to use\n\n`*.be`\n\nextension for those files.To load a Berry file, use the `load(filename)`\n\nfunction where `filename`\n\nis the name of the file with `.be`\n\nor `.bec`\n\nextension; if the file has no extension '.be' is automatically appended.\n\nYou don't need to prefix with `/`\n\n. A leading `/`\n\nwill be added automatically if it is not present.\n\n## Previous behavior before 13.4.0.3:\n\nWhen loading a Berry script, the compiled bytecode is automatically saved to the filesystem, with the extension `.bec`\n\n(this is similar to Python's `.py`\n\n/`.pyc`\n\nmechanism). The `save(filename,closure)`\n\nfunction is used internally to save the bytecode.\n\nIf a precompiled bytecode (extension `.bec`\n\n) 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`\n\nfile and keep only `*.bec`\n\nfile (even with `load(\"file.be\")`\n\n.\n\nThe loading behavior is as follows:\n\n`load(\"hello\")`\n\nand`load(\"hello.be\")`\n\nloads`hello.be`\n\nand tries`hello.bec`\n\nif the first does not exist. If both`hello.be`\n\nand`hello.bec`\n\nexist,`hello.bec`\n\nis deleted to avoid confusion between versions.`load(\"hello.bec\")`\n\nloads only`hello.bec`\n\nand fails if only`hello.be`\n\nis present\n\nTo compile to `.bec`\n\nuse `tasmota.compile(\"hello.be\")`\n\n. If all is good, it returns `true`\n\nand creates `hello.bec`\n\n. But beware that if you use `load()`\n\nthe `.bec`\n\nfile is deleted.\n\nNote: `tasmota.compile()`\n\nis different than native Berry `compile()`\n\n## Creating a Tasmota Driver[~](#creating-a-tasmota-driver)\n\nYou can easily create a complete Tasmota driver with Berry.\n\nA 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`\n\n; the call mechanism is based on names of methods that must match the name of the event to be called.\n\nDriver methods are called with the following parameters: `f(cmd, idx, payload, raw)`\n\n. `cmd`\n\nis a string, `idx`\n\nan integer, `payload`\n\na Berry object representation of the JSON in `payload`\n\n(if any) or `nil`\n\n, `raw`\n\nis a string. These parameters are meaningful to a small subset of events:\n\n`every_second()`\n\n: called every second`every_50ms()`\n\n: called every 50ms (i.e. 20 times per second)`every_100ms()`\n\n: called every 100ms (i.e. 10 times per second)`every_250ms()`\n\n: called every 250ms (i.e. 4 times per second)`web_sensor()`\n\n: display sensor information on the Web UI`json_append()`\n\n: display sensor information in JSON format for TelePeriod reporting`web_add_button()`\n\n: (deprecated) synonym of`web_add_console_button()`\n\n`web_add_main_button()`\n\n,`web_add_management_button()`\n\n,`web_add_console_button()`\n\n,`web_add_config_button()`\n\n: add a button to Tasmota's Web UI on a specific page`web_add_handler()`\n\n: called when Tasmota web server started, and the right time to call`webserver.on()`\n\nto add handlers`save_before_restart()`\n\n: called just before a restart`mqtt_data(topic, idx, data, databytes)`\n\n: called for MQTT payloads matching`mqtt.subscribe`\n\n.`idx`\n\nis zero, and`data`\n\nis normally unparsed JSON.`set_power_handler(cmd, idx)`\n\n: called whenever a Power command is made.`idx`\n\nis a combined index value, with one bit per relay or light currently on.`cmd`\n\ncan be ignored.`display()`\n\n: called by display driver with the following subtypes:`init_driver`\n\n,`model`\n\n,`dim`\n\n,`power`\n\n.`button_pressed(cmd, idx)`\n\n: called when a button is pressed. See[sample code](https://github.com/arendst/Tasmota/pull/21711#issuecomment-2198649833).`button_multi_pressed(cmd, idx)`\n\n: called for button multi-press. See[sample code](https://github.com/arendst/Tasmota/pull/21711#issuecomment-2198649833).`any_key(cmd, idx)`\n\n: called when an interaction with Button or Switch occurs.`idx`\n\nis encoded as follows:`device_save << 24 | key << 16 | state << 8 | device`\n\nSee[sample code](https://github.com/arendst/Tasmota/pull/21711#issuecomment-2198649833).\n\nThen register the driver with `tasmota.add_driver(<driver>)`\n\n.\n\nThere are basically two ways to respond to an event:\n\n**Example**\n\nDefine a class and implement methods with the same name as the events you want to respond to.\n\n``` python\nclass MyDriver\n  def every_second()\n    # do something\n  end\nend\n\nd1 = MyDriver()\n\ntasmota.add_driver(d1)\n```\n\n## Fast Loop[~](#fast-loop)\n\nBeyond the events above, a specific mechanism is available for near-real-time events or fast loops (200 times per second, or 5ms).\n\nSpecial 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.\n\nOnce a callback is registered, it is called separately from Berry drivers to ensure minimal overhead.\n\n`tasmota.add_fast_loop(cl:function) -> nil`\n\nregisters a callback to be called in fast loop mode.\n\nThe 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 set to 200Hz or 5ms.\n\nNote: since v13.1.0.2, the frequency of `fast_loop`\n\ndoes not depend anymore on the value of the `Sleep <x>`\n\ncommand.\n\n`tasmota.remove_fast_loop(cl:function) -> nil`\n\nremoves a previously registered function or closure. You need to pass the exact same closure reference.\n\nWarning, if you need to register a method from an instance, you need a closure:\n\n``` python\nclass my_driver\n  def every_100ms()\n    # called every 100ms via normal way\n  end\n\n  def fast_loop()\n    # called at each iteration, and needs to be registered separately and explicitly\n  end\n\n  def init()\n    # register fast_loop method\n    tasmota.add_fast_loop(/-> self.fast_loop())\n    # variant:\n    # tasmota.add_fast_loop(def () self.fast_loop() end)\n  end\nend\n\ntasmota.add_driver(my_driver())                     # register driver\ntasmota.add_fast_loop(/-> my_driver.fast_loop())    # register a closure to capture the instance of the class as well as the method\n```\n\n## Tasmota Only Extensions[~](#tasmota-only-extensions)\n\n`log(msg:string [, level:int = 3]) -> string`\n\n[~](#logmsgstring-levelint-3-string)\n\nLogs a message to the Tasmota console. Optional second argument is log_level (0..4), default is `2`\n\n(matching build time `LOG_LEVEL_INFO`\n\n).\n\nExample\n\n```\n> log(\"A\")\nA\n```\n\n`load(filename:string) -> bool`\n\n[~](#loadfilenamestring-bool)\n\nLoads 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 `/`\n\n, but needs to end with `.be`\n\n(Berry source code) or `.bec`\n\n(precompiled bytecode). If the `.be`\n\nextension is missing, it is automatically added.\n\nThe behavior for `.bec`\n\nfiles changed in v13.4: - when loading `<file>.be`\n\n, the `.be`\n\nfile is loaded in priority and any `.bec`\n\nfile with same prefix is removed (to avoid inconsistencies). If no `.be`\n\nfile is present, it tried to load `.bec`\n\nfile. - when loading `<file>.bec`\n\n, only `.bec`\n\nfiles are loaded and `.be`\n\nis ignored. - to create `.bec`\n\nfiles, you need to use `tasmota.compile`\n\n(see below)\n\n`tasmota.compile(filename:string) -> bool`\n\n[~](#tasmotacompilefilenamestring-bool)\n\nLoads a `.be`\n\nfile, compiles it and saves a `.bec`\n\nfile containing the compiled bytecode.\n\nNote: `tasmota.compile`\n\nis different from Berry native `compile`\n\nfunction.\n\n`save(filename:string, f:closure) -> nil`\n\n[~](#savefilenamestring-fclosure-nil)\n\nInternally used function to save bytecode. It's a wrapper to the Berry's internal API `be_savecode()`\n\n. There is no check made on the filename.\n\nThere is generally no need to use this function, it is used internally by `load()`\n\n.\n\n`tasmota`\n\nobject[~](#tasmota-object)\n\nA root level object called `tasmota`\n\nis created and contains numerous functions to interact with Tasmota.\n\n#### Functions used to retrieve Tasmota configuration[~](#functions-used-to-retrieve-tasmota-configuration)\n\n#### Functions for time, timers or cron[~](#functions-for-time-timers-or-cron)\n\n#### Functions to create custom Tasmota command[~](#functions-to-create-custom-tasmota-command)\n\n#### Functions to add custom responses to JSON and Web UI to sensors[~](#functions-to-add-custom-responses-to-json-and-web-ui-to-sensors)\n\nSee examples in the [Berry-Cookbook](../Berry-Cookbook/#adding-commands-to-tasmota)\n\n#### Functions to manage Relays and Lights[~](#functions-to-manage-relays-and-lights)\n\n#### Low-level access to Tasmota globals and settings.[~](#low-level-access-to-tasmota-globals-and-settings)\n\n*Use with care and only if you know what you are doing.*\n\nThe construct is to use `tasmota.global`\n\nor `tasmota.settings`\n\nto read or write attributes.\n\nYou can do bad things with these features\n\n| Value | Details |\n|---|---|\n| tasmota.global.sleep | Current sleep value |\n| tasmota.global.devices_present | Number of Power channels, e.g. having virtual relays |\n| tasmota.settings.sleep | Sleep value stored in flash |\n\n`mqtt`\n\nmodule[~](#mqtt-module)\n\nUse with `import mqtt`\n\n.\n\nSince v11.1.0.1, there is an easier way than registering a driver, and listening to `mqtt_data`\n\nevent. You can now just attach a function or closure to a MQTT topic, and it does the magic for you.\n\nThe function you attach to a topic pattern received **only** the matching MQTT messages, not all messages unlike `mqtt_data()`\n\nwould.\n\nThe function takes the same parameters as `mqtt_data()`\n\n:\n\n`topic`\n\n: full topic received from the broker`idx`\n\n: not used`payload_s`\n\n: payload as string, usually converted to JSON with`import json json.load(payload_s)`\n\n`payload_b`\n\n: payload as a binary payload, bytes() array\n\nthe function should return `true`\n\nif the event was parsed or if the event should not trigger a Tasmota command. If you return `nil`\n\nor nothing, it is considered as `true`\n\nwhich is the usual behavior you want (i.e. not trigger a Tasmota command from random MQTT messages).\n\n`light`\n\nobject[~](#light-object)\n\nModule `light`\n\nis automatically imported via a hidden `import light`\n\ncommand.\n\n`gpio`\n\nmodule[~](#gpio-module)\n\nThis 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()`\n\ntransforms a logical GPIO to a physical GPIO, or `-1`\n\nif the logical GPIO is not set.\n\nCurrently there is limited support for GPIO: you can only read/write in digital mode and set the GPIO mode.\n\nAny internal error or using unsupported GPIO yields a Berry exception.\n\n## Possible values for Tasmota GPIOs:\n\n`gpio.NONE`\n\n, `gpio.KEY1`\n\n, `gpio.KEY1_NP`\n\n, `gpio.KEY1_INV`\n\n, `gpio.KEY1_INV_NP`\n\n, `gpio.SWT1`\n\n, `gpio.SWT1_NP`\n\n, `gpio.REL1`\n\n, `gpio.REL1_INV`\n\n, `gpio.LED1`\n\n, `gpio.LED1_INV`\n\n, `gpio.CNTR1`\n\n, `gpio.CNTR1_NP`\n\n, `gpio.PWM1`\n\n, `gpio.PWM1_INV`\n\n, `gpio.BUZZER`\n\n, `gpio.BUZZER_INV`\n\n, `gpio.LEDLNK`\n\n, `gpio.LEDLNK_INV`\n\n, `gpio.I2C_SCL`\n\n, `gpio.I2C_SDA`\n\n, `gpio.SPI_MISO`\n\n, `gpio.SPI_MOSI`\n\n, `gpio.SPI_CLK`\n\n, `gpio.SPI_CS`\n\n, `gpio.SPI_DC`\n\n, `gpio.SSPI_MISO`\n\n, `gpio.SSPI_MOSI`\n\n, `gpio.SSPI_SCLK`\n\n, `gpio.SSPI_CS`\n\n, `gpio.SSPI_DC`\n\n, `gpio.BACKLIGHT`\n\n, `gpio.OLED_RESET`\n\n, `gpio.IRSEND`\n\n, `gpio.IRRECV`\n\n, `gpio.RFSEND`\n\n, `gpio.RFRECV`\n\n, `gpio.DHT11`\n\n, `gpio.DHT22`\n\n, `gpio.SI7021`\n\n, `gpio.DHT11_OUT`\n\n, `gpio.DSB`\n\n, `gpio.DSB_OUT`\n\n, `gpio.WS2812`\n\n, `gpio.MHZ_TXD`\n\n, `gpio.MHZ_RXD`\n\n, `gpio.PZEM0XX_TX`\n\n, `gpio.PZEM004_RX`\n\n, `gpio.PZEM016_RX`\n\n, `gpio.PZEM017_RX`\n\n, `gpio.SAIR_TX`\n\n, `gpio.SAIR_RX`\n\n, `gpio.PMS5003_TX`\n\n, `gpio.PMS5003_RX`\n\n, `gpio.SDS0X1_TX`\n\n, `gpio.SDS0X1_RX`\n\n, `gpio.SBR_TX`\n\n, `gpio.SBR_RX`\n\n, `gpio.SR04_TRIG`\n\n, `gpio.SR04_ECHO`\n\n, `gpio.SDM120_TX`\n\n, `gpio.SDM120_RX`\n\n, `gpio.SDM630_TX`\n\n, `gpio.SDM630_RX`\n\n, `gpio.TM1638CLK`\n\n, `gpio.TM1638DIO`\n\n, `gpio.TM1638STB`\n\n, `gpio.MP3_DFR562`\n\n, `gpio.HX711_SCK`\n\n, `gpio.HX711_DAT`\n\n, `gpio.TX2X_TXD_BLACK`\n\n, `gpio.TUYA_TX`\n\n, `gpio.TUYA_RX`\n\n, `gpio.MGC3130_XFER`\n\n, `gpio.MGC3130_RESET`\n\n, `gpio.RF_SENSOR`\n\n, `gpio.AZ_TXD`\n\n, `gpio.AZ_RXD`\n\n, `gpio.MAX31855CS`\n\n, `gpio.MAX31855CLK`\n\n, `gpio.MAX31855DO`\n\n, `gpio.NRG_SEL`\n\n, `gpio.NRG_SEL_INV`\n\n, `gpio.NRG_CF1`\n\n, `gpio.HLW_CF`\n\n, `gpio.HJL_CF`\n\n, `gpio.MCP39F5_TX`\n\n, `gpio.MCP39F5_RX`\n\n, `gpio.MCP39F5_RST`\n\n, `gpio.PN532_TXD`\n\n, `gpio.PN532_RXD`\n\n, `gpio.SM16716_CLK`\n\n, `gpio.SM16716_DAT`\n\n, `gpio.SM16716_SEL`\n\n, `gpio.DI`\n\n, `gpio.DCKI`\n\n, `gpio.CSE7766_TX`\n\n, `gpio.CSE7766_RX`\n\n, `gpio.ARIRFRCV`\n\n, `gpio.ARIRFSEL`\n\n, `gpio.TXD`\n\n, `gpio.RXD`\n\n, `gpio.ROT1A`\n\n, `gpio.ROT1B`\n\n, `gpio.ADC_JOY`\n\n, `gpio.SSPI_MAX31865_CS1`\n\n, `gpio.HRE_CLOCK`\n\n, `gpio.HRE_DATA`\n\n, `gpio.ADE7953_IRQ`\n\n, `gpio.SOLAXX1_TX`\n\n, `gpio.SOLAXX1_RX`\n\n, `gpio.ZIGBEE_TX`\n\n, `gpio.ZIGBEE_RX`\n\n, `gpio.RDM6300_RX`\n\n, `gpio.IBEACON_TX`\n\n, `gpio.IBEACON_RX`\n\n, `gpio.A4988_DIR`\n\n, `gpio.A4988_STP`\n\n, `gpio.A4988_ENA`\n\n, `gpio.A4988_MS1`\n\n, `gpio.OUTPUT_HI`\n\n, `gpio.OUTPUT_LO`\n\n, `gpio.DDS2382_TX`\n\n, `gpio.DDS2382_RX`\n\n, `gpio.DDSU666_TX`\n\n, `gpio.DDSU666_RX`\n\n, `gpio.SM2135_CLK`\n\n, `gpio.SM2135_DAT`\n\n, `gpio.DEEPSLEEP`\n\n, `gpio.EXS_ENABLE`\n\n, `gpio.TASMOTACLIENT_TXD`\n\n, `gpio.TASMOTACLIENT_RXD`\n\n, `gpio.TASMOTACLIENT_RST`\n\n, `gpio.TASMOTACLIENT_RST_INV`\n\n, `gpio.HPMA_RX`\n\n, `gpio.HPMA_TX`\n\n, `gpio.GPS_RX`\n\n, `gpio.GPS_TX`\n\n, `gpio.HM10_RX`\n\n, `gpio.HM10_TX`\n\n, `gpio.LE01MR_RX`\n\n, `gpio.LE01MR_TX`\n\n, `gpio.CC1101_GDO0`\n\n, `gpio.CC1101_GDO2`\n\n, `gpio.HRXL_RX`\n\n, `gpio.ELECTRIQ_MOODL_TX`\n\n, `gpio.AS3935`\n\n, `gpio.ADC_INPUT`\n\n, `gpio.ADC_TEMP`\n\n, `gpio.ADC_LIGHT`\n\n, `gpio.ADC_BUTTON`\n\n, `gpio.ADC_BUTTON_INV`\n\n, `gpio.ADC_RANGE`\n\n, `gpio.ADC_CT_POWER`\n\n, `gpio.WEBCAM_PWDN`\n\n, `gpio.WEBCAM_RESET`\n\n, `gpio.WEBCAM_XCLK`\n\n, `gpio.WEBCAM_SIOD`\n\n, `gpio.WEBCAM_SIOC`\n\n, `gpio.WEBCAM_DATA`\n\n, `gpio.WEBCAM_VSYNC`\n\n, `gpio.WEBCAM_HREF`\n\n, `gpio.WEBCAM_PCLK`\n\n, `gpio.WEBCAM_PSCLK`\n\n, `gpio.WEBCAM_HSD`\n\n, `gpio.WEBCAM_PSRCS`\n\n, `gpio.BOILER_OT_RX`\n\n, `gpio.BOILER_OT_TX`\n\n, `gpio.WINDMETER_SPEED`\n\n, `gpio.KEY1_TC`\n\n, `gpio.BL0940_RX`\n\n, `gpio.TCP_TX`\n\n, `gpio.TCP_RX`\n\n, `gpio.ETH_PHY_POWER`\n\n, `gpio.ETH_PHY_MDC`\n\n, `gpio.ETH_PHY_MDIO`\n\n, `gpio.TELEINFO_RX`\n\n, `gpio.TELEINFO_ENABLE`\n\n, `gpio.LMT01`\n\n, `gpio.IEM3000_TX`\n\n, `gpio.IEM3000_RX`\n\n, `gpio.ZIGBEE_RST`\n\n, `gpio.DYP_RX`\n\n, `gpio.MIEL_HVAC_TX`\n\n, `gpio.MIEL_HVAC_RX`\n\n, `gpio.WE517_TX`\n\n, `gpio.WE517_RX`\n\n, `gpio.AS608_TX`\n\n, `gpio.AS608_RX`\n\n, `gpio.SHELLY_DIMMER_BOOT0`\n\n, `gpio.SHELLY_DIMMER_RST_INV`\n\n, `gpio.RC522_RST`\n\n, `gpio.P9813_CLK`\n\n, `gpio.P9813_DAT`\n\n, `gpio.OPTION_A`\n\n, `gpio.FTC532`\n\n, `gpio.RC522_CS`\n\n, `gpio.NRF24_CS`\n\n, `gpio.NRF24_DC`\n\n, `gpio.ILI9341_CS`\n\n, `gpio.ILI9341_DC`\n\n, `gpio.ILI9488_CS`\n\n, `gpio.EPAPER29_CS`\n\n, `gpio.EPAPER42_CS`\n\n, `gpio.SSD1351_CS`\n\n, `gpio.RA8876_CS`\n\n, `gpio.ST7789_CS`\n\n, `gpio.ST7789_DC`\n\n, `gpio.SSD1331_CS`\n\n, `gpio.SSD1331_DC`\n\n, `gpio.SDCARD_CS`\n\n, `gpio.ROT1A_NP`\n\n, `gpio.ROT1B_NP`\n\n, `gpio.ADC_PH`\n\n, `gpio.BS814_CLK`\n\n, `gpio.BS814_DAT`\n\n, `gpio.WIEGAND_D0`\n\n, `gpio.WIEGAND_D1`\n\n, `gpio.NEOPOOL_TX`\n\n, `gpio.NEOPOOL_RX`\n\n, `gpio.SDM72_TX`\n\n, `gpio.SDM72_RX`\n\n, `gpio.TM1637CLK`\n\n, `gpio.TM1637DIO`\n\n, `gpio.PROJECTOR_CTRL_TX`\n\n, `gpio.PROJECTOR_CTRL_RX`\n\n, `gpio.SSD1351_DC`\n\n, `gpio.XPT2046_CS`\n\n, `gpio.CSE7761_TX`\n\n, `gpio.CSE7761_RX`\n\n, `gpio.VL53LXX_XSHUT1`\n\n, `gpio.MAX7219CLK`\n\n, `gpio.MAX7219DIN`\n\n, `gpio.MAX7219CS`\n\n, `gpio.TFMINIPLUS_TX`\n\n, `gpio.TFMINIPLUS_RX`\n\n, `gpio.ZEROCROSS`\n\n, `gpio.HALLEFFECT`\n\n, `gpio.EPD_DATA`\n\n, `gpio.GPIO_INPUT`\n\n, `gpio.KEY1_PD`\n\n, `gpio.KEY1_INV_PD`\n\n, `gpio.SWT1_PD`\n\n, `gpio.I2S_OUT_DATA`\n\n, `gpio.I2S_OUT_CLK`\n\n, `gpio.I2S_OUT_SLCT`\n\n, `gpio.I2S_IN_DATA`\n\n, `gpio.I2S_IN_CLK`\n\n, `gpio.I2S_IN_SLCT`\n\n, `gpio.INTERRUPT`\n\n, `gpio.MCP2515_CS`\n\n, `gpio.HRG15_TX`\n\n, `gpio.VINDRIKTNING_RX`\n\n, `gpio.BL0939_RX`\n\n, `gpio.BL0942_RX`\n\n, `gpio.HM330X_SET`\n\n, `gpio.HEARTBEAT`\n\n, `gpio.HEARTBEAT_INV`\n\n, `gpio.SHIFT595_SRCLK`\n\n, `gpio.SHIFT595_RCLK`\n\n, `gpio.SHIFT595_OE`\n\n, `gpio.SHIFT595_SER`\n\n, `gpio.SOLAXX1_RTS`\n\n, `gpio.OPTION_E`\n\n, `gpio.SDM230_TX`\n\n, `gpio.SDM230_RX`\n\n, `gpio.ADC_MQ`\n\n, `gpio.CM11_TXD`\n\n, `gpio.CM11_RXD`\n\n, `gpio.BL6523_TX`\n\n, `gpio.BL6523_RX`\n\n, `gpio.ADE7880_IRQ`\n\n, `gpio.RESET`\n\n, `gpio.MS01`\n\n, `gpio.SDIO_CMD`\n\n, `gpio.SDIO_CLK`\n\n, `gpio.SDIO_D0`\n\n, `gpio.SDIO_D1`\n\n, `gpio.SDIO_D2`\n\n, `gpio.SDIO_D3`\n\n, `gpio.FLOWRATEMETER_SIGNAL`\n\n, `gpio.SENSOR_END`\n\nAn 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.\n\nSee the Berry cookbook for [H-bridge control](../Berry-Cookbook/#h-bridge-control)\n\n### DAC GPIOs[~](#dac-gpios)\n\nDAC is limited to specific GPIOs:\n\n- ESP32: only GPIO 25-26\n- ESP32-S2: only GPIO 17-18\n- ESP32-C3: not supported\n\nExample\n\n```\n> gpio.pin_mode(25, gpio.DAC)   # sets GPIO25 to a DAC pin\n> gpio.dac_voltage(25, 1250)    # set voltage to 1250mV\n1255\n```\n\n### I2S[~](#i2s)\n\nDAC can also be used via `Esp8266Audio`\n\nthrough the ESP32 I2S -> DAC bridge.\n\n## Example\n\n``` js\nclass MP3_Player : Driver\n  var audio_output, audio_mp3, fast_loop_closure\n  def init()\n    self.audio_output = AudioOutputI2S()\n    self.audio_mp3 = AudioGeneratorMP3()\n    self.fast_loop_closure = def () self.fast_loop() end\n    tasmota.add_fast_loop(self.fast_loop_closure)\n  end\n\n  def play(mp3_fname)\n    if self.audio_mp3.isrunning()\n      self.audio_mp3.stop()\n    end\n    var audio_file = AudioFileSourceFS(mp3_fname)\n    self.audio_mp3.begin(audio_file, self.audio_output)\n    self.audio_mp3.loop()    #- start playing now -#\n  end\n\n  def fast_loop()\n    if self.audio_mp3.isrunning()\n      if !self.audio_mp3.loop()\n        self.audio_mp3.stop()\n        tasmota.remove_fast_loop(self.fast_loop_closure)\n      end\n    end\n  end\nend\n\nmp3_player = MP3_Player()\nmp3_player.play(\"/pno-cs.mp3\")\n```\n\n`energy`\n\nmodule[~](#energy-module)\n\nThe `energy`\n\nmodule 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).\n\nIt relies on a new Berry feature that provides a direct mapping between the internal `C`\n\nstructure called `struct Energy`\n\nand the `energy`\n\nmodule in Berry.\n\nFor example, if you want to read or update an energy value:\n\n```\n> energy.active_power\n0\n> energy.active_power = 460\n> energy.active_power\n460\n\n# internally it updates the C value `Energy.active_power[0]` (float)\n```\n\nYou don't need to do `import energy`\n\nsince Tasmota does it for you at boot.\n\nThe special `energy.read()`\n\nfunction dumps all current values to a single `map`\n\n. Be aware that the object is very long. Prefer accessing individual attributes instead.\n\nList of `energy`\n\nattributes that you can read or write:\n\n| Attribute | Type | Description |\n|---|---|---|\n| voltage | float | Voltage (V) for main phase |\n| voltage_phases | array of float | Voltage (V) as an array of phases |\n| current | float | Current (A) for main phase |\n| current_phases | array of float | Current (A) as an array of phases |\n| active_power | float | Active Power (W) for main phase |\n| active_power_phases | array of float | Active Power (W) as an array of phases |\n| reactive_power | float | Reactive Power (W) for main phase |\n| reactive_power_phases | array of float | Reactive Power (W) as an array of phases |\n| power_factor | float | Power Factor (no unit) for main phase |\n| power_factor_phases | array of float | Power Factor (no unit) as an array of phases |\n| frequency | float | Frequency (Hz) for main phase |\n| frequency_phases | array of float | Frequency (Hz) as an array of phases |\n| export_active | float | (kWh) |\n| export_active_phases | array of float | (kWh) |\n| start_energy | float | Total previous energy (kWh) |\n| daily | float | Daily energy (kWh) |\n| total | float | Total energy (kWh) |\n| today_delta_kwh | uint32 | (deca milli Watt hours) 5764 = 0.05764 kWh = 0.058 kWh |\n| today_offset_kwh | uint32 | (deca milli Watt hours) |\n| today_kwh | uint32 | (deca milli Watt hours) |\n| period | uint32 | (deca milli Watt hours) |\n| fifth_second | uint8 | |\n| command_code | uint8 | |\n| data_valid | uint8 | `0` if data is valid for main phase |\n| data_valid_phases | array of uint8 | `0` if data is valid as an array of phases |\n| phase_count | uint8 | Number of phases (1..8) |\n| voltage_common | bool | Use single voltage |\n| frequency_common | bool | Use single frequency |\n| use_overtemp | bool | Use global temperature as overtemp trigger on internal energy monitor hardware |\n| today_offset_init_kwh | bool | |\n| voltage_available | bool | Enable if voltage is measured |\n| current_available | bool | Enable if current is measured |\n| type_dc | bool | |\n| power_on | bool | |\n| power_history_0 power_history_1 power_history_2 | uint16 | |\n| power_steady_counter | uint8 | Allow for power on stabilization |\n| min_power_flag | bool | |\n| max_power_flag | bool | |\n| min_voltage_flag | bool | |\n| max_voltage_flag | bool | |\n| min_current_flag | bool | |\n| max_current_flag | bool | |\n| mplh_counter | uint16 | |\n| mplw_counter | uint16 | |\n| mplr_counter | uint8 | |\n| max_energy_state | uint8 |\n\n### Energy driver in Berry[~](#energy-driver-in-berry)\n\nSince v14.2.0, it is possible to implement an Energy driver in pure Berry. The Berry driver is enabled when an `OPTION_A 9`\n\nGPIO is configured:\n\n- by default, the energy driver has zero consumption.\n- the berry code can is\n`energy.driver_enabled()`\n\nto check if the virtual Berry Energy driver is active (i.e.`OPTION_A 9`\n\nis configured) - the following values need to be configured:\n`energy.phase_count`\n\n(default`1`\n\n),`energy.voltage`\n\n,`energy.current`\n\n,`energy.power_factor`\n\n(typically`1.0`\n\nor less),`energy.frequency`\n\n(default`nan`\n\n) - the most important value is\n`energy.active_power`\n\n(in Watt) which is added to the daily power consumption\n\nExample test code in `autoexec.be`\n\n:\n\n```\nif energy.driver_enabled()\n  energy.phase_count = 1\n  energy.voltage = 240\n  energy.power_factor = 1.0\n  energy.current = 1.5\n  energy.frequency = 50\n  energy.active_power = 360\nend\n```\n\n`wire`\n\nobject for I2C[~](#wire-object-for-i2c)\n\nBerry Scripting provides 2 objects: `wire1`\n\nand `wire2`\n\nto communicate with both I2C buses.\n\nUse `wire1.scan()`\n\nand `wire2.scan()`\n\nto scan both buses:\n\n```\n> wire1.scan()\n[]\n\n> wire2.scan()\n[140]\n```\n\nYou generally use `tasmota.wire_scan()`\n\nto find a device and the corresponding I2C bus.\n\nMPU6886 on bus 2\n\n```\n> mpuwire = tasmota.wire_scan(0x68, 58)\n> mpuwire\n<instance: Wire()>\n```\n\nLow-level commands if you need finer control:\n\n`path`\n\nmodule[~](#path-module)\n\nA simplified version of `os.path`\n\nmodule of standard Berry which is disabled in Tasmota because we don't have a full OS.\n\nThe default file-system is the ESP32 internal flash. If you have a SD card mounted, it is mapped to the `/sd/`\n\nsubdirectory.\n\nExample:\n\n``` python\nimport path\nprint(path.listdir(\"/sd/\"))\n# outputs a list of filenames at the root dir of the SD card\n```\n\n`persist`\n\nmodule[~](#persist-module)\n\nEasy way to persist simple values in Berry and read/write any attribute. Values are written in JSON format in `_persist.json`\n\nfile. Be aware that `persist`\n\ncannot detect any change in sub-objects like lists or maps; in such case you can call `persist.dirty()`\n\nto indicate that data needs to be saved.\n\nExample\n\n``` python\n> import persist    \n> persist.a = 1    \n> persist.b = \"foobar\"    \n> print(persist)    \n<instance: Persist({'a': 1, 'b': 'foobar'})>    \n> persist.save()   # save to _persist.json\n```\n\n`introspect`\n\nmodule[~](#introspect-module)\n\nAllows to do introspection on instances and modules, to programmatically list attributes, set and get them.\n\n``` python\n> class A var a,b def f() return 1 end end\n> ins=A()\n> ins.a = \"foo\"\n> import introspect\n\n> introspect.members(ins)\n['b', 'a', 'f']\n\n> introspect.get(ins, \"a\")\nfoo\n\n> introspect.set(ins, \"a\", \"bar\")\nbar\n\n> ins.a\nbar\n```\n\n`webclient`\n\nclass[~](#webclient-class)\n\nClass `webclient`\n\nprovides an implementation of an HTTP/HTTPS web client and make requests on the LAN or over the Internet.\n\nFeatures:\n\n- Support HTTP and HTTPS requests to IPv4 addresses and domain names, to arbitrary ports, via a full URL.\n- Support for HTTPS and TLS via BearSSL (which is much lighter than default mbedTLS)\n- HTTPS (TLS) only supports cipher ECDHE_RSA_WITH_AES_128_GCM_SHA256 which is both secure and widely supported\n- Support for URL redirections\n- Ability to set custom User-Agent\n- Ability to set custom headers\n- Ability to set Authentication header\n- Support for Chunked encoding response (so works well with Tasmota devices)\n- Support for\n`GET`\n\n,`POST`\n\n,`PUT`\n\n,`PATCH`\n\n,`DELETE`\n\nmethods\n\nThe current implementation is based on a fork of Arduino's HttpClient customized to use BearSSL\n\nCurrent limitations (if you need extra features please open a feature request on GitHub):\n\n- Payload sent to server (\n`POST`\n\n) can include either text or binary - Only supports text responses (html, json...) but not binary content yet (no NULL char allowed). However you can download binary content to the file-system with\n`write_file`\n\n- Maximum response size is 32KB, requests are dropped if larger\n- HTTPS (TLS) is in 'insecure' mode and does not check the server's certificate; it is subject to Man-in-the-Middle attack\n- No support for compressed response - this should not be a problem since the client does not advertise support for compressed responses\n\nExample\n\n```\n> cl = webclient()\n> cl.begin(\"http://ota.tasmota.com/tasmota32/release/\")\n<instance: webclient()>\n\n> r = cl.GET()\n> print(r)\n200\n\n> s = cl.get_string()\n> print(s)\n<pre>\n<b></b>Alternative firmware for ESP32 based devices with web UI,\n[.../...]\n```\n\nExample\n\n```\n> cl = webclient()\n> cl.begin(\"https://raw.githubusercontent.com/tasmota/autoconf/main/esp32/M5Stack_Fire_autoconf.zip\")\n<instance: webclient()>\n\n> r = cl.GET()\n> print(r)\n200\n\n> cl.write_file(\"M5Stack_Fire_autoconf.zip\")\n950\n```\n\n#### Managing redirects[~](#managing-redirects)\n\nHTTP redirects (301/302) are not followed by default. You can use `wc.set_follow_redirects(true)`\n\nto have redirects automatically followed for HEAD and GET. There is a default limit of 10 successive redirects, this prevents from infinite loops.\n\nFor the examples, we use `http://ota.tasmota.com/tasmota32`\n\nwhich is redirected to `http://ota.tasmota.com/tasmota32/`\n\nExample\n\n```\ncl = webclient()\ncl.set_follow_redirects(true)\ncl.begin(\"http://ota.tasmota.com/tasmota32\")\nr = cl.GET()\nprint(r)\ns = cl.get_string()\nprint(s)\n```\n\nAlternatively, you can manage yourself redirects and retrieve the `Location`\n\nheader\n\nExample\n\n```\ncl = webclient()\ncl.set_follow_redirects(false)\ncl.collect_headers(\"Location\")\ncl.begin(\"http://ota.tasmota.com/tasmota32\")\nr = cl.GET()\nprint(r)\nif r == 301 || r == 302\n  print(\"Location:\", cl.get_header(\"Location\"))\nelif r == 200\n  s = cl.get_string()\n  print(s)\nend\ncl.close()\n```\n\nMain functions:\n\nRequest customization:\n\nStatic utility methods:\n\n| webclient static method | Parameters and details |\n|---|---|\n| url_encode | `(url:string) -> string` Encodes a string according to URL escape rules. Use before you use `begin()` |\n\n`webserver`\n\nmodule[~](#webserver-module)\n\nModule `webserver`\n\nprovides functions to enrich Tasmota's Web UI. It is tightly linked to Tasmota page layout.\n\nFunctions used to add UI elements like buttons to Tasmota pages, and analyze the current request. See above `Driver`\n\nto add buttons to Tasmota UI.\n\nLow-level functions if you want to display custom pages and content:\n\nModule `webserver`\n\nalso defines the following constants:\n\n- Tasmota's web server states:\n`webserver.HTTP_OFF`\n\n,`webserver.HTTP_USER`\n\n,`webserver.HTTP_ADMIN`\n\n,`webserver.HTTP_MANAGER`\n\n,`webserver.HTTP_MANAGER_RESET_ONLY`\n\n- Tasmota's pages:\n`webserver.BUTTON_CONFIGURATION`\n\n,`webserver.BUTTON_INFORMATION`\n\n,`webserver.BUTTON_MAIN`\n\n,`webserver.BUTTON_MANAGEMENT`\n\n,`webserver.BUTTON_MODULE`\n\n- Methods received by handler:\n`webserver.HTTP_ANY`\n\n,`webserver.HTTP_GET`\n\n,`webserver.HTTP_OPTIONS`\n\n,`webserver.HTTP_POST`\n\nSee the [Berry Cookbook](../Berry-Cookbook/) for examples.\n\n`tcpclient`\n\nclass[~](#tcpclient-class)\n\nSimple TCP client supporting string and binary transfers:\n\n- create an instance of the client with\n`var tcp = tcpclient()`\n\n- connect to the server\n`tcp.connect(address:string, port:int [, timeout_ms:int]) -> bool`\n\nAddress can be numerical IPv4 or domain name. Returns`true`\n\nif the connection succeeded. Optional`timeout`\n\nin milliseconds. The default timeout is`USE_BERRY_WEBCLIENT_TIMEOUT`\n\n(2 seconds). - check if the socket is connected with\n`tcp.connected()`\n\n- send content with\n`tcp.write(content:string or bytes) -> int`\n\n. 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\n`tcp.available() -> int`\n\n. Returns`0`\n\nif nothing was received. This is the call you should make in loops for polling. - read incoming content as string\n`tcp.read() -> string`\n\nor as bytes`tcp.readbytes() -> bytes`\n\n. It is best to call`tcp.available()`\n\nfirst to avoid creating empty response objects when not needed - close the socket with\n`tcp.close()`\n\nFull example:\n\n```\ntcp = tcpclient()\ntcp.connect(\"192.168.2.204\", 80)\nprint(\"connected:\", tcp.connected())\ns= \"GET / HTTP/1.0\\r\\n\\r\\n\"\ntcp.write(s)\nprint(\"available1:\", tcp.available())\ntasmota.delay(100)\nprint(\"available1:\", tcp.available())\nr = tcp.read()\ntcp.close()\nprint(r)\n```\n\n`tcpclientasync`\n\nclass[~](#tcpclientasync-class)\n\nVariant of `tcpclient`\n\nusing only non-blocking calls in full asynchronous mode. This allows to have multiple concurrent connections with fine-grained control over timeouts and no blocking of Tasmota. This is especially useful for Matter Border Router for ESP8266 Tasmota based devices via HTTP.\n\nAll calls return immediately, so you need to poll the API periodically to send/receive data, and manage timeouts yourself.\n\nTypical sequence:\n\n- create an instance of the client with\n`var tcp = tcpclientasync()`\n\n- connect to the server\n`tcp.connect(address:string, port:int) -> bool`\n\n. Address should be numerical IPv4 or IPv6 if you want the call to return immediately (i.e. do DNS resolution ahead of time), otherwise a DNS resolution might take some time and fail. If DNS failed, this call returns`false`\n\n. - regularly call\n`connected()`\n\nwaiting for`true`\n\nto detect when the connection is established. While`connected()`\n\nreturns`nil`\n\nthen connection is in-progress. If`connected()`\n\nchanges to`false`\n\nthen the connection was refused by the host. - if the connection is not established after a definite amount of time, you should declare 'timeout' and call\n`close()`\n\n- to send data: first call\n`listening()`\n\nto ensure that the socket is ready to send data. Note: the socket is always listening when the connection was just established. Then call`write()`\n\nto send you data (string or bytes), this call returns the actual amount of data sent; if it is lower than your content, you need to handle yourself re-sending the remaining data. Note: ensuring that you send less than the MTU should keep you from happening (~1430 bytes max). - to receive data: first call\n`available()`\n\nto check if some data is ready to be received. Then call`read()`\n\nor`readbytes()`\n\nto get the buffer as string or bytes. You can limit the amount of data received, but in such case, the extra data is discarded and lost. - regularly call\n`connected()`\n\nto check if the connection is still up - finally call\n`close()`\n\nto close the connection on your side and free resources. It is implicitly called if the connection was closed from the peer.\n\nFull example:\n\n``` python\ndef try_connect(addr, port)\n  import string\n  var tcp = tcpclientasync()\n  var now = tasmota.millis()\n  var r = tcp.connect(addr, port)\n  print(string.format(\"Time=%5i state=%s\", tasmota.millis()-now, str(tcp.connected())))\n  print(tcp.info())\n  tasmota.delay(50)\n  print(string.format(\"Time=%5i state=%s\", tasmota.millis()-now, str(tcp.connected())))\n  print(tcp.info())\n  tasmota.delay(150)\n  print(string.format(\"Time=%5i state=%s\", tasmota.millis()-now, str(tcp.connected())))\n  print(tcp.info())\n  tasmota.delay(500)\n  print(string.format(\"Time=%5i state=%s\", tasmota.millis()-now, str(tcp.connected())))\n  print(tcp.info())\n  return tcp\nend\ntcp = try_connect(\"192.168.1.19\", 80)\n```\n\n`tcpserver`\n\nclass[~](#tcpserver-class)\n\nSimple TCP server (socket) listening for incoming connection on any port.\n\n- create an instance of the\n`tcpserver`\n\non a specific port with`s = tcpserver(8888)`\n\n- periodically call\n`s.hasclient()`\n\nto know if a new client has connected - if the previous returned\n`true`\n\n, call`var c = s.accept()`\n\nor`var c = s.acceptasync()`\n\nto accept the connection. It returns an instance of`tcpclient`\n\nor`tcpclientasync`\n\n; it responds to the same APIs as outgoing TCP connection and allows text and binary transfers. - you can call\n`c.close()`\n\nto close the connection, or call`c.connected()`\n\nto know if it's still connected (i.e. the client hasn't closed the connection on their side) - close the server with\n`s.close()`\n\n. This will prevent the server from receiving any new connection, but existing connections are kept alive.\n\nFull example:\n\n```\n> s = tcpserver(8888)    # listen on port 8888\n> s.hasclient()\nfalse\n\n# in parallel connect on this port with `nc <ip_address> 8888`\n\n> s.hasclient()\ntrue               # we have an incoming connection\n> c = s.accept()\n> c\n<instance: tcpclient()>\n\n# send 'foobar' from the client\n> c.read()\nfoobar\n\n# send 'foobar2' again from the client\n> c.readbytes()\nbytes('666F6F626172320A')\n\n> c.close()\n# this closes the connection\n```\n\n`udp`\n\nclass[~](#udp-class)\n\nClass `udp`\n\nprovides ability to send and received UDP packets, including multicast addresses.\n\nYou need to create an object of class `udp`\n\n. 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.\n\nWhen creating a local port, you need to use `udp->begin(<ip>, <port)>`\n\n. If `<ip>`\n\nis empty string `\"\"`\n\nthen the port is open on all interfaces (wifi and ethernet).\n\n#### Sending udp packets[~](#sending-udp-packets)\n\n```\n> u = udp()\n> u.begin(\"\", 2000)    # listen on all interfaces, port 2000\ntrue\n> u.send(\"192.168.1.10\", 2000, bytes(\"414243\"))   # send 'ABC' to 192.168.1.10:2000, source port is 2000\ntrue\n```\n\n#### Receive udp packets[~](#receive-udp-packets)\n\nYou need to do polling on `udp->read()`\n\n. If no packet was received, the call immediately returns `nil`\n\n.\n\n```\n> u = udp()\n> u.begin(\"\", 2000)    # listen on all interfaces, port 2000\ntrue\n> u.read()     # if no packet received, returns `nil`\n>\n\n> u.read()     # if no packet received, returns `nil`\nbytes(\"414243\")    # received packet as `bytes()`\n```\n\n#### Simple UDP server printing received packets[~](#simple-udp-server-printing-received-packets)\n\n``` python\nclass udp_listener\n  var u\n  def init(ip, port)\n    self.u = udp()\n    print(self.u.begin_multicast(ip, port))\n    tasmota.add_driver(self)\n  end\n  def every_50ms()\n    import string\n    var packet = self.u.read()\n    while packet != nil\n      tasmota.log(string.format(\">>> Received packet ([%s]:%i): %s\", self.u.remote_ip, self.u.remote_port, packet.tohex()), 2)\n      packet = self.u.read()\n    end\n  end\nend\n\n# listen on port 2000 for all interfaces\n# udp_listener(\"\", 2000)\n```\n\n#### Send and receive multicast[~](#send-and-receive-multicast)\n\nIPv4 example, using the `udp_listener`\n\nlistener above.\n\nOn receiver side:\n\n```\nudp_listener(\"224.3.0.1\", 2000)\n```\n\nOn sender side:\n\n```\nu = udp()\nu.begin_multicast(\"224.3.0.1\", 2000)\nu.send_multicast(bytes().fromstring(\"hello\"))\n\n# alternatively\nu = udp()\nu.begin(\"\", 0)      # send on all interfaces, choose random port number\nu.send(\"224.3.0.1\", 2000, bytes().fromstring(\"world\"))\n```\n\nThe receiver will show:\n\n```\n>>> Received packet ([192.168.x.x]:2000): 68656C6C6F\n>>> Received packet ([192.168.x.x]:64882): 776F726C64\n```\n\nThis works the same with IPv6 using an address like \"FF35:0040:FD00::AABB\"\n\n`mdns`\n\nmodule[~](#mdns-module)\n\nModule `import mdns`\n\nsupport for mDNS (Multicast DNS, aka Bonjour protocol) announces. This is needed for Matter Wifi support.\n\nThis feature requires `#define USE_DISCOVERY`\n\ncompile option (not included in standard builds).\n\nExample (announce of a Matter Wifi device):\n\n``` python\nimport mdns\nmdns.start()\nmdns.add_service(\"_matterc\",\"_udp\", 5540, {\"VP\":\"65521+32768\", \"SII\":5000, \"SAI\":300, \"T\":1, \"D\":3840, \"CM\":1, \"PH\":33, \"PI\":\"\"})\n```\n\n### Addressable LEDs (WS2812, SK6812)[~](#addressable-leds-ws2812-sk6812)\n\nThere is native support for addressable LEDs via NeoPixelBus, with support for animations. Currently supported: WS2812, SK6812.\n\nDetails are in [Berry LEDs](../Berry_Addressable-LED/)\n\n`serial`\n\nclass[~](#serial-class)\n\nThe `serial`\n\nclass provides a low-level interface to hardware UART. The serial GPIOs don't need to be configured in the template.\n\nExample\n\n```\n# gpio_rx:4 gpio_tx:5\nser = serial(4, 5, 9600, serial.SERIAL_7E1)\n\nser.write(bytes(203132))   # send binary 203132\nser.write(bytes().fromstring(\"Hello))   # send string \"Hello\"\n\nmsg = ser.read()   # read bytes from serial as bytes\nprint(msg.asstring())   # print the message as string\n```\n\nSupported serial message formats: `SERIAL_5N1`\n\n, `SERIAL_6N1`\n\n, `SERIAL_7N1`\n\n, `SERIAL_8N1`\n\n, `SERIAL_5N2`\n\n, `SERIAL_6N2`\n\n, `SERIAL_7N2`\n\n, `SERIAL_8N2`\n\n, `SERIAL_5E1`\n\n, `SERIAL_6E1`\n\n, `SERIAL_7E1`\n\n, `SERIAL_8E1`\n\n, `SERIAL_5E2`\n\n, `SERIAL_6E2`\n\n, `SERIAL_7E2`\n\n, `SERIAL_8E2`\n\n, `SERIAL_5O1`\n\n, `SERIAL_6O1`\n\n, `SERIAL_7O1`\n\n, `SERIAL_8O1`\n\n, `SERIAL_5O2`\n\n, `SERIAL_6O2`\n\n, `SERIAL_7O2`\n\n, `SERIAL_8O2`\n\n`display`\n\nmodule[~](#display-module)\n\nThe `display`\n\nmodule provides a simple API to initialize the Universal Display Driver with data provided as a string. It is used by `autoconf`\n\nmechanism.\n\n`uuid`\n\nmodule[~](#uuid-module)\n\nThe `uuid`\n\nmodule allows to generate uuid4 random ids.\n\n``` python\n> import uuid\n> uuid.uuid4()\n1a8b7f78-59d8-4868-96a7-b7ff3477d43f\n```\n\n| Tasmota Function | Parameters and details |\n|---|---|\n| uuid4 | `uuid.uuid4() -> string` Generates a uuid4 random id as string. |\n\n`crc`\n\nmodule[~](#crc-module)\n\nThe `crc`\n\nmodule allows to compute crc32/16/8 from bytes() arrays.\n\n``` python\n> import crc\n> crc.crc32(0xFFFFFFFF, bytes(\"AABBCC\"))\n-1091314015\n> crc.crc16(0xFFFF, bytes(\"AABBCC\"))\n20980\n> crc.crc8(0xFF, bytes(\"AABBCC\"))\n139\n```\n\n`tasmota_log_reader`\n\nclass[~](#tasmota_log_reader-class)\n\nThe `tasmota_log_reader`\n\nclass 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`\n\nwidget to display logs on a display.\n\nNote: calling `tasmota_log_reader`\n\ncan be expensive in string allocations, and adds pressure on the garbage collector. Use wisely.\n\nExample:\n\n``` js\nvar lr = tasmota_log_reader()\n\n# do this regularly\nvar ret = lr.get_log(2)    # read at log level 2\nif ret != nil\n  var lines = r.split('\\n')  # extract as a list of lines\n  # do whatever you need\nend\n```\n\n`ULP`\n\nmodule[~](#ulp-module)\n\nThe `ULP`\n\nmodule 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.\n\n``` python\n# simple LED blink example\nimport ULP\nULP.wake_period(0,500000) # off time\nULP.wake_period(1,200000) # on time \nc = bytes(\"756c70000c006c00000000001000008000000000000000000000000010008072010000d0e5af2c72340040802705cc190005681d10008072e1af8c720100006821008072040000d0120080720800207004000068010005825c0000800405681d00000092680000800505681d0100009268000080000000b0\")\nULP.load(c)\nULP.run()\n```\n\nMore information (including suggestions for a toolchain) on the [ULP page](../ULP/).\n\n`re`\n\nregex module[~](#re-regex-module)\n\nUse with `import re`\n\n.\n\nThere 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.\n\n``` python\n> import re\n\n# first series are all-in-one, patterns are compiled on the fly\n\n# Returns the list of matches, or empty list of no match\n> re.search(\"a.*?b(z+)\", \"zaaaabbbccbbzzzee\")\n['aaaabbbccbbzzz', 'zzz']\n\n# Returns the list of list of matches\n> re.searchall('<([a-zA-Z]+)>', '<abc> yeah <xyz>')\n[['<abc>', 'abc'], ['<xyz>', 'xyz']]\n\n# Returns the list of matches, or empty list of no match; must match from the beginning of the string.\n> re.match(\"a.*?b(z+)\", \"aaaabbbccbbzzzee\")\n['aaaabbbccbbzzz', 'zzz']\n\n# Returns the number of chars matched instead of the entire match (saves memory)\n> re.match2(\"a.*?b(z+)\", \"aaaabbbccbbzzzee\")\n[14, 'zzz']\n\n# Returns the list of matches, or empty list of no match; there should not be any gaps between matches.\n> re.matchall('<([a-zA-Z]+)>', '<abc> yeah <xyz>')\n[['<abc>', 'abc']])\n> re.matchall('<([a-zA-Z]+)>', '<abc><xyz>')\n[['<abc>', 'abc'], ['<xyz>', 'xyz']]\n\n# Returns the list of strings from split\n> re.split('/', \"foo/bar//baz\")\n['foo', 'bar', '', 'baz']\n\n# below are pre-compiled patterns, which is much faster if you use the\n# pattern multiple times\n#\n# the compiled pattern is a `bytes()` object that can be used\n# as a replacement for the pattern string\n> rb = re.compilebytes('<([a-zA-Z]+)>')\n# rb is compiled to bytes('1A0000000C0000000100000062030260FB7E00013C7E020302617A415A62F87E03013E7E017F')\n\n> re.searchall(rb, '<abc> yeah <xyz>')\n[['<abc>', 'abc'], ['<xyz>', 'xyz']]\n\n> rb = re.compilebytes(\"/\")\n> rb\nbytes('0C000000070000000000000062030260FB7E00012F7E017F')\n\n> re.split(rb, \"foo/bar//baz\")\n['foo', 'bar', '', 'baz']\n> re.split(rb, \"/b\")\n['', 'b']\n```\n\nNote: for `match`\n\nand `search`\n\n, the first element in the list contains the global match of the pattern. Additional elements correspond to the sub-groups (in parenthesis).\n\nThe regex engine is based on [re1.5](https://github.com/pfalcon/re1.5) also used in MicroPython.\n\n`crypto`\n\nmodule[~](#crypto-module)\n\nModule `import crypto`\n\nsupport for common cryptographic algorithms.\n\nCurrently supported algorithms:\n\n- AES CTR 256 bits - requires\n`#define USE_BERRY_CRYPTO_AES_CTR`\n\n- AES GCM 256 bits\n- AES CCM 128 or 256 bits\n- AES CBC 128 bits\n- Elliptic Curve C25519 - requires\n`#define USE_BERRY_CRYPTO_EC_C25519`\n\n- Elliptic Curve P256 (secp256r1) - requires\n`#define USE_BERRY_CRYPTO_EC_P256`\n\n- HKDF key derivation with HMAC SHA256 - requires\n`#define USE_BERRY_CRYPTO_HKDF_SHA256`\n\n- HMAC SHA256\n- MD5\n- PKKDF2 with HMAC SHA256 key derivation - requires\n`#define USE_BERRY_CRYPTO_PBKDF2_HMAC_SHA256`\n\n- SHA256\n- JWT RS256 (RSASSA-PKCS1-v1_5 with SHA256) - requires\n`#define USE_BERRY_CRYPTO_RSA`\n\n`crypto.AES_CTR`\n\nclass[~](#cryptoaes_ctr-class)\n\nEncrypt and decrypt, using AES CTR (Counter mode) with 256 bits keys.\n\nTest vectors from [https://datatracker.ietf.org/doc/html/rfc4231](https://datatracker.ietf.org/doc/html/rfc4231)\n\n``` python\n# Test case from https://www.ietf.org/rfc/rfc3686.txt\nimport crypto\nkey = bytes(\"F6D66D6BD52D59BB0796365879EFF886C66DD51A5B6A99744B50590C87A23884\")\niv = bytes(\"00FAAC24C1585EF15A43D875\")\ncc = 0x000001\naes = crypto.AES_CTR(key)\nplain = bytes(\"000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F\")\ncipher = aes.encrypt(plain, iv, cc)\nassert(cipher == bytes(\"F05E231B3894612C49EE000B804EB2A9B8306B508F839D6A5530831D9344AF1C\"))\nplain2 = aes.decrypt(cipher, iv, cc)\nassert(plain == plain2)\n```\n\n`crypto.AES_GCM`\n\nclass[~](#cryptoaes_gcm-class)\n\nEncrypt, decrypt and verify, using AES GCM (Galois Counter Mode) with 256 bits keys.\n\nExample taken from [https://wizardforcel.gitbooks.io/practical-cryptography-for-developers-book/content/symmetric-key-ciphers/aes-encrypt-decrypt-examples.html](https://wizardforcel.gitbooks.io/practical-cryptography-for-developers-book/content/symmetric-key-ciphers/aes-encrypt-decrypt-examples.html)\n\n``` python\nimport crypto\n\nkey = bytes('233f8ce4ac6aa125927ccd98af5750d08c9c61d98a3f5d43cbf096b4caaebe80')\nciphertext = bytes('1334cd5d487f7f47924187c94424a2079656838e063e5521e7779e441aa513de268550a89917fbfb0492fc')\niv = bytes('2f3849399c60cb04b923bd33265b81c7')\nauthTag = bytes('af453a410d142bc6f926c0f3bc776390')\n\n# decrypt ciphertext with key and iv\naes = crypto.AES_GCM(key, iv)\nplaintext = aes.decrypt(ciphertext)\nprint(plaintext.asstring())\n# 'Message for AES-256-GCM + Scrypt encryption'\n\ntag = aes.tag()\nprint(tag == authTag)\n# true\n```\n\n`crypto.AES_CCM`\n\nclass[~](#cryptoaes_ccm-class)\n\nEncrypt and decrypt, using AES CCM with 256 bits keys.\n\nExample from Matter:\n\n```\n# raw_in is the received frame\nraw_in = bytes(\"00A0DE009A5E3D0F3E85246C0EB1AA630A99042B82EC903483E26A4148C8AC909B12EF8CDB6B144493ABD6278EDBA8859C9B2C\")\n\npayload_idx = 8     # unencrypted header is 8 bytes\ntag_len = 16        # MIC is 16 bytes\n\np = raw[payload_idx .. -tag_len - 1]   # payload\nmic = raw[-tag_len .. ]                # MIC\na = raw[0 .. payload_idx - 1]          # AAD\n\ni2r = bytes(\"92027B9F0DBC82491D4C3B3AFA5F2DEB\")   # key\n# p   = bytes(\"3E85246C0EB1AA630A99042B82EC903483E26A4148C8AC909B12EF\")\n# a     = bytes(\"00A0DE009A5E3D0F\")\nn   = bytes(\"009A5E3D0F0000000000000000\")         # nonce / IV\n# mic = bytes(\"8CDB6B144493ABD6278EDBA8859C9B2C\")\n\n# expected cleartext\nclr = bytes(\"05024FF601001536001724020024031D2404031818290324FF0118\")\n\n# method 1 - with distinct calls\nimport crypto\naes = crypto.AES_CCM(i2r, n, a, size(p), 16)\ncleartext = aes.decrypt(p)\ntag = aes.tag()\n\nassert(cleartext == clr)\nassert(tag == mic)\n\n# method 2 - single call\nraw = raw_in.copy()      # copy first if we want to keep the encrypted version\nvar ret = crypto.AES_CCM.decrypt1(i2r, n, 0, size(n), raw, 0, payload_idx, raw, payload_idx, size(raw) - payload_idx - tag_len, raw, size(raw) - tag_len, tag_len)\n\nassert(ret)\nassert(raw[payload_idx .. -tag_len - 1] == clr)\n```\n\n`crypto.AES_CBC`\n\nclass[~](#cryptoaes_cbc-class)\n\nEncrypt and decrypt, using AES CBC with 128 bits keys.\n\nExample:\n\n``` js\nvar b = bytes().fromstring(\"hello world_____\") # 16-byte aligned\nvar key = bytes().fromstring(\"1122334455667788\") # 16 bytes\nvar iv = bytes().fromstring(\"8877665544332211\") # 16 bytes\n\nprint(\"data:\",b.asstring()) # \"hello world_____\"\nimport crypto\naes = crypto.AES_CBC()\naes.encrypt1(key, iv, b)\nprint(\"cipher:\",b)\niv = bytes().fromstring(\"8877665544332211\")\naes.decrypt1(key, iv, b)\nprint(\"decrypted data:\",b.asstring()) # \"hello world_____\"\n```\n\n`crypto.EC_C25519`\n\nclass[~](#cryptoec_c25519-class)\n\nProvides Elliptic Curve C25519 Diffie-Hellman key agreement. Requires `#define USE_BERRY_CRYPTO_EC_C25519`\n\nExample from test vectors [https://www.rfc-editor.org/rfc/rfc7748](https://www.rfc-editor.org/rfc/rfc7748):\n\n``` python\nimport crypto\n\n# alice side\nalice_priv_key = bytes(\"77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a\")\nalice_pub_key = bytes(\"8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a\")\nassert(crypto.EC_C25519().public_key(alice_priv_key) == alice_pub_key)\n\n# bob side\nbob_priv_key = bytes(\"5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb\")\nbob_pub_key = bytes(\"de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f\")\nassert(crypto.EC_C25519().public_key(bob_priv_key) == bob_pub_key)\n\n# shared key computed by alice\nref_shared_key = bytes(\"4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742\")\nalice_shared_key = crypto.EC_C25519().shared_key(alice_priv_key, bob_pub_key)\nbob_shared_key = crypto.EC_C25519().shared_key(bob_priv_key, alice_pub_key)\nassert(alice_shared_key == ref_shared_key)\nassert(bob_shared_key == ref_shared_key)\n```\n\n`crypto.EC_P256`\n\nclass[~](#cryptoec_p256-class)\n\nProvides Elliptic Curve Prime256 (secp256r1) Diffie-Hellman key agreement and various functions on P256 curve. Requires `#define USE_BERRY_CRYPTO_EC_P256`\n\nExample:\n\n``` python\nimport crypto\npriv = bytes(\"f502fb911d746b77f4438c674e1c43650b68285dfcc0583c49cd6ed88f0fbb58\")\np = crypto.EC_P256()\npub = p.public_key(priv)\nassert(pub == bytes(\"04F94C20D682DA29B7E99985D8DBA6ABEA9051D16508742899835098B1113D3D749466644C47B559DB184556C1733C33E5788AE250B8FB45F29D4CF48FF752C1ED\"))\n\nimport crypto\npriv = bytes(\"4E832960415F2B5FA2B1FDA75C1A8F3C84BAEB189EDC47211EF6D27A21FC0ED8\")\np = crypto.EC_P256()\npub = p.public_key(priv)\nassert(pub == bytes(\"042166AE4F89981472B7589B8D79B8F1244E2EEE6E0A737FFBFED2981DA3E193D6643317E054D2A924F2F56F1BF4BECA13192B27D8566AF379FBBF8615A223D899\"))\nprint(\"x=\",pub[1..32])\nprint(\"y=\",pub[33..65])\n\nimport crypto\np = crypto.EC_P256()\npriv_A = bytes(\"f502fb911d746b77f4438c674e1c43650b68285dfcc0583c49cd6ed88f0fbb58\")\npub_A = bytes(\"04F94C20D682DA29B7E99985D8DBA6ABEA9051D16508742899835098B1113D3D749466644C47B559DB184556C1733C33E5788AE250B8FB45F29D4CF48FF752C1ED\")\npriv_B = bytes(\"4E832960415F2B5FA2B1FDA75C1A8F3C84BAEB189EDC47211EF6D27A21FC0ED8\")\npub_B = bytes(\"042166AE4F89981472B7589B8D79B8F1244E2EEE6E0A737FFBFED2981DA3E193D6643317E054D2A924F2F56F1BF4BECA13192B27D8566AF379FBBF8615A223D899\")\n\nshared_1 = p.shared_key(priv_A, pub_B)\nshared_2 = p.shared_key(priv_B, pub_A)\nassert(shared_1 == shared_2)\n```\n\n`crypto.HKDF_SHA256`\n\nclass[~](#cryptohkdf_sha256-class)\n\nProvides 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`\n\nTest vectors from [https://www.rfc-editor.org/rfc/rfc5869](https://www.rfc-editor.org/rfc/rfc5869)\n\n``` python\nimport crypto\n\n# Test Case 1\nhk = crypto.HKDF_SHA256()\nikm = bytes(\"0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B\")\nsalt = bytes(\"000102030405060708090A0B0C\")\ninfo = bytes(\"F0F1F2F3F4F5F6F7F8F9\")\nk = hk.derive(ikm, salt, info, 42)\nassert(k == bytes(\"3CB25F25FAACD57A90434F64D0362F2A2D2D0A90CF1A5A4C5DB02D56ECC4C5BF34007208D5B887185865\"))\n\n# Test Case 2\nhk = crypto.HKDF_SHA256()\nikm  = bytes(\"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f\")\nsalt = bytes(\"606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf\")\ninfo = bytes(\"b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff\")\nk = hk.derive(ikm, salt, info, 82)\nassert(k == bytes(\"b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87\"))\n\n# Test Case 3\nhk = crypto.HKDF_SHA256()\nikm  = bytes(\"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b\")\nsalt = bytes()\ninfo = bytes()\nk = hk.derive(ikm, salt, info, 42)\nassert(k == bytes(\"8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8\"))\n```\n\n`crypto.PBKDF2_HMAC_SHA256`\n\nclass[~](#cryptopbkdf2_hmac_sha256-class)\n\nProvides PBKDF2 using HMAC SHA256 key derivation. Turns a password into a hash.\n\nTest vectors from [https://github.com/brycx/Test-Vector-Generation/blob/master/PBKDF2/pbkdf2-hmac-sha2-test-vectors.md](https://github.com/brycx/Test-Vector-Generation/blob/master/PBKDF2/pbkdf2-hmac-sha2-test-vectors.md)\n\n``` python\nimport crypto\npb = crypto.PBKDF2_HMAC_SHA256()\n\nassert(pb.derive(\"password\", \"salt\", 1, 20) == bytes('120fb6cffcf8b32c43e7225256c4f837a86548c9'))\n\nassert(pb.derive(\"password\", \"salt\", 2, 20) == bytes('ae4d0c95af6b46d32d0adff928f06dd02a303f8e'))\n\nassert(pb.derive(\"password\", \"salt\", 3, 20) == bytes('ad35240ac683febfaf3cd49d845473fbbbaa2437'))\n\nassert(pb.derive(\"password\", \"salt\", 4096, 20) == bytes('c5e478d59288c841aa530db6845c4c8d962893a0'))\n\nassert(pb.derive(\"passwd\", \"salt\", 1, 128) == bytes('55AC046E56E3089FEC1691C22544B605F94185216DDE0465E68B9D57C20DACBC49CA9CCCF179B645991664B39D77EF317C71B845B1E30BD509112041D3A19783C294E850150390E1160C34D62E9665D659AE49D314510FC98274CC79681968104B8F89237E69B2D549111868658BE62F59BD715CAC44A1147ED5317C9BAE6B2A'))\n```\n\n`crypto.SHA256`\n\nclass[~](#cryptosha256-class)\n\nProvides SHA256 hashing function\n\nExample test vectors from [https://www.dlitz.net/crypto/shad256-test-vectors/](https://www.dlitz.net/crypto/shad256-test-vectors/)\n\n``` python\nimport crypto\nh = crypto.SHA256()\n\n# SHA256 of empty message\nassert(h.out() == bytes(\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\"))\n\n# (first 16 bytes of RC4 keystream where the key = 0)\nh.update(bytes(\"de188941a3375d3a8a061e67576e926d\"))\nassert(h.out() == bytes(\"067c531269735ca7f541fdaca8f0dc76305d3cada140f89372a410fe5eff6e4d\"))\n```\n\n`crypto.HMAC_SHA256`\n\nclass[~](#cryptohmac_sha256-class)\n\nProvides HMAC SHA256 hashing function\n\nTest case from [https://datatracker.ietf.org/doc/html/rfc4231](https://datatracker.ietf.org/doc/html/rfc4231):\n\n``` python\nimport crypto\nkey = bytes(\"4a656665\")\nmsg = bytes(\"7768617420646f2079612077616e7420666f72206e6f7468696e673f\")\nh = crypto.HMAC_SHA256(key)\nh.update(msg)\nhmac = h.out()\nassert(hmac == bytes(\"5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843\"))\n```\n\n`crypto.RSA`\n\nclass[~](#cryptorsa-class)\n\nProvides RSA core features, currently only JWT RS256 signing (RSASSA-PKCS1-v1_5 with SHA256) - requires `#define USE_BERRY_CRYPTO_RSA`\n\nSigning a full JWT token with RS256\n\n``` python\nimport string\nimport crypto\n\n# JWT requires base64url and not raw base64\n# see https://base64.guru/standards/base64url\n# input: string or bytes\ndef base64url(v)\n  import string\n  if type(v) == 'string'   v = bytes().fromstring(v) end\n  var b64 = v.tob64()\n  # remove trailing padding\n  b64 = string.tr(b64, '=', '')\n  b64 = string.tr(b64, '+', '-')\n  b64 = string.tr(b64, '/', '_')\n  return b64\nend\n\n# JWT header and claim\nvar header = '{\"alg\":\"RS256\",\"typ\":\"JWT\"}'\nvar claim = '{\"sub\":\"1234567890\",\"name\":\"John Doe\",\"admin\":true,\"iat\":1516239022}'\nvar b64header = base64url(header)\nvar b64claim = base64url(claim)\n\nassert(b64header == 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9')\nassert(b64claim == 'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0')\n\n# `body` is the payload to sign with RS256\nvar body = b64header + '.' + b64claim\nassert(body == 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0')\n\nvar private_key =\n'-----BEGIN PRIVATE KEY-----\\n'+\n'MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7VJTUt9Us8cKj\\n'+\n'MzEfYyjiWA4R4/M2bS1GB4t7NXp98C3SC6dVMvDuictGeurT8jNbvJZHtCSuYEvu\\n'+\n'NMoSfm76oqFvAp8Gy0iz5sxjZmSnXyCdPEovGhLa0VzMaQ8s+CLOyS56YyCFGeJZ\\n'+\n'qgtzJ6GR3eqoYSW9b9UMvkBpZODSctWSNGj3P7jRFDO5VoTwCQAWbFnOjDfH5Ulg\\n'+\n'p2PKSQnSJP3AJLQNFNe7br1XbrhV//eO+t51mIpGSDCUv3E0DDFcWDTH9cXDTTlR\\n'+\n'ZVEiR2BwpZOOkE/Z0/BVnhZYL71oZV34bKfWjQIt6V/isSMahdsAASACp4ZTGtwi\\n'+\n'VuNd9tybAgMBAAECggEBAKTmjaS6tkK8BlPXClTQ2vpz/N6uxDeS35mXpqasqskV\\n'+\n'laAidgg/sWqpjXDbXr93otIMLlWsM+X0CqMDgSXKejLS2jx4GDjI1ZTXg++0AMJ8\\n'+\n'sJ74pWzVDOfmCEQ/7wXs3+cbnXhKriO8Z036q92Qc1+N87SI38nkGa0ABH9CN83H\\n'+\n'mQqt4fB7UdHzuIRe/me2PGhIq5ZBzj6h3BpoPGzEP+x3l9YmK8t/1cN0pqI+dQwY\\n'+\n'dgfGjackLu/2qH80MCF7IyQaseZUOJyKrCLtSD/Iixv/hzDEUPfOCjFDgTpzf3cw\\n'+\n'ta8+oE4wHCo1iI1/4TlPkwmXx4qSXtmw4aQPz7IDQvECgYEA8KNThCO2gsC2I9PQ\\n'+\n'DM/8Cw0O983WCDY+oi+7JPiNAJwv5DYBqEZB1QYdj06YD16XlC/HAZMsMku1na2T\\n'+\n'N0driwenQQWzoev3g2S7gRDoS/FCJSI3jJ+kjgtaA7Qmzlgk1TxODN+G1H91HW7t\\n'+\n'0l7VnL27IWyYo2qRRK3jzxqUiPUCgYEAx0oQs2reBQGMVZnApD1jeq7n4MvNLcPv\\n'+\n't8b/eU9iUv6Y4Mj0Suo/AU8lYZXm8ubbqAlwz2VSVunD2tOplHyMUrtCtObAfVDU\\n'+\n'AhCndKaA9gApgfb3xw1IKbuQ1u4IF1FJl3VtumfQn//LiH1B3rXhcdyo3/vIttEk\\n'+\n'48RakUKClU8CgYEAzV7W3COOlDDcQd935DdtKBFRAPRPAlspQUnzMi5eSHMD/ISL\\n'+\n'DY5IiQHbIH83D4bvXq0X7qQoSBSNP7Dvv3HYuqMhf0DaegrlBuJllFVVq9qPVRnK\\n'+\n'xt1Il2HgxOBvbhOT+9in1BzA+YJ99UzC85O0Qz06A+CmtHEy4aZ2kj5hHjECgYEA\\n'+\n'mNS4+A8Fkss8Js1RieK2LniBxMgmYml3pfVLKGnzmng7H2+cwPLhPIzIuwytXywh\\n'+\n'2bzbsYEfYx3EoEVgMEpPhoarQnYPukrJO4gwE2o5Te6T5mJSZGlQJQj9q4ZB2Dfz\\n'+\n'et6INsK0oG8XVGXSpQvQh3RUYekCZQkBBFcpqWpbIEsCgYAnM3DQf3FJoSnXaMhr\\n'+\n'VBIovic5l0xFkEHskAjFTevO86Fsz1C2aSeRKSqGFoOQ0tmJzBEs1R6KqnHInicD\\n'+\n'TQrKhArgLXX4v3CddjfTRJkFWDbE/CkvKZNOrcf1nhaGCPspRJj2KUkj1Fhl9Cnc\\n'+\n'dn/RsYEONbwQSjIfMPkvxF+8HQ==\\n'+\n'-----END PRIVATE KEY-----\\n'\n\n# public_key for reference but not actually used here\nvar public_key =\n'-----BEGIN PUBLIC KEY-----\\n'+\n'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\\n'+\n'4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\\n'+\n'+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\\n'+\n'kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\\n'+\n'0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\\n'+\n'cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\\n'+\n'mwIDAQAB\\n'+\n'-----END PUBLIC KEY-----\\n'\n\n# read private_key as DER binary\nwhile (private_key[-1] == '\\n') private_key = private_key[0..-2] end\nvar private_key_DER = bytes().fromb64(string.split(private_key, '\\n')[1..-2].concat())\n\n# comparison with what was expected\nassert(private_key_DER.tob64() == 'MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7VJTUt9Us8cKjMzEfYyjiWA4R4/M2bS1GB4t7NXp98C3SC6dVMvDuictGeurT8jNbvJZHtCSuYEvuNMoSfm76oqFvAp8Gy0iz5sxjZmSnXyCdPEovGhLa0VzMaQ8s+CLOyS56YyCFGeJZqgtzJ6GR3eqoYSW9b9UMvkBpZODSctWSNGj3P7jRFDO5VoTwCQAWbFnOjDfH5Ulgp2PKSQnSJP3AJLQNFNe7br1XbrhV//eO+t51mIpGSDCUv3E0DDFcWDTH9cXDTTlRZVEiR2BwpZOOkE/Z0/BVnhZYL71oZV34bKfWjQIt6V/isSMahdsAASACp4ZTGtwiVuNd9tybAgMBAAECggEBAKTmjaS6tkK8BlPXClTQ2vpz/N6uxDeS35mXpqasqskVlaAidgg/sWqpjXDbXr93otIMLlWsM+X0CqMDgSXKejLS2jx4GDjI1ZTXg++0AMJ8sJ74pWzVDOfmCEQ/7wXs3+cbnXhKriO8Z036q92Qc1+N87SI38nkGa0ABH9CN83HmQqt4fB7UdHzuIRe/me2PGhIq5ZBzj6h3BpoPGzEP+x3l9YmK8t/1cN0pqI+dQwYdgfGjackLu/2qH80MCF7IyQaseZUOJyKrCLtSD/Iixv/hzDEUPfOCjFDgTpzf3cwta8+oE4wHCo1iI1/4TlPkwmXx4qSXtmw4aQPz7IDQvECgYEA8KNThCO2gsC2I9PQDM/8Cw0O983WCDY+oi+7JPiNAJwv5DYBqEZB1QYdj06YD16XlC/HAZMsMku1na2TN0driwenQQWzoev3g2S7gRDoS/FCJSI3jJ+kjgtaA7Qmzlgk1TxODN+G1H91HW7t0l7VnL27IWyYo2qRRK3jzxqUiPUCgYEAx0oQs2reBQGMVZnApD1jeq7n4MvNLcPvt8b/eU9iUv6Y4Mj0Suo/AU8lYZXm8ubbqAlwz2VSVunD2tOplHyMUrtCtObAfVDUAhCndKaA9gApgfb3xw1IKbuQ1u4IF1FJl3VtumfQn//LiH1B3rXhcdyo3/vIttEk48RakUKClU8CgYEAzV7W3COOlDDcQd935DdtKBFRAPRPAlspQUnzMi5eSHMD/ISLDY5IiQHbIH83D4bvXq0X7qQoSBSNP7Dvv3HYuqMhf0DaegrlBuJllFVVq9qPVRnKxt1Il2HgxOBvbhOT+9in1BzA+YJ99UzC85O0Qz06A+CmtHEy4aZ2kj5hHjECgYEAmNS4+A8Fkss8Js1RieK2LniBxMgmYml3pfVLKGnzmng7H2+cwPLhPIzIuwytXywh2bzbsYEfYx3EoEVgMEpPhoarQnYPukrJO4gwE2o5Te6T5mJSZGlQJQj9q4ZB2Dfzet6INsK0oG8XVGXSpQvQh3RUYekCZQkBBFcpqWpbIEsCgYAnM3DQf3FJoSnXaMhrVBIovic5l0xFkEHskAjFTevO86Fsz1C2aSeRKSqGFoOQ0tmJzBEs1R6KqnHInicDTQrKhArgLXX4v3CddjfTRJkFWDbE/CkvKZNOrcf1nhaGCPspRJj2KUkj1Fhl9Cncdn/RsYEONbwQSjIfMPkvxF+8HQ==')\n\n# sign body\nvar body_b64 = bytes().fromstring(body)\nvar sign = crypto.RSA.rs256(private_key_DER, body_b64)\nvar b64sign = base64url(sign)\n\n# check output\nassert(b64sign == 'NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ')\n\n# Final token:\nvar jwt_token = payload + '.' + b64sign\n```\n\n`crypto.MD5`\n\nclass[~](#cryptomd5-class)\n\nProvides MD5 hashing function.\n\nTest vector:\n\n``` python\nimport crypto\nh = crypto.MD5()\nt = bytes().fromstring(\"The quick brown fox jumps over the lazy dog\")\nh.update(t)\nm = h.finish()\nassert(m == bytes(\"9e107d9d372bb6826bd81d3542a419d6\"))\n```\n\n`flash`\n\nmodule[~](#flash-module)\n\nWarning: this is a low-level module used to read and write flash memory. You normally shouldn't need to use it. It is used internally by `partition_core`\n\n. Use with `import flash`\n\n.\n\n`img`\n\nclass[~](#img-class)\n\nThin wrapper for image data, that allows format conversions and is able to reduce memory reallocations in certain scenarios.\n\nSupports following image types, which integer values are equal to the enum `pixformat_t`\n\nof Espressif's webcam driver:\n\n- `img.RGB565`\n\n= 0\n\n- `img.RGB888`\n\n= 5\n\n- `img.JPEG`\n\n= 4\n\n- `img.GRAYSCALE`\n\n= 3\n\nCreate an instance of an image with `var i = img()`\n\n.\n\nMemory will be released automatically by Berry's garbage collector after deletion of the instance.\n\n| img Function | Parameters and details |\n|---|---|\n| from_jpeg | `img.from_jpeg(jpeg_buffer:bytes[, type:img.type]) -> nil` Copy JPEG image as byte buffer to the buffer of an `img` instance. If optional image type is provided, this will be converted on the fly. This will not reallocate the image buffer, if the size and format does not change. |\n| from_buffer | `img.from_buffer(image_data:bytes,width:int:height:int,type:img.type) -> nil` Construct image from raw image data for the types `RGB565` , `RGB888` and `GRAYSCALE` . |\n| get_buffer | `img.get_buffer([descriptor:bytes]) -> image_data:bytes` Returns the raw image data for any supported type. For `RGB565` , `RGB888` and `GRAYSCALE` a descriptor can be provided to get a ROI (region of interest). |\n| convert_to | `img.convert_to(type:img.type) -> nil` Internal conversion of the image format. |\n| info | `img.info() -> map` Returns a map with some information about the current image. |\n\nThe optional ROI descriptor is a representation of an affine matrix, which can be constructed in Berry:\n\n```\n#  Describe ROI using an affine matrix (https://en.wikipedia.org/wiki/Affine_transformation#Image_transformation)\n#   | scale_x shear_x translation_x |\n#   | shear_y scale_y translation_y |\n#   | 0       0       1             | - these are constants in this scope\n\ndef roi_dsc(m)\n    var d = bytes(-24)\n    d.setfloat(0,m[\"scaleX\"])\n    d.setfloat(4,m[\"shearX\"])\n    d.setfloat(8,m[\"shearY\"])\n    d.setfloat(12,m[\"scaleY\"])\n    d.seti(16,m[\"transX\"],2)\n    d.seti(18,m[\"transY\"],2)\n    d.seti(20,m[\"width\"],2)\n    d.seti(22,m[\"height\"],2)\n    return d\nend\n```\n\n[ROI editor](../ROI-editor/)\n\nExample:\n\n``` js\n# load jpg file into img\nvar i = img()\nvar f = open(\"j.jpg\",\"r\")\ni.from_jpg(f.readbytes(),img.RGB565) # i now holds image data with type RGB565\nf.close()\n```\n\n`cam`\n\nmodule[~](#cam-module)\n\nVery small module to access a connected camera module with the purpose to have as much heap memory available as possible in comparison to the fully fledged webcam drivers for machine learning, but there are more possible applications. It is not intended to be a general replacement for the webcam drivers.\n\n| Tasmota Function | Parameters and details |\n|---|---|\n| cam.setup | `(mode:int) -> bool` Init camera hardware with the resolution (same value as command `wcresolution` ). |\n| cam.get_image | `([image:img[,type:img.type]]) -> bytes or nil` Takes a picture - without an additional option this is just a JPEG buffer. If an image instance is provided, the image data will go there. If an additional type is given, a conversion will happen on the fly. This will not lead to a memory reallocation, if there is no change for size and type of the image. |\n| cam.info | `() -> map` Shows info map with last current resolution and camera mode |\n\nExample:\n\n```\n# Simple \"video player\" for boards with a camera and a display\n\nscr = lv.scr_act()\nscr.set_style_bg_color(lv.color(lv.COLOR_BLUE), lv.PART_MAIN | lv.STATE_DEFAULT)\n\n# create a lv_img object as image view\ncam_view = lv.img(scr)\ncam_view.center()\n\ni = img()\n\nimport cam\ncam.setup(5) # 240 x 240\ncam.get_image(i,i.RGB565LE)\n\ndef lv_img_dsc(image)\n    var i = image.info()\n    var dsc = bytes(24)\n    dsc..0x19 # magic\n    dsc..0x12 # cf RGB565\n    dsc.add(0,2) # flags\n    dsc.add(i[\"width\"],2) # width\n    dsc.add(i[\"height\"],2) # height\n    dsc.add(i[\"width\"] * 2,2) # stride\n    dsc.add(0,2) # reserved\n    dsc.add(i[\"size\"],4) # size\n    dsc.add(i[\"buf_addr\"],4) # data\n    dsc.add(0,4) # reserved\n    print(dsc)\n    return dsc\nend\n\ndescriptor = lv_img_dsc(i)\ncam_view.set_src(descriptor) # bind cam_view to buffer of img\n\ndef video()\n    cam.get_image(i,i.RGB565LE) # this will just update the buffer, no reallocation\n    cam_view.invalidate()\n    tasmota.set_timer(20,/->video()) # aim for 50 Hz\nend\n\nvideo()\n```\n\n`BLE`\n\nmodule[~](#ble-module)\n\nWrite drivers and applications for Bluetooth Low Energy supporting all 4 roles. More information here: [BLE module](../Bluetooth_MI32/#ble-module)\n\n## Philips Hue emulation for Alexa[~](#philips-hue-emulation-for-alexa)\n\nBerry extends the native Hue/Alexa emulation and makes it possible to handle any number of virtual lights. You can easily define \"virtual\" lights in Berry, respond to commands from Alexa and send light status.\n\nIt is up to you to define the final behavior. For example you could control some fancy devices, light strips or whatever takes on/off, dimmer or RGB commands. Your imagination is the limit.\n\nHue emulation requires both `#define USE_EMULATION`\n\nand `#define USE_EMULATION_HUE`\n\n. Emulation must also be enabled with `Emulation 2`\n\ncommand.\n\n`light_state`\n\nclass[~](#light_state-class)\n\nThe core class is `light_state`\n\nwhich represents a virtual light.\n\n`light_state`\n\ngeneral methods:\n\n`light_state`\n\ngetters:\n\n| Attributes | Parameters and details |\n|---|---|\n| power | `(bool)` on/off state |\n| reachable | `(bool)` light is reachable |\n| type | `(int)` number of channels of the light |\n| bri | `(int)` brightness of the light (0..255) |\n| ct | `(int)` white temperature of the light (153..500) |\n| sat | `(int)` saturation of the light (0..255) |\n| hue | `(int)` hue of the light (0..360) |\n| hue16 | `(int)` hue as 16 bits (0..65535) |\n| r g b | `(int)` Red Green Blue channels (0..255) |\n| x y | `(float)` x/y color as floats (0.0 .. 1.0) |\n| mode_ct mode_rgb | `(bool)` light is in RGB or CT mode |\n| get | `get() -> map` returns the complete state of the light as a mapExample: `{'rgb': '1E285A', 'hue': 230, 'type': 5, 'power': false, 'bri': 90, 'mode_rgb': true, 'sat': 170, 'mode_ct': false, 'channels': [30, 40, 90, 0, 0]}` |\n\n`light_state`\n\nsetters:\n\n| Methods | Parameters and details |\n|---|---|\n| set_power | `set_power(bool) -> nil` sets on/off state |\n| set_reachable | `set_reachable(bool) -> nil` sets the reachable state |\n| set_bri | `set_bri(int) -> nil` sets the brightness (0..255) |\n| set_ct | `set_ct(int) -> nil` sets the white temperature (153..500) |\n| set_sat | `set_sat(int) -> nil` sets the saturation (0..255) |\n| set_huesat | `set_huesat(hue:int, sat:int) -> nil` sets hue and saturation (0..360, 0..255) |\n| set_hue16sat | `set_hue16sat(hue16:int, sat:int) -> nil` sets hue16 and saturation (0..65535, 0..255) |\n| set_rgb | `set_rgb(r:int, g:int, b=int) -> nil` sets red/green/blue channels (0..255 x 3) |\n| set_xy | `set_xy(x:float, y:float) -> nil` sets color as x/y (0.0 .. 1.0 x 2) |\n\n`light_state`\n\nstatic helper functions:\n\n| Methods | Parameters and details |\n|---|---|\n| gamma8 | `gamma8(int) -> nil` applies gamma correction to 8 bits value (0..255) |\n| gamma10 | `gamma10(int) -> nil` applies gamma correction to 10 bits value (0..1023) |\n| reverse_gamma10 | `reverse_gamma10(int) -> nil` applies reverse gamma correction to 10 bits value (0..1023) |\n\n`hue_bridge`\n\nmodule[~](#hue_bridge-module)\n\nUse `import hue_bridge`\n\nand declare all the virtual lights. Example:\n\n``` python\n# put this in `autoexec.be`\nimport hue_bridge\n\nl1 = light_state(light_state.DIMMER)\nhue_bridge.add_light(11, l1, \"Synthetic Dimmer\", \"V1\", \"Tasmota Factory\")\n\nl2 = light_state(light_state.CT)\nhue_bridge.add_light(12, l2, \"Synthetic CT\", \"V1\", \"Tasmota Factory\")\n\nl5 = light_state(light_state.RGBCT)\nhue_bridge.add_light(15, l5, \"Synthetic RGBCT\")\n```\n\nWhen you start the Hue pairing, all virtual lights are advertised. You need to make sure that virtual lights are defined at each restart (in `autoexec.be`\n\nfor example).\n\n`hue_bridge`\n\nfunctions:\n\n| Methods | Parameters and details |\n|---|---|\n| add_light | `add_light(id:int, light:instance of light_state, name:string [, model:string, manuf:strin]) -> light` Adds an virtual light to the Hue bridge. `id` = numerical identifier of the Hue light. Using low numbers avoids conflict with real lights from Tasmota`light` = instance of `light_state` handling the state and behavior of the light`name` = name of the light as displayed in the Alexa app (can be overridden in the app)`model` (opt) = name of the manufacturer model, defaults to \"Unknown\"`manuf` (opt) = name of the manufacturer, defaults to \"Tasmota\" |\n| remove_light | `remove_light(id:int) -> nil` Removes a light from the Hue bridge by hue id. |\n| light_to_id | `light_to_id(light:instance) -> int` converts a registered `light_instance` instance to its Hue id |\n\n## Zigbee[~](#zigbee)\n\nFor Zigbee coordinators, there is a Berry mapping that allows explore Zigbee configurations and devices. It also allows to intercept incoming message (low and high level) and transform messages before they reach the Tasmota layer. This is useful for non-standard Zigbee devices for which Zigbee plug-ins are not sufficient.\n\nNote: the following are only available when compiling with `#define USE_ZIGBEE`\n\nInternally, the Tasmota Zigbee engine calls `callBerryZigbeeDispatcher()`\n\nat key points to allow your Berry code to take over and change messages on-the-fly.\n\n`import zigbee`\n\n[~](#import-zigbee)\n\nFirst step is to use `import zigbee`\n\nwhich returns an instance (monad) of `zb_coord()`\n\n.\n\n| General methods | Parameters and details |\n|---|---|\n| started | `zigbee.started() -> bool or nil` Returns `true` if Zigbee successfully started, then all other Zigbee methods are available. This state is final and does not change.Returns `false` if Zigbee is still in initialization process. This state eventually changes to `true` or `nil` .Returns `nil` if Zigbee is not configured (no GPIO) or if initialization failed. This state is final and indicates a fatal error. |\n| info | `zigbee.info() -> map` returns a map with general configuration of the Zigbee coordinator.Format is identical to `ZbConfig` Example: `{'ext_pan_id': '0xCCCCCCCCA11A2233', 'tx_radio': 20, 'shortaddr': 0, 'longaddr': '0x00124B0026BAABBC', 'channel': 11, 'pan_id': 837, 'pan_id_hex': '0x0345', 'shortaddr_hex': '0x0000'}` |\n| size | `zigbee.size() -> int` returns the number of devices known by the coordinator |\n| iter | `zigbee.iter() -> iterator` Returns an iterator on all Zigbee devices Use compact implicit form: `for ze: zigbee print(ze) end` |\n| item [] | `zigbee.item(shortaddr:int | friendlyname:str) -> instance of zb_device` Returns the Zigbee device corresponding to short address `shortaddr` or to friendly name `friendlyname` .Returns an `index_error` exception if not found.You can use the compact syntax `zigbee[0xFAB6]` |\n| find | `zigbee.find(shortaddr:int | friendlyname:str) -> instance of zb_device` Returns the Zigbee device corresponding to short address `shortaddr` or to friendly name `friendlyname` .Contrary to the above, returns `nil` if not found (no exception). |\n| abort | `zigbee.abort() -> nil` aborts the initialization of Zigbee MCU. To be used when initialization of Zigbee failed |\n\n`zb_device`\n\nclass[~](#zb_device-class)\n\nThe class `zb_device`\n\ncontains all known information about a paired Zigbee device (end-device or router). You can't create a `zb_device`\n\nfrom scratch, they most be retrieved from `zigbee`\n\nobject.\n\n| General methods | Parameters and details |\n|---|---|\n| info | `info() -> attribute_list or nil` Returns the last known state for this device as an `attribute_list` This is equivalent of running `ZbInfo |\n\n`zb_device`\n\ninstances can only be read, you can't change directly any attribute.\n\n| Instance Variables | Parameters and details |\n|---|---|\n| shortaddr | `shortaddr -> int` returns the 16 bits short address |\n| longaddr | `longaddr -> bytes` returns the long 64 bits address as 8 bytes (or all zeroes if unknown) |\n| name | `name -> string` returns the friendly name of the device or `0x....` hex name if no friendly name was defined using `ZbName` command |\n| reachable | `reachable -> bool` is the device reachable, i.e. did it respond last time we tried to contact them |\n| hidden | `hidden -> bool` is the device declared as hidden, i.e. not announced in Hue emulation |\n| router | `router -> bool` is the device known to be a router |\n| model | `model -> string` model of the device |\n| manufacturer | `manufacturer -> string` manufacturer name of the device |\n| lastseen | `lastseen -> int` timestamp (epoch) when the device was last seen |\n| lqi | `lqi -> int` radio strength and quality when the device was last seen |\n| battery | `battery -> int` percentage of battery, or `-1` if unknown of no battery |\n| battery_lastseen | `battery_lastseen -> int` timestamp (epoch) when the battery was last reported, or `-1` |\n\nExample:\n\n``` python\nimport zigbee\n\n# show all devices\nfor device: zigbee\n  print(device)\nend\n#\n# outputs:\n# <instance: zb_device(0x868E, 0x00124B001F841E41, name:'Bedroom', model:'TH01', manufacturer:'eWeLink')>\n# ... more devices\n\n# read one device by short address\nvar device = zigbee[0x868E]\n\nprint(device.longaddr)\n# bytes('411E841F004B1200')\n\nprint(device.reachable)\n# false - because it's a sleep device\n\nprint(device.router)\n# false - it's a sleepy device so not a router\n\nprint(device.manufacturer, device.model)\n# eWeLink TH013000_g5xawfcq')>\n\n# example with a plug\ndevice = zigbee[0xC1BC]\nprint(device.longaddr, device.reachable, device.router)\n# bytes('859F4E001044EF54') true false\nprint(device.manufacturer, device.model)\n# LUMI lumi.plug.maeu01\n```\n\n### Changing Zigbee values on-the-fly[~](#changing-zigbee-values-on-the-fly)\n\nWhenever a Zigbee message is received (typically values of attributes), the Tasmota Zigbee engines generates events at key points which allow custom Berry code to intercept and change messages on-the-fly.\n\nMessages are sent in the following order:\n\n`frame_received`\n\n: (low-level) the raw Zigbee message is passed as`bytes`\n\nand attributes are not yet decoded. The`bytes`\n\nbuffer can be modified and passed back to the Tasmota Zigbee engine.`attributes_raw`\n\n: (mid-level) Zigbee attributes are decoded but no transformation is applied yet. Attributes are only available in cluster/attribute format, names are not decoded and plug-ins are not yet applied.\n\nThis is the perfect moment to change non-standard attributes and map them to standard ones.`attributes_refined`\n\n: (high-level) Attributes are mapped to their names (when possible) and all transformations are applied. This is the last chance to change values.`attributes_final`\n\n: (high-level) consolidated`attributes_refined`\n\n. It is triggered just before final and consolidated attributes are sent to MQTT. Zigbee typically waits for 350ms before sending attributes, so it can consolidate multiple sensors (like temperature + humidity + pressure) in a single MQTT message\n\nThe format of methods are the following: `def <zigbee event>(event_type, frame, attr_list, idx)`\n\n| Argument | Description |\n|---|---|\n`event_type` | (string) can take values: `frame_received` , `attributes_raw` or `attributes_refined` |\n`frame` | (instance of `zcl_frame` ) low-level ZCL frameAlways present |\n`attr_list` | (instance of `XXX` ) list of attributes.This attribute is `nil` for `frame_received` , contains raw attributes in `attributes_raw` and refined attributes in `attributes_refined` |\n`idx` | (int 16 bits unsigned) contains the Zigbee short address |\n\nExample, if you want to dump all the traffic passed:\n\n``` python\nimport zigbee\nclass my_zb_handler\n  def frame_received(event_type, frame, attr_list, idx)\n    print(f\"shortaddr=0x{idx:04X} {event_type=} {frame=}\")\n  end\n  def attributes_raw(event_type, frame, attr_list, idx)\n    print(f\"shortaddr=0x{idx:04X} {event_type=} {attr_list=}\")\n  end\n  def attributes_refined(event_type, frame, attr_list, idx)\n    print(f\"shortaddr=0x{idx:04X} {event_type=} {attr_list=}\")\n  end\n  def attributes_final(event_type, frame, attr_list, idx)\n    print(f\"shortaddr=0x{idx:04X} {event_type=} {attr_list=}\")\n  end\n\nend\n\nvar my_handler = my_zb_handler()\nzigbee.add_handler(my_handler)\n\n# example of reading for a plug\n#\n# shortaddr=0xC1BC event_type=frame_received frame={'srcendpoint': 21, 'transactseq_set': 0, 'shortaddr': 49596, 'dstendpoint': 1, 'payload': bytes('5500003956CE8243'), 'shortaddr_hex': '0xC1BC', 'manuf': 0, 'payload_ptr': <ptr: 0x3ffccb5c>, 'need_response': 0, 'transactseq': 25, 'cmd': 1, 'direct': 0, 'cluster': 12, 'cluster_specific': 0, 'groupaddr': 0}\n# shortaddr=0xC1BC event_type=attributes_raw attr_list={\"000C/0055\":261.612,\"Endpoint\":21,\"LinkQuality\":21}\n# shortaddr=0xC1BC event_type=attributes_refined attr_list={\"ActivePower\":261.612,\"(ActivePower)\":\"0B04/050B\",\"Endpoint\":21,\"LinkQuality\":21}\n# shortaddr=0xC1BC event_type=attributes_final attr_list={\"ActivePower\":261.612,\"(ActivePower)\":\"0B04/050B\",\"Endpoint\":21,\"LinkQuality\":21}\n\n# to remove handler:\n# zigbee.remove_handler(my_handler)\n```\n\nThe `attr_list`\n\nis of class `zcl_attribute_list`\n\nand can be accessed with `zigbee.zcl_attribute_list`\n\n.\n\n| Methods | Parameters and details |\n|---|---|\n| size | `size() -> int` Number of attributes in the list |\n| remove | `remove(index:int) -> nil` Remove the item at `index` |\n| item [x] | `item(index:int) -> instance` or `[index:int] -> instance` Retrieve attribute at `index` , or `nil` if none.Note: contrary to native `list` it does not throw an exception if the index if off bounds. |\n| new_head | `new_head(attribute:instance of zigbee.zcl_attribute_list) -> self` Adds a new attribute at the beginning (head) of the list |\n| new_tail | `new_tail(attribute:instance of zigbee.zcl_attribute_list) -> self` Adds a new attribute at the end (tail) of the list |\n\nVariables of `zcl_attribute_list`\n\nfor the entire list and common to all attributes:\n\n| Attributes (read or write) | Details |\n|---|---|\n`groupaddr` | `uint16` group address if the message was multicast, or `nil` |\n`src_ep` | `uint8` source endpoint of the message |\n`lqi` | `uint8` lqi for the message received (link quality) |\n\nThe `zcl_attribute_list`\n\ncontains a list of `zcl_attribute`\n\ninstance.\n\n| Attributes (read or write) | Details |\n|---|---|\n`cluster` | `uint16` ZCL cluster number |\n`attr_id` | `uint16` ZCL attribute id |\n`cmd` | `uint8` ZCL command number |\n`direction` | `0 or 1` ZCL direction of the message (to or from the coordinator) |\n`cmd_general` | `0 or 1` ZCL flag indicating a general command vs a cluster specific command |\n`key` | `string or nil` attribute name (if any) or `nil` |\n`val` | `any` ZCL value of the attribute, can be `int/float/string/bytes...` |\n`key_suffix` | `uint8` key suffix in case a same attribute is repeatedLike `Power1` , `Power2` ... |\n`manuf` | `uint16` ZCL manufacturer specific code or 0 if noneThis is typically indicating a proprietary attribute |\n`attr_multiplier` | `int` multiplier to be applied or `1` |\n`attr_divider` | `int` divider to be applied or `1` |\n`attr_base` | `int` offset to be applied or `0` |\n`attr_type` | `uint8` ZCL type byte for the received attribute |\n\n`zcl_attribute_list`\n\nmethods:\n\n| Methods | Parameters and details |\n|---|---|\n| tomap | `tomap() -> map` Transforms main attributes as map (read-only): `cluster` , `attr_id` , `cmd` , `direction` , `key` , `val` |\n\n#### Changing attributes received[~](#changing-attributes-received)\n\nFor events `attributes_raw`\n\nand `attributes_refined`\n\n, you receive an instance of `attr_list`\n\nwhich represents all the attributes received. This list can be modified according to specificities of devices, hence giving full liberty on decoding exotic protocols or manufacturers.\n\nThe decoding is done in 2 steps:\n\n-\n`attributes_raw`\n\ncontains individual attributes with their native raw values. Names are not yet matched, nor scale factors applied. This is where you want to decode non-standard protocols Example:`{\"000C/0055\":261.612,\"Endpoint\":21,\"LinkQuality\":21}`\n\nrepresents raw value from a plug; the value was decoded as float. -\n`attributes_refined`\n\ncontains a similar list with additional decoding handled, any scale factor applied (like transforming integer temperature in 1/100 of Celsius to a`float`\n\n), and human readable names attached. Example:`{\"ActivePower\":261.612,\"(ActivePower)\":\"0B04/050B\",\"Endpoint\":21,\"LinkQuality\":21}`\n\nIn this example, the attribute is`0B04/050B`\n\nis rename as`ActivePower`\n\n, but the original`0B04/050B`\n\nattribute cluster/id is still readable. We can see that the generic`000C/0055 (AnalogValue)`\n\nfrom`lumi.plug.maeu01`\n\nis replaced with`0B04/050B (ActivePower)`\n\n.\n\n#### Changing Zigbee frame, `zcl_frame`\n\nclass[~](#changing-zigbee-frame-zcl_frame-class)\n\nThe `zcl_frame`\n\nrepresents a low-level ZCL (Zigbee Cluster Library) structure before any decoding or specific processing. You generally prefer to modify a frame later on when attributes or commands are decoded.\n\nclass `zcl_frame`\n\n:\n\n| Attributes (read or write) | Details |\n|---|---|\n`srcendpoint` | `uint8` source endpoint |\n`dtsendpoint` | `uint8` destination endpoint |\n`shortaddr` | `uint16` destination short address |\n`groupadddr` | `uint16` destination multicast group address (if shortaddr is 0xFFFE) |\n`cluster` | `uint16` cluster number |\n`cmd` | `uint8` ZCL command number |\n`cluster_specific` | `flag 0/1` is the command general or cluster specific |\n`manuf` | `uint16` manufacturer specific number (or 0x0000) |\n`needs_response` | `flag 0/1` does this frame needs a response |\n`payload` | `bytes()` bytes of the actual data (use with caution, can be read and changed) |\n| The following are rarely used flags | |\n`direct` | `flag 0/1` is the frame to be sent directly only (not routed) |\n`transactseq` | `uint8` transaction number (read only) |\n`transactseq_set` | `uint8` transaction number (write only - if you need to change it) |\n\nExample:\n\n```\nframe_received frame_received {'srcendpoint': 21, 'transactseq_set': 0, 'shortaddr': 49596, 'dstendpoint': 1, 'payload': bytes('550039D5787B43'), 'shortaddr_hex': '0xC1BC', 'manuf': 4447, 'payload_ptr': <ptr: 0x3ffd4d04>, 'need_response': 0, 'transactseq': 60, 'cmd': 10, 'direct': 0, 'cluster': 12, 'cluster_specific': 0, 'groupaddr': 0} nil 49596\n```\n\n## Compiling Berry[~](#compiling-berry)\n\nBerry is included if the following is defined in `user_config_override.h`\n\n:\n\n```\n#define USE_BERRY\n```\n\nOther options that can be changed:\n\n| Option | Description |\n|---|---|\n`#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 |\n`#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 |\n`#define USE_WEBCLIENT` | Enable the `webclient` module allowing to do HTTP requests.Enabled by default |\n`#define USE_WEBCLIENT_HTTPS` | Adds support for HTTPS to `webclient` . This feature adds ~45KB of Flash space for TLS support.Disabled by default |\n`#define USE_BERRY_WEBCLIENT_USERAGENT \"TasmotaClient\"` | Specifies the default `User-Agent` field sent by `webclient` . Can be changed on a per request basis. |\n`#define USE_BERRY_WEBCLIENT_TIMEOUT 5000` | Specifies the default timeout in millisecond for `webclient` . Can be changed on a per request basis. |\n\n[Berry Cookbook](../Berry-Cookbook/)[~](#berry-cookbook)\n\nFind complete examples and use scenarios of Berry in the [Berry Cookbook](../Berry-Cookbook/)", "url": "https://wpnews.pro/news/berry", "canonical_source": "https://tasmota.github.io/docs/Berry/", "published_at": "2026-06-12 21:28:08+00:00", "updated_at": "2026-06-12 21:44:45.152127+00:00", "lang": "en", "topics": ["ai-tools"], "entities": ["Berry", "Tasmota", "ESP32", "ESP8266", "Visual Studio Code", "Claude", "LVGL", "PSRAM"], "alternates": {"html": "https://wpnews.pro/news/berry", "markdown": "https://wpnews.pro/news/berry.md", "text": "https://wpnews.pro/news/berry.txt", "jsonld": "https://wpnews.pro/news/berry.jsonld"}}