Bartosz Nitka 7e1afd0fdf
[POC] Run berry-lang (#1544)
* setChannel(ch, v) in Berry

* try

* t2

* b

* fx

* update to berry with autogenerated files

* Add debug to find out whether the submodule is really checked out

* Revert "Add debug to find out whether the submodule is really checked out"

This reverts commit 40ec2f667716aa20180305ec7c3f1e61e88394c8.

* Add src/berry checkout to every build

* berry separate file p1

* #define ENABLE_OBK_BERRY 1 on Beken only

* Compile in obkSimulator

Compiles, but crashes in parser.
Seems like the Berry code has not been tested under MSVC2017.

* Checkout with submodules in obkSimulator workflow

* berry with ffs msvc fix

* berry autogen fiasco fix

* OpenBK7231T compiles from official berry, handle prebuild

Breaks obkSimulator

* Fix botched rebase on .gitmodules

* Build in msvc with a berry prebuild step (requires python)

* MSVC: also extend include directories for Release

* Fix BK723x build, extract common berry build rules into berry.mk

* Fix OpenBL602 build

This doesn't actually compile berry for OpenBL602, but makes compile
errors go away and marks where further work will be needed.

* working delayMs

Test Plan:
```
berry state = 1; var tick; def tick() state = 1 - state; channelSet(1, state); delayMs(1000, tick); end; tick();
```
can be stopped with `stopAllScripts`

* only use os_realloc on PLATFORM_BK7231T

* Move as much berry logic as possible out of cmd_script

* Guard more with ENABLE_OBK_BERRY

* upload script

* file operations & working import

* enough to implement "Advanced turn off after time with timer on UI and timer setting on UI and kept in flash"

* experiments

* clang-format -i src/berry/be_*.{h,c} src/cmnds/cmd_berry.c

* Remove redundant hfile != NULL

Calling with hfile == NULL would be bug in some other part of the code

* Don't checkout other submodules for simulator build

* remove stray debug & .user files

* Use be_newcomobj

* Build sim on linux

* build linux sim [2]

* threads & tests

* Leave enabled on Windows, disable on BEKEN

* SIM_RunWindow fix?

* allow "startScript test.be" as a shorthand for "berry import test" ??

* test?

* fix /

* test arg

* test2

* test

* test add

* fx

* test

* just checking if i can use import without module?

* test with module

* CMD_StopBerry fix?

* run obk command from berry? probably not good idea due to the stack size, will delay execution later?

* str arg

* test to see if i have to repeat import

* submit unfinished code

* concat tst

* more tests

* fix copy/paste mistake, add fib test

* fx

* channelSet

* fx

* try

* tester.fib(11)

* rename

---------

Co-authored-by: Tester23 <85486843+openshwprojects@users.noreply.github.com>
Co-authored-by: NonPIayerCharacter <18557343+NonPIayerCharacter@users.noreply.github.com>
2025-03-30 20:37:37 +02:00

4.2 KiB

Berry

What

Berry is a lightweight scripting language designed for small embedded systems. In OpenBeken, Berry provides a more powerful alternative to the built-in scripting system, with features like:

  • Object-oriented programming
  • First-class functions and closures
  • Proper exception handling
  • Module system for code organization
  • Rich standard library

Why

While OpenBeken's built-in scripting is great for simple automation tasks, Berry offers several advantages:

  • More structured programming with proper functions and classes
  • Better error handling with try/catch
  • More powerful data structures (maps, lists, etc.)
  • Ability to create reusable modules
  • Familiar syntax for those coming from languages like Python or JavaScript

How to Enable Berry

Berry is currently enabled by default on some platforms (like WINDOWS), but not on all platforms.

To enable Berry on other platforms:

  1. Edit src/obk_config.h
  2. Find your platform's section (e.g., #elif PLATFORM_W600)
  3. Add the following line within your platform's section:
    #define ENABLE_OBK_BERRY 1
    
  4. Recompile and flash your firmware

If you're unsure which platform you're using, check the existing entries in obk_config.h and look for the section that matches your device.

Basic Usage

To run Berry code, use the berry command followed by your code:

berry print("Hello from Berry!")

You can also create Berry script files with the .be extension and load them:

berry import mymodule

Caveats and Limitations

Note: Berry support in OpenBeken is currently experimental. The API and functionality may change in future releases without notice.

  • The Berry API is not finalized and may change between firmware versions
  • When using Berry alongside traditional OBK scripts, be aware that Berry uses thread IDs starting at 5000
  • Using thread IDs above 5000 in traditional OBK scripts may cause unexpected behavior or conflicts with Berry threads
  • Berry consumes more RAM than traditional scripts, so complex scripts may lead to memory issues on devices with limited RAM
  • Error handling is still being improved, so some errors may not provide helpful messages

Key Functions

  • setChannel(channel, value) - Set a channel value
  • getChannel(channel) - Get a channel value
  • scriptDelayMs(ms, function) - Run a function after a delay, returns a thread ID
  • addChangeHandler(event, relation, value, function) - Run a function when an event occurs, returns a thread ID
  • cancel(threadId) - Cancel a delayed function or event handler by its thread ID

Examples

Toggle a relay every second

def toggle_relay()
  current = getChannel(1)
  setChannel(1, 1 - current)
  scriptDelayMs(1000, toggle_relay)
end

toggle_relay()

React to events

# Store the handler ID so we can cancel it later if needed
handler_id = addChangeHandler("Channel3", "=", 1, def()
  print("Button pressed!")
  setChannel(1, 1)  # Turn on relay
end)

# Later, to cancel the handler:
# cancel(handler_id)

Cancelling timers and event handlers

# Create a repeating timer that toggles a relay every second
def setup_toggle()
  def toggle_relay()
    current = getChannel(1)
    setChannel(1, 1 - current)
    return scriptDelayMs(1000, toggle_relay)  # Return the new timer ID
  end
  
  return toggle_relay()  # Start the timer and return its ID
end

# Set up a button handler that cancels the timer when pressed
def setup_cancel_button()
  return addChangeHandler("Channel3", "=", 1, def()
    print("Cancelling the toggle timer")
    cancel(toggle_timer_id)
    # We could also cancel ourselves with: cancel(button_handler_id)
  end)
end

# Start everything
toggle_timer_id = setup_toggle()
button_handler_id = setup_cancel_button()

# To cancel everything later:
# cancel(toggle_timer_id)
# cancel(button_handler_id)

Create a module

Create a file named mymodule.be:

mymodule = module("mymodule")

mymodule.init = def(self)
  print("Module initialized")
  return self
end

mymodule.toggle_channel = def(self, channel)
  current = getChannel(channel)
  setChannel(channel, 1 - current)
end

return mymodule

Then use it:

import mymodule
mymodule.toggle_channel(1)