diff --git a/.github/workflows/workflow.yaml b/.github/workflows/workflow.yaml index 5368b61e2..08094e494 100644 --- a/.github/workflows/workflow.yaml +++ b/.github/workflows/workflow.yaml @@ -64,6 +64,10 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 + - name: Checkout berry submodule + run: | + git submodule update --init --recursive libraries/berry + - name: Setup MSBuild uses: microsoft/setup-msbuild@v2 diff --git a/.gitmodules b/.gitmodules index fcd4298f1..83ea002ee 100644 --- a/.gitmodules +++ b/.gitmodules @@ -53,3 +53,8 @@ path = sdk/OpenECR6600 url = https://github.com/NonPIayerCharacter/OpenECR6600.git branch = master +[submodule "libraries/berry"] + path = libraries/berry + url = https://github.com/berry-lang/berry.git + branch = master + diff --git a/Makefile b/Makefile index cc252963c..f417c1494 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ else endif update-submodules: submodules - git add sdk/OpenBK7231T sdk/OpenBK7231N sdk/OpenXR809 sdk/OpenBL602 sdk/OpenW800 sdk/OpenW600 sdk/OpenLN882H sdk/esp-idf sdk/OpenTR6260 sdk/beken_freertos_sdk + git add sdk/OpenBK7231T sdk/OpenBK7231N sdk/OpenXR809 sdk/OpenBL602 sdk/OpenW800 sdk/OpenW600 sdk/OpenLN882H sdk/esp-idf sdk/OpenTR6260 sdk/beken_freertos_sdk libraries/berry ifdef GITHUB_ACTIONS git config user.name github-actions git config user.email github-actions@github.com @@ -83,6 +83,7 @@ sdk/OpenLN882H/project/OpenBeken/app: prebuild_OpenBK7231N: git submodule update --init --recursive sdk/OpenBK7231N + git submodule update --init --recursive libraries/berry @if [ -e platforms/BK7231N/pre_build.sh ]; then \ echo "prebuild found for OpenBK7231N"; \ sh platforms/BK7231N/pre_build.sh; \ @@ -91,6 +92,7 @@ prebuild_OpenBK7231N: prebuild_OpenBK7231T: git submodule update --init --recursive sdk/OpenBK7231T + git submodule update --init --recursive libraries/berry @if [ -e platforms/BK7231T/pre_build.sh ]; then \ echo "prebuild found for OpenBK7231T"; \ sh platforms/BK7231T/pre_build.sh; \ @@ -99,6 +101,7 @@ prebuild_OpenBK7231T: prebuild_OpenBL602: git submodule update --init --recursive sdk/OpenBL602 + git submodule update --init --recursive libraries/berry @if [ -e platforms/BL602/pre_build.sh ]; then \ echo "prebuild found for OpenBL602"; \ sh platforms/BL602/pre_build.sh; \ @@ -107,6 +110,7 @@ prebuild_OpenBL602: prebuild_OpenLN882H: git submodule update --init --recursive sdk/OpenLN882H + git submodule update --init --recursive libraries/berry @if [ -e platforms/LN882H/pre_build.sh ]; then \ echo "prebuild found for OpenLN882H"; \ sh platforms/LN882H/pre_build.sh; \ @@ -115,6 +119,7 @@ prebuild_OpenLN882H: prebuild_OpenW600: git submodule update --init --recursive sdk/OpenW600 + git submodule update --init --recursive libraries/berry @if [ -e platforms/W600/pre_build.sh ]; then \ echo "prebuild found for OpenW600"; \ sh platforms/W600/pre_build.sh; \ @@ -123,6 +128,7 @@ prebuild_OpenW600: prebuild_OpenW800: git submodule update --init --recursive sdk/OpenW800 + git submodule update --init --recursive libraries/berry @if [ -e platforms/W800/pre_build.sh ]; then \ echo "prebuild found for OpenW800"; \ sh platforms/W800/pre_build.sh; \ @@ -131,6 +137,7 @@ prebuild_OpenW800: prebuild_OpenXR809: git submodule update --init --recursive sdk/OpenXR809 + git submodule update --init --recursive libraries/berry @if [ -e platforms/XR809/pre_build.sh ]; then \ echo "prebuild found for OpenXR809"; \ sh platforms/XR809/pre_build.sh; \ @@ -139,6 +146,7 @@ prebuild_OpenXR809: prebuild_ESPIDF: #git submodule update --init --recursive sdk/esp-idf + git submodule update --init --recursive libraries/berry @if [ -e platforms/ESP-IDF/pre_build.sh ]; then \ echo "prebuild found for ESP-IDF"; \ sh platforms/ESP-IDF/pre_build.sh; \ @@ -147,6 +155,7 @@ prebuild_ESPIDF: prebuild_OpenTR6260: git submodule update --init --recursive sdk/OpenTR6260 + git submodule update --init --recursive libraries/berry @if [ -e platforms/TR6260/pre_build.sh ]; then \ echo "prebuild found for OpenTR6260"; \ sh platforms/TR6260/pre_build.sh; \ @@ -155,6 +164,7 @@ prebuild_OpenTR6260: prebuild_OpenRTL87X0C: git submodule update --init --recursive sdk/OpenRTL87X0C + git submodule update --init --recursive libraries/berry @if [ -e platforms/RTL87X0C/pre_build.sh ]; then \ echo "prebuild found for OpenRTL87X0C"; \ sh platforms/RTL87X0C/pre_build.sh; \ @@ -163,6 +173,7 @@ prebuild_OpenRTL87X0C: prebuild_OpenRTL8710B: git submodule update --init --recursive sdk/OpenRTL8710A_B + git submodule update --init --recursive libraries/berry @if [ -e platforms/RTL8710B/pre_build.sh ]; then \ echo "prebuild found for OpenRTL8710B"; \ sh platforms/RTL8710B/pre_build.sh; \ @@ -175,6 +186,7 @@ prebuild_OpenRTL8710B: prebuild_OpenRTL8710A: git submodule update --init --recursive sdk/OpenRTL8710A_B + git submodule update --init --recursive libraries/berry @if [ -e platforms/RTL8710A/pre_build.sh ]; then \ echo "prebuild found for OpenRTL8710A"; \ sh platforms/RTL8710A/pre_build.sh; \ @@ -191,6 +203,7 @@ prebuild_OpenRTL8720D: prebuild_OpenBK7238: git submodule update --init --recursive sdk/beken_freertos_sdk + git submodule update --init --recursive libraries/berry @if [ -e platforms/BK723x/pre_build_7238.sh ]; then \ echo "prebuild found for OpenBK7238"; \ sh platforms/BK723x/pre_build_7238.sh; \ @@ -199,6 +212,7 @@ prebuild_OpenBK7238: prebuild_OpenBK7231N_ALT: git submodule update --init --recursive sdk/beken_freertos_sdk + git submodule update --init --recursive libraries/berry @if [ -e platforms/BK723x/pre_build_7231n.sh ]; then \ echo "prebuild found for OpenBK7231N"; \ sh platforms/BK723x/pre_build_7231n.sh; \ diff --git a/bouffalo.mk b/bouffalo.mk index 61b9356d1..984e54e2f 100644 --- a/bouffalo.mk +++ b/bouffalo.mk @@ -1,7 +1,7 @@ # Component Makefile # ## These include paths would be exported to project level -COMPONENT_ADD_INCLUDEDIRS += src/ src/httpserver/ src/cmnds/ src/logging/ src/hal/bl602/ src/mqtt/ src/cJSON src/base64 src/driver src/devicegroups src/bitmessage src/littlefs +COMPONENT_ADD_INCLUDEDIRS += src/ src/httpserver/ src/cmnds/ src/logging/ src/hal/bl602/ src/mqtt/ src/cJSON src/base64 src/driver src/devicegroups src/bitmessage src/littlefs libraries/berry/src include/ ## not be exported to project level COMPONENT_PRIV_INCLUDEDIRS := diff --git a/components.mk b/components.mk index 552b58a77..9f4538ea1 100644 --- a/components.mk +++ b/components.mk @@ -1,3 +1,19 @@ +OBK_DIR = $(TOP_DIR)/apps/$(APP_BIN_NAME)/ + +BERRY_SRCPATH = $(OBK_DIR)/libraries/berry/src/ + +# different frameworks put object files in different places, +# berry needs to add a rule to autogenerate some files before the object files +# are built, so it needs the translation function from a C source to an object +# file +define obj_from_c + $(patsubst %.c, %.o, $(1)) +endef + +include $(OBK_DIR)/libraries/berry.mk + +SRC_C += $(BERRY_SRC_C) + ifeq ($(TARGET_PLATFORM),bk7231n) CFG_USE_MQTT_TLS ?= 0 @@ -66,4 +82,4 @@ SRC_C += ${MBEDTLS_DIR}/library/camellia.c SRC_C += ${MBEDTLS_DIR}/library/ssl_cli.c endif #ifeq ($(CFG_USE_MQTT_TLS),1) -endif #ifeq ($(TARGET_PLATFORM),bk7231n) \ No newline at end of file +endif #ifeq ($(TARGET_PLATFORM),bk7231n) diff --git a/custom.mk b/custom.mk index 899f29f50..eb1d8dadd 100644 --- a/custom.mk +++ b/custom.mk @@ -9,18 +9,36 @@ EXCLUDED_FILES ?= src/httpserver/http_tcp_server.c src/ota/ota.c src/cmnds/cmd_t SRCS := $(filter-out $(EXCLUDED_FILES), $(wildcard $(shell find $(SRC_DIRS) -not \( -path "src/hal/bl602" -prune \) -not \( -path "src/hal/xr809" -prune \) -not \( -path "src/hal/w800" -prune \) -not \( -path "src/hal/bk7231" -prune \) -name *.c | sort -k 1nr | cut -f2-))) +INC_DIRS := include $(shell find $(SRC_DIRS) -type d) +INC_DIRS := $(filter-out src/hal/bl602 src/hal/xr809 src/hal/w800 src/hal/bk7231 src/memory, $(wildcard $(INC_DIRS))) + +INCLUDES := $(addprefix -I,$(INC_DIRS)) + +default: $(BUILD_DIR)/$(TARGET_EXEC) + +BERRY_SRCPATH = libraries/berry/src/ +# different frameworks put object files in different places, +# berry needs to add a rule to autogenerate some files before the object files +# are built, so it needs the translation function from a C source to an object +# file +define obj_from_c + $(patsubst %.c, %.o, $(1)) +endef + +include libraries/berry.mk + +SRCS += $(BERRY_SRC_C) + #OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) DEPS := $(OBJS:.o=.d) -INC_DIRS := $(shell find $(SRC_DIRS) -type d) -INC_DIRS := $(filter-out src/hal/bl602 src/hal/xr809 src/hal/w800 src/hal/bk7231 src/memory, $(wildcard $(INC_DIRS))) -INC_FLAGS := $(addprefix -I,$(INC_DIRS)) LDLIBS := -lpthread -lm -lnsl -CPPFLAGS ?= $(INC_FLAGS) -MMD -MP -std=gnu99 -DWINDOWS -DLINUX +CPPFLAGS ?= $(INCLUDES) -MMD -MP -std=gnu99 -DWINDOWS -DLINUX + CFLAGS ?= -std=gnu99 -W -Wall -Wextra -g @@ -41,4 +59,6 @@ clean: -include $(DEPS) -MKDIR_P ?= mkdir -p \ No newline at end of file + +MKDIR_P ?= mkdir -p + diff --git a/docs/berry.md b/docs/berry.md new file mode 100644 index 000000000..877e14598 --- /dev/null +++ b/docs/berry.md @@ -0,0 +1,155 @@ +# 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: + ```c + #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 + +```berry +def toggle_relay() + current = getChannel(1) + setChannel(1, 1 - current) + scriptDelayMs(1000, toggle_relay) +end + +toggle_relay() +``` + +### React to events + +```berry +# 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 + +```berry +# 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`: + +```berry +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: + +```berry +import mymodule +mymodule.toggle_channel(1) +``` diff --git a/include/berry_conf.h b/include/berry_conf.h new file mode 100644 index 000000000..9950fccbe --- /dev/null +++ b/include/berry_conf.h @@ -0,0 +1,247 @@ +/******************************************************************** +** Copyright (c) 2018-2020 Guan Wenliang +** This file is part of the Berry default interpreter. +** skiars@qq.com, https://github.com/Skiars/berry +** See Copyright Notice in the LICENSE file or at +** https://github.com/Skiars/berry/blob/master/LICENSE +********************************************************************/ +#ifndef BERRY_CONF_H +#define BERRY_CONF_H + +#include + +/* Macro: BE_DEBUG + * Berry interpreter debug switch. + * Default: 0 + **/ +#ifndef BE_DEBUG +#define BE_DEBUG 1 +#endif + +/* Macro: BE_LONGLONG_INT + * Select integer length. + * If the value is 0, use an integer of type int, use a long + * integer type when the value is 1, and use a long long integer + * type when the value is 2. + * Default: 2 + */ +#define BE_INTGER_TYPE 1 + +/* Macro: BE_USE_SINGLE_FLOAT + * Select floating point precision. + * Use double-precision floating-point numbers when the value + * is 0 (default), otherwise use single-precision floating-point + * numbers. + * Default: 0 + **/ +#define BE_USE_SINGLE_FLOAT 1 + +/* Macro: BE_BYTES_MAX_SIZE + * Maximum size in bytes of a `bytes()` object. + * Putting too much pressure on the memory allocator can do + * harm, so we limit the maximum size. + * Default: 32kb + **/ +#define BE_BYTES_MAX_SIZE (32 * 1024) /* 32 kb default value */ + +/* Macro: BE_USE_PRECOMPILED_OBJECT + * Use precompiled objects to avoid creating these objects at + * runtime. Enable this macro can greatly optimize RAM usage. + * Default: 1 + **/ +#define BE_USE_PRECOMPILED_OBJECT 1 + +/* Macro: BE_DEBUG_SOURCE_FILE + * Indicate if each function remembers its source file name + * 0: do not keep the file name (saves 4 bytes per function) + * 1: keep the source file name + * Default: 1 + **/ +#define BE_DEBUG_SOURCE_FILE 1 + +/* Macro: BE_DEBUG_RUNTIME_INFO + * Set runtime error debugging information. + * 0: unable to output source file and line number at runtime. + * 1: output source file and line number information at runtime. + * 2: the information use uint16_t type (save space). + * Default: 1 + **/ +#define BE_DEBUG_RUNTIME_INFO 1 + +/* Macro: BE_DEBUG_VAR_INFO + * Set variable debugging tracking information. + * 0: disable variable debugging tracking information at runtime. + * 1: enable variable debugging tracking information at runtime. + * Default: 1 + **/ +#define BE_DEBUG_VAR_INFO 1 + +/* Macro: BE_USE_PERF_COUNTERS + * Use the obshook function to report low-level actions. + * Default: 1 + **/ +#define BE_USE_PERF_COUNTERS 1 + +/* Macro: BE_VM_OBSERVABILITY_SAMPLING + * If BE_USE_PERF_COUNTERS == 1 + * then the observability hook is called regularly in the VM loop + * allowing to stop infinite loops or too-long running code. + * The value is a power of 2. + * Default: 20 - which translates to 2^20 or ~1 million instructions + **/ +#define BE_VM_OBSERVABILITY_SAMPLING 20 + +/* Macro: BE_STACK_TOTAL_MAX + * Set the maximum total stack size. + * Default: 20000 + **/ +#define BE_STACK_TOTAL_MAX 8192 + +/* Macro: BE_STACK_FREE_MIN + * Set the minimum free count of the stack. The stack idles will + * be checked when a function is called, and the stack will be + * expanded if the number of free is less than BE_STACK_FREE_MIN. + * Default: 10 + **/ +#define BE_STACK_FREE_MIN 10 + +/* Macro: BE_STACK_START + * Set the starting size of the stack at VM creation. + * Default: 50 + **/ +#define BE_STACK_START 50 + +/* Macro: BE_CONST_SEARCH_SIZE + * Constants in function are limited to 255. However the compiler + * will look for a maximum of pre-existing constants to avoid + * performance degradation. This may cause the number of constants + * to be higher than required. + * Increase is you need to solidify functions. + * Default: 50 + **/ +#define BE_CONST_SEARCH_SIZE 50 + +/* Macro: BE_STACK_FREE_MIN + * The short string will hold the hash value when the value is + * true. It may be faster but requires more RAM. + * Default: 0 + **/ +#define BE_USE_STR_HASH_CACHE 0 + +/* Macro: BE_USE_FILE_SYSTEM + * The file system interface will be used when this macro is true + * or when using the OS module. Otherwise the file system interface + * will not be used. + * Default: 0 + **/ +#define BE_USE_FILE_SYSTEM 1 + +/* Macro: BE_USE_SCRIPT_COMPILER + * Enable compiler when BE_USE_SCRIPT_COMPILER is not 0, otherwise + * disable the compiler. + * Default: 1 + **/ +#define BE_USE_SCRIPT_COMPILER 1 + +/* Macro: BE_USE_BYTECODE_SAVER + * Enable save bytecode to file when BE_USE_BYTECODE_SAVER is not 0, + * otherwise disable the feature. + * Default: 1 + **/ +#define BE_USE_BYTECODE_SAVER 1 + +/* Macro: BE_USE_BYTECODE_LOADER + * Enable load bytecode from file when BE_USE_BYTECODE_LOADER is not 0, + * otherwise disable the feature. + * Default: 1 + **/ +#define BE_USE_BYTECODE_LOADER 1 + +/* Macro: BE_USE_SHARED_LIB + * Enable shared library when BE_USE_SHARED_LIB is not 0, + * otherwise disable the feature. + * Default: 1 + **/ +#define BE_USE_SHARED_LIB 0 + +/* Macro: BE_USE_OVERLOAD_HASH + * Allows instances to overload hash methods for use in the + * built-in Map class. Disable this feature to crop the code + * size. + * Default: 1 + **/ +#define BE_USE_OVERLOAD_HASH 1 + +/* Macro: BE_USE_DEBUG_HOOK + * Berry debug hook switch. + * Default: 0 + **/ +#define BE_USE_DEBUG_HOOK 0 + +/* Macro: BE_USE_DEBUG_GC + * Enable GC debug mode. This causes an actual gc after each + * allocation. It's much slower and should not be used + * in production code. + * Default: 0 + **/ +#define BE_USE_DEBUG_GC 0 + +/* Macro: BE_USE_DEBUG_STACK + * Enable Stack Resize debug mode. At each function call + * the stack is reallocated at a different memory location + * and the previous location is cleared with toxic data. + * Default: 0 + **/ +#define BE_USE_DEBUG_STACK 0 + +/* Macro: BE_USE_MEM_ALIGNED + * Some embedded processors have special memory areas + * with read/write constraints of being aligned to 32 bits boundaries. + * This options tries to move such memory areas to this region. + * Default: 0 + **/ +#define BE_USE_MEM_ALIGNED 0 + +/* Macro: BE_USE_XXX_MODULE + * These macros control whether the related module is compiled. + * When they are true, they will enable related modules. At this + * point you can use the import statement to import the module. + * They will not compile related modules when they are false. + **/ +#define BE_USE_STRING_MODULE 1 +#define BE_USE_JSON_MODULE 1 +#define BE_USE_MATH_MODULE 1 +#define BE_USE_TIME_MODULE 0 +#define BE_USE_OS_MODULE 0 +#define BE_USE_GLOBAL_MODULE 1 +#define BE_USE_SYS_MODULE 1 +#define BE_USE_DEBUG_MODULE 1 +#define BE_USE_GC_MODULE 0 +#define BE_USE_SOLIDIFY_MODULE 0 +#define BE_USE_INTROSPECT_MODULE 0 +#define BE_USE_STRICT_MODULE 0 + +/* Macro: BE_EXPLICIT_XXX + * If these macros are defined, the corresponding function will + * use the version defined by these macros. These macro definitions + * are not required. + * The default is to use the functions in the standard library. + **/ +#define BE_EXPLICIT_ABORT abort +#define BE_EXPLICIT_EXIT exit +#define BE_EXPLICIT_MALLOC malloc +#define BE_EXPLICIT_FREE free +// normal realloc appears broken on OpenBK7231T: #1563, #298 +#ifdef PLATFORM_BK7231T +#define BE_EXPLICIT_REALLOC os_realloc +#else +#define BE_EXPLICIT_REALLOC realloc +#endif + +/* Macro: be_assert + * Berry debug assertion. Only enabled when BE_DEBUG is active. + * Default: use the assert() function of the standard library. + **/ +#define be_assert(expr) assert(expr) + +#endif diff --git a/libraries/berry b/libraries/berry new file mode 160000 index 000000000..b9ce913d9 --- /dev/null +++ b/libraries/berry @@ -0,0 +1 @@ +Subproject commit b9ce913d9d43bb296d9604bc0cb4f2f68a6b703f diff --git a/libraries/berry.mk b/libraries/berry.mk new file mode 100644 index 000000000..4cd5a0c33 --- /dev/null +++ b/libraries/berry.mk @@ -0,0 +1,67 @@ +# meant to be included, depends on BERRY_SRCPATH and obj_from_c function + +INCLUDES += -I$(BERRY_SRCPATH) + +BERRY_SRC_C += $(BERRY_SRCPATH)/be_api.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_baselib.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_bytecode.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_byteslib.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_class.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_code.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_debug.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_debuglib.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_exec.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_filelib.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_func.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_gc.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_gclib.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_globallib.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_introspectlib.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_jsonlib.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_lexer.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_libs.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_list.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_listlib.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_map.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_maplib.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_mathlib.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_mem.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_module.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_object.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_oslib.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_parser.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_rangelib.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_repl.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_solidifylib.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_strictlib.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_string.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_strlib.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_syslib.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_timelib.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_undefinedlib.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_var.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_vector.c +BERRY_SRC_C += $(BERRY_SRCPATH)/be_vm.c + +COC = $(BERRY_SRCPATH)/../tools/coc/coc +BERRY_GENERATE = $(BERRY_SRCPATH)/../generate +BERRY_CONFIG = $(BERRY_SRCPATH)/../../../include/berry_conf.h + +ifneq ($(V), 1) + Q=@ + MSG=@echo +else + MSG=@true +endif + + +BERRY_OBJS = $(call obj_from_c,$(BERRY_SRC_C)) + +$(BERRY_OBJS): berry_const_tab + +berry_const_tab: $(BERRY_GENERATE) $(BERRY_SRC_C) $(BERRY_CONFIG) + $(MSG) [Prebuild] generate resources + $(Q) $(COC) -o $(BERRY_GENERATE) $(BERRY_SRCPATH) -c $(BERRY_CONFIG) + +$(BERRY_GENERATE): + $(Q) mkdir $(BERRY_GENERATE) diff --git a/openBeken_win32_mvsc2017.vcxproj b/openBeken_win32_mvsc2017.vcxproj index 16b49bc08..fd84829ca 100644 --- a/openBeken_win32_mvsc2017.vcxproj +++ b/openBeken_win32_mvsc2017.vcxproj @@ -52,7 +52,7 @@ Disabled - .\libs_for_simulator\SDL2-2.24.2\include;.\src\win32\stubs\;.\libs_for_simulator\DevIL-Windows-SDK-1.8.0\include;.\libs_for_simulator\freeglut-MSVC-3.0.0-2.mp\include;.\libs_for_simulator\nativefiledialog\src\include;%(AdditionalIncludeDirectories) + .\libs_for_simulator\SDL2-2.24.2\include;.\src\win32\stubs\;.\libs_for_simulator\DevIL-Windows-SDK-1.8.0\include;.\libs_for_simulator\freeglut-MSVC-3.0.0-2.mp\include;.\libs_for_simulator\nativefiledialog\src\include;.\include;.\libraries\berry\src;%(AdditionalIncludeDirectories) WINDOWS;_CRT_NONSTDC_NO_WARNINGS;WINSOCK_DEPRECATED_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions);_USE_32BIT_TIME_T;DEBUG true EnableFastChecks @@ -65,11 +65,17 @@ MachineX86 .\libs_for_simulator\SDL2-2.24.2\lib\x86;.\libs_for_simulator\DevIL-Windows-SDK-1.8.0\lib\x86\Release;.\libs_for_simulator\freeglut-MSVC-3.0.0-2.mp\lib;.\libs_for_simulator\nativefiledialog\build\lib\Debug\x86 + + cd libraries\berry & mkdir generate & python tools/coc/coc -o generate src -c ..\..\include\berry_conf.h + + + prebuild berry + Disabled - .\libs_for_simulator\SDL2-2.24.2\include;.\src\win32\stubs\;.\libs_for_simulator\DevIL-Windows-SDK-1.8.0\include;.\libs_for_simulator\freeglut-MSVC-3.0.0-2.mp\include;.\libs_for_simulator\nativefiledialog\src\include;%(AdditionalIncludeDirectories) + .\libs_for_simulator\SDL2-2.24.2\include;.\src\win32\stubs\;.\libs_for_simulator\DevIL-Windows-SDK-1.8.0\include;.\libs_for_simulator\freeglut-MSVC-3.0.0-2.mp\include;.\libs_for_simulator\nativefiledialog\src\include;.\include;.\libraries\berry\src;%(AdditionalIncludeDirectories) WINDOWS;_CRT_NONSTDC_NO_WARNINGS;WINSOCK_DEPRECATED_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions);_USE_32BIT_TIME_T;WIN32_LEAN_AND_MEAN;WIN32;_CRT_NON_CONFORMING_SWPRINTFS;_CRT_SECURE_NO_WARNINGS;_CRT_SECURE_NO_DEPRECATE;_CONSOLE true Default @@ -82,6 +88,12 @@ MachineX86 .\libs_for_simulator\SDL2-2.24.2\lib\x86;.\libs_for_simulator\DevIL-Windows-SDK-1.8.0\lib\x86\Release;.\libs_for_simulator\freeglut-MSVC-3.0.0-2.mp\lib;.\libs_for_simulator\nativefiledialog\build\lib\Release\x86 + + cd libraries\berry & mkdir generate & python tools/coc/coc -o generate src -c ..\..\include\berry_conf.h + + + prebuild berry + @@ -116,10 +128,55 @@ true true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -702,6 +759,7 @@ + @@ -808,6 +866,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1176,4 +1287,4 @@ - \ No newline at end of file + diff --git a/openBeken_win32_mvsc2017.vcxproj.filters b/openBeken_win32_mvsc2017.vcxproj.filters index 5d018c60b..735c14e22 100644 --- a/openBeken_win32_mvsc2017.vcxproj.filters +++ b/openBeken_win32_mvsc2017.vcxproj.filters @@ -233,6 +233,7 @@ + @@ -347,7 +348,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -503,6 +549,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -538,4 +637,4 @@ - \ No newline at end of file + diff --git a/platforms/BK723x/OpenBeken.mk b/platforms/BK723x/OpenBeken.mk index a9e9291b0..f827c8ece 100644 --- a/platforms/BK723x/OpenBeken.mk +++ b/platforms/BK723x/OpenBeken.mk @@ -27,6 +27,7 @@ APP_C += $(OBK_DIR)/base64/base64.c APP_C += $(OBK_DIR)/bitmessage/bitmessage_read.c APP_C += $(OBK_DIR)/bitmessage/bitmessage_write.c APP_C += $(OBK_DIR)/cJSON/cJSON.c +APP_C += $(OBK_DIR)/cmnds/cmd_berry.c APP_C += $(OBK_DIR)/cmnds/cmd_channels.c APP_C += $(OBK_DIR)/cmnds/cmd_eventHandlers.c APP_C += $(OBK_DIR)/cmnds/cmd_if.c @@ -154,4 +155,23 @@ APP_C += $(OBK_DIR)/driver/drv_ir2.c SRC_C += ./fixes/blank.c + APP_CXX += $(OBK_DIR)/driver/drv_ir.cpp + +BERRY_SRCPATH = $(OBK_DIR)/../libraries/berry/src/ + +# different frameworks put object files in different places, +# berry needs to add a rule to autogenerate some files before the object files +# are built, so it needs the translation function from a C source to an object +# file +define obj_from_c + $(patsubst %.c, $(OBJ_DIR)/%.app.o, $(1)) +endef + +include $(OBK_DIR)/../libraries/berry.mk + +APP_C += $(BERRY_SRC_C) +APP_C += $(OBK_DIR)/berry/be_bindings.c +APP_C += $(OBK_DIR)/berry/be_modtab.c +APP_C += $(OBK_DIR)/berry/be_port.c +APP_C += $(OBK_DIR)/berry/be_run.c diff --git a/src/berry/be_bindings.c b/src/berry/be_bindings.c new file mode 100644 index 000000000..49d413ff0 --- /dev/null +++ b/src/berry/be_bindings.c @@ -0,0 +1,197 @@ +#include "be_bindings.h" +#include "../driver/drv_local.h" +#include "../hal/hal_generic.h" +#include "../new_cfg.h" +#include "../new_pins.h" +#include "be_object.h" + +int be_SetStartValue(bvm *vm) { + int top = be_top(vm); + + if (top == 2 && be_isint(vm, 1) && be_isint(vm, 2)) { + int ch = be_toint(vm, 1); + int v = be_toint(vm, 2); + CFG_SetChannelStartupValue(ch, v); + } + be_return_nil(vm); +} + +int be_runCmd(bvm *vm) { + int top = be_top(vm); + int ret; + if (top == 1) { + const char *cmd = be_tostring(vm, 1); + ret = CMD_ExecuteCommand(cmd, 0); + } + else { + ret = 0; + } + be_return_nil(vm); +} +int be_ChannelSet(bvm *vm) { + int top = be_top(vm); + + if (top == 2 && be_isint(vm, 1) && be_isint(vm, 2)) { + int ch = be_toint(vm, 1); + int v = be_toint(vm, 2); + CHANNEL_Set(ch, v, 0); + } + be_return_nil(vm); /* return calculation result */ +} + +int be_ChannelAdd(bvm *vm) { + int top = be_top(vm); + + if (top == 2 && be_isint(vm, 1) && be_isint(vm, 2)) { + int ch = be_toint(vm, 1); + int v = be_toint(vm, 2); + CHANNEL_Add(ch, v); + } + be_return_nil(vm); /* return calculation result */ +} +int be_ChannelGet(bvm *vm) { + int top = be_top(vm); + + if (top == 1 && be_isint(vm, 1)) { + int ch = be_toint(vm, 1); + int v = CHANNEL_Get(ch); + be_pushint(vm, v); + be_return(vm); + } else { + be_return_nil(vm); + } +} + +int be_SetChannelType(bvm *vm) { + int top = be_top(vm); + + if (top == 2 && be_isint(vm, 1) && be_isstring(vm, 2)) { + int ch = be_toint(vm, 1); + const char *typeStr = be_tostring(vm, 2); + int type = CHANNEL_ParseChannelType(typeStr); + CHANNEL_SetType(ch, type); + } + be_return_nil(vm); +} + +int be_SetChannelLabel(bvm *vm) { + int top = be_top(vm); + + if (top >= 2 && top <= 3 && be_isint(vm, 1) && be_isstring(vm, 2)) { + int ch = be_toint(vm, 1); + const char *label = be_tostring(vm, 2); + bool bHideTogglePrefix = true; + if (top == 3 && be_isbool(vm, 3)) { + bHideTogglePrefix = be_tobool(vm, 3); + } + CHANNEL_SetLabel(ch, label, bHideTogglePrefix); + } + be_return_nil(vm); +} + +int be_rtosDelayMs(bvm *vm) { + int top = be_top(vm); + if (top >= 1 && be_isint(vm, 1)) { + int delay = be_toint(vm, 1); + + rtos_delay_milliseconds(delay); + } + be_return_nil(vm); +} + +int be_delayUs(bvm *vm) { + int top = be_top(vm); + if (top >= 1 && be_isint(vm, 1)) { + int delay = be_toint(vm, 1); + + HAL_Delay_us(delay); + } + be_return_nil(vm); +} + +int be_initI2c(bvm *vm) { + int top = be_top(vm); + if (top >= 2 && be_isint(vm, 1) && be_isint(vm, 2)) { + softI2C_t *i2c = malloc(sizeof(softI2C_t)); + memset(i2c, 0, sizeof(softI2C_t)); + i2c->pin_clk = be_toint(vm, 1); + i2c->pin_data = be_toint(vm, 2); + be_pushcomptr(vm, i2c); + be_newcomobj(vm, i2c, &be_commonobj_destroy_generic); + be_return(vm); + } + be_return_nil(vm); +} + +int be_startI2c(bvm *vm) { + int top = be_top(vm); + if (top >= 2 && be_iscomptr(vm, 1) && be_isint(vm, 2)) { + softI2C_t *i2c = be_tocomptr(vm, 1); + int addr = be_toint(vm, 2); + Soft_I2C_Start(i2c, addr); + } + be_return_nil(vm); +} + +int be_stopI2c(bvm *vm) { + int top = be_top(vm); + if (top >= 1 && be_iscomptr(vm, 1)) { + softI2C_t *i2c = be_tocomptr(vm, 1); + Soft_I2C_Stop(i2c); + } + be_return_nil(vm); +} + +int be_writeByteI2c(bvm *vm) { + int top = be_top(vm); + if (top >= 2 && be_iscomptr(vm, 1) && be_isint(vm, 2)) { + softI2C_t *i2c = be_tocomptr(vm, 1); + int data = be_toint(vm, 2); + Soft_I2C_WriteByte(i2c, data); + } + be_return_nil(vm); +} + +int be_readByteI2c(bvm *vm) { + int top = be_top(vm); + if (top >= 2 && be_iscomptr(vm, 1) && be_isbool(vm, 2)) { + softI2C_t *i2c = be_tocomptr(vm, 1); + int nack = be_tobool(vm, 2); + uint8_t data = Soft_I2C_ReadByte(i2c, nack); + be_pushint(vm, data); + be_return(vm); + } + be_return_nil(vm); +} + +int be_readBytesI2c(bvm *vm) { + int top = be_top(vm); + if (top >= 2 && be_iscomptr(vm, 1) && be_isint(vm, 2)) { + softI2C_t *i2c = be_tocomptr(vm, 1); + int size = be_toint(vm, 2); + uint8_t *data = malloc(size * sizeof(uint8_t)); + Soft_I2C_ReadBytes(i2c, data, size); + be_newobject(vm, "list"); + for (int i = 0; i < size; i++) { + be_pushint(vm, data[i]); + be_data_push(vm, -2); + be_pop(vm, 1); + } + be_pop(vm, 1); + free(data); + be_return(vm); + } + be_return_nil(vm); +} + +int be_CancelThread(bvm *vm) { + int top = be_top(vm); + if (top == 1 && be_isint(vm, 1)) { + int thread_id = be_toint(vm, 1); + SVM_StopScripts(thread_id, 0); + be_pushbool(vm, true); + be_return(vm); + } + be_pushbool(vm, false); + be_return(vm); +} diff --git a/src/berry/be_bindings.h b/src/berry/be_bindings.h new file mode 100644 index 000000000..ad9406eb5 --- /dev/null +++ b/src/berry/be_bindings.h @@ -0,0 +1,22 @@ +#pragma once +#include "../new_common.h" +#include "berry.h" + +int be_SetStartValue(bvm *vm); +int be_ChannelSet(bvm *vm); +int be_ChannelGet(bvm *vm); +int be_ChannelAdd(bvm *vm); +int be_SetChannelType(bvm *vm); +int be_SetChannelLabel(bvm *vm); +int be_runCmd(bvm *vm); + +int be_rtosDelayMs(bvm *vm); +int be_delayUs(bvm *vm); +int be_initI2c(bvm *vm); +int be_deinitI2c(bvm *vm); +int be_startI2c(bvm *vm); +int be_stopI2c(bvm *vm); +int be_writeByteI2c(bvm *vm); +int be_readByteI2c(bvm *vm); +int be_readBytesI2c(bvm *vm); +int be_CancelThread(bvm *vm); diff --git a/src/berry/be_modtab.c b/src/berry/be_modtab.c new file mode 100644 index 000000000..5ecd5dd1e --- /dev/null +++ b/src/berry/be_modtab.c @@ -0,0 +1,85 @@ +/******************************************************************** +** Copyright (c) 2018-2020 Guan Wenliang +** This file is part of the Berry default interpreter. +** skiars@qq.com, https://github.com/Skiars/berry +** See Copyright Notice in the LICENSE file or at +** https://github.com/Skiars/berry/blob/master/LICENSE +********************************************************************/ +#include "berry.h" + +/* this file contains the declaration of the module table. */ + +/* default modules declare */ +be_extern_native_module(string); +be_extern_native_module(json); +be_extern_native_module(math); +be_extern_native_module(time); +be_extern_native_module(os); +be_extern_native_module(global); +be_extern_native_module(sys); +be_extern_native_module(debug); +be_extern_native_module(gc); +be_extern_native_module(solidify); +be_extern_native_module(introspect); +be_extern_native_module(strict); +be_extern_native_module(undefined); + +/* user-defined modules declare start */ + +/* user-defined modules declare end */ + +/* module list declaration */ +BERRY_LOCAL const bntvmodule_t *const be_module_table[] = { +/* default modules register */ +#if BE_USE_STRING_MODULE + &be_native_module(string), +#endif +#if BE_USE_JSON_MODULE + &be_native_module(json), +#endif +#if BE_USE_MATH_MODULE + &be_native_module(math), +#endif +#if BE_USE_TIME_MODULE + &be_native_module(time), +#endif +#if BE_USE_OS_MODULE + &be_native_module(os), +#endif +#if BE_USE_GLOBAL_MODULE + &be_native_module(global), +#endif +#if BE_USE_SYS_MODULE + &be_native_module(sys), +#endif +#if BE_USE_DEBUG_MODULE + &be_native_module(debug), +#endif +#if BE_USE_GC_MODULE + &be_native_module(gc), +#endif +#if BE_USE_SOLIDIFY_MODULE + &be_native_module(solidify), +#endif +#if BE_USE_INTROSPECT_MODULE + &be_native_module(introspect), +#endif +#if BE_USE_STRICT_MODULE + &be_native_module(strict), +#endif + &be_native_module(undefined), + /* user-defined modules register start */ + + /* user-defined modules register end */ + NULL /* do not remove */ +}; + +/* user-defined classes declare start */ +/* be_extern_native_class(my_class); */ +/* user-defined classes declare end */ + +BERRY_LOCAL bclass_array be_class_table = { + /* first list are direct classes */ + /* &be_native_class(my_class), */ + NULL, /* do not remove */ +}; diff --git a/src/berry/be_port.c b/src/berry/be_port.c new file mode 100644 index 000000000..2961fd5fb --- /dev/null +++ b/src/berry/be_port.c @@ -0,0 +1,178 @@ +/******************************************************************** +** Copyright (c) 2018-2020 Guan Wenliang +** This file is part of the Berry default interpreter. +** skiars@qq.com, https://github.com/Skiars/berry +** See Copyright Notice in the LICENSE file or at +** https://github.com/Skiars/berry/blob/master/LICENSE +********************************************************************/ + +#include "be_mem.h" +#include "be_sys.h" +#include "berry.h" +#include +#include + +#include "../littlefs/our_lfs.h" +#include "../logging/logging.h" + +/* this file contains configuration for the file system. */ + +/* standard input and output */ + +BERRY_API void be_writebuffer(const char *buffer, size_t length) { + ADDLOG_INFO(LOG_FEATURE_BERRY, "%.*s", length, buffer); +} + +BERRY_API char *be_readstring(char *buffer, size_t size) { + if ((size > 0) && (buffer != NULL)) { + *buffer = 0; + } + return buffer; +} + +#if ENABLE_LITTLEFS + +// Mapping of fopen modes to lfs_file_open flags +static int mode_to_flags(const char *mode) { + // accepted regex: ^(r|w|a)b?+?.*$ + bool has_plus = false; + if (*mode) { + // skip r, w, b + const char *extra = mode + 1; + // skip allowed b + if (*extra == 'b') + extra++; + has_plus = *extra == '+'; + } + + switch (mode[0]) { + case 'r': + return has_plus ? LFS_O_RDWR : LFS_O_RDONLY; + case 'w': + return has_plus ? LFS_O_RDWR | LFS_O_CREAT | LFS_O_TRUNC : LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC; + case 'a': + return has_plus ? LFS_O_RDWR | LFS_O_CREAT | LFS_O_APPEND : LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND; + } + return -1; +} + +void *be_fopen(const char *filename, const char *modes) { + if (!lfs_present()) + init_lfs(1); + if (lfs_present()) { + int flags = mode_to_flags(modes); + if (flags == -1) { + return NULL; + } + lfs_file_t *file = malloc(sizeof(lfs_file_t)); + memset(file, 0, sizeof(lfs_file_t)); + int err = lfs_file_open(&lfs, file, filename, flags); + if (err) { + free(file); + return NULL; + } + return file; + } + return NULL; +} + +int be_fclose(void *hfile) { + int ret = lfs_file_close(&lfs, (lfs_file_t *)hfile); + free(hfile); + return ret; +} + +size_t be_fwrite(void *hfile, const void *buffer, size_t length) { + return lfs_file_write(&lfs, hfile, buffer, length); +} + +size_t be_fread(void *hfile, void *buffer, size_t length) { + return lfs_file_read(&lfs, hfile, buffer, length); +} + +char *be_fgets(void *hfile, void *buffer, int size) { + if (size < 1) + return NULL; + + char *dest = buffer; + int count = 0; + + while (count < size - 1) { + if (lfs_file_read(&lfs, hfile, dest, 1) != 1) { + // EOF or error + if (count == 0) + return NULL; + break; + } + + // Check for newline + if (*dest == '\n') { + count++; + dest++; + break; + } + + count++; + dest++; + } + + // Null-terminate the string + *dest = '\0'; + return buffer; +} + +int be_fseek(void *hfile, long offset) { + return lfs_file_seek(&lfs, hfile, offset, LFS_SEEK_SET); +} + +long int be_ftell(void *hfile) { + return lfs_file_tell(&lfs, hfile); +} + +long int be_fflush(void *hfile) { + return lfs_file_sync(&lfs, hfile); +} + +size_t be_fsize(void *hfile) { + return lfs_file_size(&lfs, hfile); +} + +#else // !ENABLE_LITTLEFS + +void *be_fopen(const char *filename, const char *modes) { + return NULL; +} + +int be_fclose(void *hfile) { + return -1; +} + +size_t be_fwrite(void *hfile, const void *buffer, size_t length) { + return 0; +} + +size_t be_fread(void *hfile, void *buffer, size_t length) { + return 0; +} + +char *be_fgets(void *hfile, void *buffer, int size) { + return NULL; +} + +int be_fseek(void *hfile, long offset) { + return -1; +} + +long int be_ftell(void *hfile) { + return 0; +} + +long int be_fflush(void *hfile) { + return 0; +} + +size_t be_fsize(void *hfile) { + return 0; +} + +#endif // ENABLE_LITTLEFS diff --git a/src/berry/be_run.c b/src/berry/be_run.c new file mode 100644 index 000000000..575ddb8bd --- /dev/null +++ b/src/berry/be_run.c @@ -0,0 +1,85 @@ +#include "be_run.h" +#include "../logging/logging.h" +#include "be_debug.h" + +const char berryPrelude[] = + "_suspended_closures = {}\n" + "_suspended_closures_idx = 0\n" + "\n" + "def suspend_closure(closure)\n" + " _suspended_closures_idx += 1\n" + " _suspended_closures[_suspended_closures_idx] = closure\n" + " return _suspended_closures_idx\n" + "end\n" + "\n" + "def resume_closure(idx)\n" + " closure = _suspended_closures[idx]\n" + " _suspended_closures.remove(idx)\n" + " closure()\n" + "end\n"; + +void be_error_pop_all(bvm *vm) { + be_pop(vm, be_top(vm)); // clear Berry stack +} + +// From Tasmota +void be_dumpstack(bvm *vm) { + int32_t top = be_top(vm); + ADDLOG_INFO(LOG_FEATURE_BERRY, "top=%d", top); + be_tracestack(vm); + for (uint32_t i = 1; i <= top; i++) { + const char *tname = be_typename(vm, i); + const char *cname = be_classname(vm, i); + if (be_isstring(vm, i)) { + cname = be_tostring(vm, i); + } + ADDLOG_INFO(LOG_FEATURE_BERRY, "stack[%d] = type='%s' (%s)", i, (tname != NULL) ? tname : "", (cname != NULL) ? cname : ""); + } +} + +bool berryRun(bvm *vm, const char *prog) { + bool success = true; + ADDLOG_INFO(LOG_FEATURE_BERRY, "[berry start]"); + int ret_code1 = be_loadstring(vm, prog); + if (ret_code1 != 0) { + ADDLOG_INFO(LOG_FEATURE_BERRY, "be_loadstring fail, retcode %d: %s", ret_code1, prog); + be_dumpstack(vm); + be_error_pop_all(vm); + success = false; + goto err; + } + + int ret_code2 = be_pcall(vm, 0); + if (ret_code2 != 0) { + ADDLOG_INFO(LOG_FEATURE_BERRY, "be_pcall fail, retcode %d", ret_code2); + be_dumpstack(vm); + be_error_pop_all(vm); + success = false; + goto err; + } + + if (be_top(vm) > 1) { + be_error_pop_all(vm); + } else { + be_pop(vm, 1); + } + +err: + ADDLOG_INFO(LOG_FEATURE_BERRY, "[berry end]"); + return success; +} + +void berryResumeClosure(bvm *vm, int closureId) { + if (!be_getglobal(vm, "resume_closure")) { + return; + } + be_pushint(vm, closureId); + // call resume_closure(closureId) + be_call(vm, 1); +} + +void berryFreeAllClosures(bvm *vm) { + // free waiting closures + // kind of hacky + berryRun(vm, "_suspended_closures = {}"); +} diff --git a/src/berry/be_run.h b/src/berry/be_run.h new file mode 100644 index 000000000..92e759c81 --- /dev/null +++ b/src/berry/be_run.h @@ -0,0 +1,10 @@ +#pragma once +#include "../new_common.h" +#include "berry.h" + +extern const char berryPrelude[]; +void be_dumpstack(bvm *vm); + +bool berryRun(bvm *vm, const char *prog); +void berryResumeClosure(bvm *vm, int closureId); +void berryFreeAllClosures(bvm *vm); diff --git a/src/cmnds/cmd_berry.c b/src/cmnds/cmd_berry.c new file mode 100644 index 000000000..821f37566 --- /dev/null +++ b/src/cmnds/cmd_berry.c @@ -0,0 +1,233 @@ +#include "../logging/logging.h" +#include "../new_cfg.h" +#include "../new_pins.h" +#include "../obk_config.h" + +#if ENABLE_OBK_BERRY + +#include "../driver/drv_ir.h" +#include "../driver/drv_local.h" +#include "../driver/drv_public.h" +#include "../driver/drv_uart.h" +#include "../hal/hal_adc.h" +#include "../hal/hal_flashVars.h" +#include "cmd_local.h" +#include + +#include "../berry/be_bindings.h" +#include "../berry/be_run.h" +#include "be_repl.h" +#include "be_vm.h" +#include "berry.h" + +bvm *g_vm = NULL; + +int be_AddChangeHandler(bvm *vm) { + int top = be_top(vm); + + if (top == 4 && be_isstring(vm, 1) && be_isstring(vm, 2) && be_isint(vm, 3) && be_isfunction(vm, 4)) { + const char *eventName = be_tostring(vm, 1); + const char *relationStr = be_tostring(vm, 2); + int reqArg = be_toint(vm, 3); + + int relation = parseRelationChar(relationStr); + + int eventCode = EVENT_ParseEventName(eventName); + if (eventCode == CMD_EVENT_NONE) { + ADDLOG_INFO(LOG_FEATURE_EVENT, "be_AddChangeHandler: %s is not a valid event", eventName); + be_return_nil(vm); + } + + // try to push suspend_closure function on the stack + if (!be_getglobal(vm, "suspend_closure")) { + // prelude not loaded?? + be_return_nil(vm); + } + // push the 4th argument (closure) on the stack + be_pushvalue(vm, 4); + // call suspend_closure with the second arg + be_call(vm, 1); + // it should return an ID of the suspended closure, to be used to wake up later + if (be_isint(vm, -2)) { + int closure_id = be_toint(vm, -2); + scriptInstance_t *th; + th = SVM_RegisterThread(); + if (th == 0) { + ADDLOG_INFO(LOG_FEATURE_CMD, "be_AddChangeHandler: failed to alloc thread"); + be_return_nil(vm); + } + int thread_id = 5000 + closure_id; // TODO: alloc IDs? + th->uniqueID = thread_id; + th->curFile = NULL; + th->curLine = ""; // NB. needs to be != NULL to get scheduled + th->currentDelayMS = 0; + th->waitingForEvent = eventCode; + th->waitingForArgument = reqArg; + th->waitingForRelation = relation; + th->isBerry = true; + th->closureId = closure_id; + + // remove the 2 values we pushed on the stack + be_pop(vm, 2); + + // Return the thread ID to Berry + be_pushint(vm, thread_id); + be_return(vm); + } + // remove the 2 values we pushed on the stack + be_pop(vm, 2); + } + be_return_nil(vm); +} + +int be_ScriptDelayMs(bvm *vm) { + int top = be_top(vm); + if (top == 2 && be_isint(vm, 1) && be_isfunction(vm, 2)) { + int delay_ms = be_toint(vm, 1); + // try to push suspend_closure function on the stack + if (!be_getglobal(vm, "suspend_closure")) { + // prelude not loaded?? + be_return_nil(vm); + } + // push the second argument (closure) on the stack + be_pushvalue(vm, 2); + // call suspend_closure with the second arg + be_call(vm, 1); + // it should return an ID of the suspended closure, to be used to wake up later + if (be_isint(vm, -2)) { + int closure_id = be_toint(vm, -2); + scriptInstance_t *th; + th = SVM_RegisterThread(); + if (th == 0) { + ADDLOG_INFO(LOG_FEATURE_CMD, "be_delayMs: failed to alloc thread"); + be_return_nil(vm); + } + int thread_id = 5000 + closure_id; // TODO: alloc IDs? + th->uniqueID = thread_id; + th->curFile = NULL; + th->curLine = ""; // NB. needs to be != NULL to get scheduled + th->currentDelayMS = delay_ms; + th->isBerry = true; + th->closureId = closure_id; + + // remove the 2 values we pushed on the stack + be_pop(vm, 2); + + // Return the thread ID to Berry + be_pushint(vm, thread_id); + be_return(vm); + } + // remove the 2 values we pushed on the stack + be_pop(vm, 2); + } + be_return_nil(vm); +} + +static int BasicInit() { + if (!g_vm) { + // Lazy init for now, to avoid resource consumption and boot loops + ADDLOG_INFO(LOG_FEATURE_BERRY, "[berry init]"); + g_vm = be_vm_new(); /* create a virtual machine instance */ + be_regfunc(g_vm, "setChannel", be_ChannelSet); + be_regfunc(g_vm, "scriptDelayMs", be_ScriptDelayMs); + be_regfunc(g_vm, "getChannel", be_ChannelGet); + be_regfunc(g_vm, "addChannel", be_ChannelAdd); + be_regfunc(g_vm, "setStartValue", be_SetStartValue); + be_regfunc(g_vm, "setChannelType", be_SetChannelType); + be_regfunc(g_vm, "setChannelLabel", be_SetChannelLabel); + be_regfunc(g_vm, "addChangeHandler", be_AddChangeHandler); + be_regfunc(g_vm, "runCmd", be_runCmd); + + be_regfunc(g_vm, "rtosDelayMs", be_rtosDelayMs); + be_regfunc(g_vm, "delayUs", be_delayUs); + be_regfunc(g_vm, "initI2c", be_initI2c); + be_regfunc(g_vm, "startI2c", be_startI2c); + be_regfunc(g_vm, "stopI2c", be_stopI2c); + be_regfunc(g_vm, "writeByteI2c", be_writeByteI2c); + be_regfunc(g_vm, "readByteI2c", be_readByteI2c); + be_regfunc(g_vm, "readBytesI2c", be_readBytesI2c); + be_regfunc(g_vm, "cancel", be_CancelThread); + if (!berryRun(g_vm, berryPrelude)) { + return 0; + } + } + return 1; +} + +static commandResult_t CMD_Berry(const void *context, const char *cmd, const char *args, int cmdFlags) { + if (BasicInit()) { + return berryRun(g_vm, args) ? CMD_RES_OK : CMD_RES_ERROR; + } + return CMD_RES_ERROR; +} + +void berryThreadComplete(scriptInstance_t *thread) { + // Free the associated closure if it exists + if (thread->closureId > 0 && g_vm) { + // Get the _suspended_closures table from global scope + if (be_getglobal(g_vm, "_suspended_closures")) { + // Push the closure ID as the key + be_pushint(g_vm, thread->closureId); + + // Push nil as the value + be_pushnil(g_vm); + + // Set _suspended_closures[closureId] = nil + be_setindex(g_vm, -3); + + // Pop the _suspended_closures table from the stack + be_pop(g_vm, 1); + } + } + + // Reset all Berry-specific flags and data + thread->isBerry = false; + thread->closureId = -1; + thread->curLine = 0; + thread->curFile = 0; + thread->uniqueID = 0; + thread->currentDelayMS = 0; +} + +// Useful for testing things that affect global state: +// * globals +// * module init() +void stopBerrySVM() { + if (g_vm) { + // Find and stop any Berry script threads + scriptInstance_t *t = g_scriptThreads; + while (t) { + if (t->isBerry) { + berryThreadComplete(t); + } + t = t->next; + } + } +} + +void CMD_StopBerry() { + if (g_vm) { + stopBerrySVM(); + be_vm_delete(g_vm); + g_vm = NULL; + } +} +static commandResult_t CMD_StopBerryCommand(const void *context, const char *cmd, const char *args, int cmdFlags) { + CMD_StopBerry(); + return CMD_RES_OK; +} + +void CMD_InitBerry() { + // cmddetail:{"name":"berry","args":"[Berry code]", + // cmddetail:"descr":"Execute Berry code", + // cmddetail:"fn":"CMD_Berry","file":"cmnds/cmd_berry.c","requires":"", + // cmddetail:"examples":"berry 1+2"} + CMD_RegisterCommand("berry", CMD_Berry, NULL); + // cmddetail:{"name":"stopBerry","args":"[Berry code]", + // cmddetail:"descr":"Stop Berry VM", + // cmddetail:"fn":"CMD_StopBerry","file":"cmnds/cmd_berry.c","requires":"", + // cmddetail:"examples":"stopBerry"} + CMD_RegisterCommand("stopBerry", CMD_StopBerryCommand, NULL); +} + +#endif diff --git a/src/cmnds/cmd_main.c b/src/cmnds/cmd_main.c index c93e3f6a3..6884c8f0e 100644 --- a/src/cmnds/cmd_main.c +++ b/src/cmnds/cmd_main.c @@ -412,6 +412,8 @@ static commandResult_t CMD_Echo(const void* context, const char* cmd, const char return CMD_RES_OK; } + + static commandResult_t CMD_StartupCommand(const void* context, const char* cmd, const char* args, int cmdFlags) { const char *cmdToSet; @@ -966,6 +968,7 @@ void CMD_Init_Early() { //cmddetail:"fn":"NULL);","file":"cmnds/cmd_main.c","requires":"", //cmddetail:"examples":""} CMD_RegisterCommand("IndexRefreshInterval", CMD_IndexRefreshInterval, NULL); + #if MQTT_USE_TLS //cmddetail:{"name":"WebServer","args":"[0 - Stop / 1 - Start]", //cmddetail:"descr":"Setting state of WebServer", @@ -976,6 +979,9 @@ void CMD_Init_Early() { #if ENABLE_OBK_SCRIPTING CMD_InitScripting(); +#endif +#if ENABLE_OBK_BERRY + CMD_InitBerry(); #endif if (!bSafeMode) { if (CFG_HasFlag(OBK_FLAG_CMD_ACCEPT_UART_COMMANDS)) { diff --git a/src/cmnds/cmd_public.h b/src/cmnds/cmd_public.h index ce37b32b7..144412de3 100644 --- a/src/cmnds/cmd_public.h +++ b/src/cmnds/cmd_public.h @@ -13,6 +13,35 @@ typedef enum commandResult_e { } commandResult_t; +typedef struct scriptFile_s +{ + char* fname; + char* data; + + struct scriptFile_s* next; +} scriptFile_t; + +typedef struct scriptInstance_s +{ + scriptFile_t* curFile; + int uniqueID; + const char* curLine; + int currentDelayMS; + + int waitingForArgument; + unsigned short waitingForEvent; + char waitingForRelation; + + struct scriptInstance_s* next; +#if ENABLE_OBK_BERRY + bool isBerry; + int closureId; +#endif +} scriptInstance_t; + +scriptInstance_t *SVM_RegisterThread(); +extern scriptInstance_t *g_scriptThreads; + typedef commandResult_t(*commandHandler_t)(const void* context, const char* cmd, const char* args, int flags); // command was entered in console (web app etc) @@ -134,6 +163,9 @@ enum EventCode { int EVENT_ParseEventName(const char *s); +// Helper to parse relation characters (<, >, !) from string arguments +char parseRelationChar(const char *relationStr); + // the slider control in the UI emits values //in the range from 154-500 (defined //in homeassistant/util/color.py as HASS_COLOR_MIN and HASS_COLOR_MAX). @@ -267,6 +299,8 @@ int CMD_InitSendCommands(); void CMD_StartTCPCommandLine(); // cmd_script.c int CMD_GetCountActiveScriptThreads(); +// cmd_berry.c +void CMD_InitBerry(); const char* CMD_GetResultString(commandResult_t r); @@ -281,4 +315,5 @@ commandResult_t RepeatingEvents_Cmd_ClearRepeatingEvents(const void* context, co commandResult_t CMD_resetSVM(const void* context, const char* cmd, const char* args, int cmdFlags); int RepeatingEvents_GetActiveCount(); + #endif // __CMD_PUBLIC_H__ diff --git a/src/cmnds/cmd_script.c b/src/cmnds/cmd_script.c index e159c2de4..d8bb8b456 100644 --- a/src/cmnds/cmd_script.c +++ b/src/cmnds/cmd_script.c @@ -7,6 +7,10 @@ #include #include "cmd_local.h" +#if ENABLE_OBK_BERRY +#include "berry.h" +#endif + /* startScript test1.bat @@ -225,26 +229,6 @@ addEventHandler OnHold 20 backlog AddChannel 10 2 0 255; DGR_SendBrightness room */ -typedef struct scriptFile_s { - char *fname; - char *data; - - struct scriptFile_s *next; -} scriptFile_t; - -typedef struct scriptInstance_s { - scriptFile_t *curFile; - int uniqueID; - const char *curLine; - int currentDelayMS; - - int waitingForArgument; - unsigned short waitingForEvent; - char waitingForRelation; - - struct scriptInstance_s *next; -} scriptInstance_t; - int g_scrBufferSize = 0; char *g_scrBuffer = NULL; int svm_deltaMS; @@ -252,6 +236,12 @@ scriptFile_t *g_scriptFiles = 0; scriptInstance_t *g_scriptThreads = 0; scriptInstance_t *g_activeThread = 0; +#if ENABLE_OBK_BERRY +extern bvm* g_vm; +// Function to properly cleanup Berry threads +extern void berryThreadComplete(scriptInstance_t *thread); +#endif + scriptInstance_t *SVM_RegisterThread() { scriptInstance_t *r; @@ -371,6 +361,15 @@ void SVM_RunThread(scriptInstance_t *t, int maxLoops) { g_scrBuffer = malloc(g_scrBufferSize + 1); } + +#if ENABLE_OBK_BERRY + if (t->isBerry && t->closureId > 0) { + berryResumeClosure(g_vm, t->closureId); + berryThreadComplete(t); + return; + } +#endif + while(1) { loop++; // check if "waitFor" was executed last frame @@ -558,10 +557,15 @@ void SVM_StopAllScripts() { t->curFile = 0; t->uniqueID = 0; t->currentDelayMS = 0; - +#if ENABLE_OBK_BERRY + if (t->isBerry) { + berryThreadComplete(t); + } +#endif t = t->next; } } + void SVM_StopScripts(int id, int bExcludeSelf) { scriptInstance_t *t; @@ -575,6 +579,11 @@ void SVM_StopScripts(int id, int bExcludeSelf) { t->curFile = 0; t->uniqueID = 0; t->currentDelayMS = 0; +#if ENABLE_OBK_BERRY + if (t->isBerry) { + berryThreadComplete(t); + } +#endif } } t = t->next; @@ -590,10 +599,33 @@ void SVM_GoToLocal(scriptInstance_t *th, const char *label) { return; } +int hasExtension(const char *fname, const char *ext) { + size_t len_fname = strlen(fname); + size_t len_ext = strlen(ext); + return (len_fname >= len_ext && strcmp(fname + len_fname - len_ext, ext) == 0); +} scriptInstance_t *SVM_StartScript(const char *fname, const char *label, int uniqueID) { scriptFile_t *f; scriptInstance_t *th; +#if 1 + // allow "startScript test.be" as a shorthand for "berry import test" +#if ENABLE_OBK_BERRY + if (hasExtension(fname, ".be")) { + // berry does not like slash? + if (*fname == '/' || *fname == '\\') { + fname++; + } + char tmp[64]; + sprintf(tmp, "berry import %s",fname); + tmp[strlen(tmp) - 3] = 0; + // strip .be + ADDLOG_INFO(LOG_FEATURE_CMD, "CMD_StartScript: will run %s", tmp); + CMD_ExecuteCommand(tmp,0); + return NULL; + } +#endif +#endif f = SVM_RegisterFile(fname); if(f == 0) { ADDLOG_INFO(LOG_FEATURE_CMD, "CMD_StartScript: failed to get file %s",fname); @@ -817,9 +849,13 @@ static commandResult_t CMD_StopAllScripts(const void *context, const char *cmd, return CMD_RES_OK; } +void CMD_StopBerry(); + commandResult_t CMD_resetSVM(const void *context, const char *cmd, const char *args, int cmdFlags){ - +#if ENABLE_OBK_BERRY + CMD_StopBerry(); +#endif // stop scripts SVM_StopAllScripts(); // clear files @@ -827,6 +863,19 @@ commandResult_t CMD_resetSVM(const void *context, const char *cmd, const char *a return CMD_RES_OK; } + +// Helper function to parse relation character for scripts and event handlers +char parseRelationChar(const char *relationStr) { + if (*relationStr == '<') { + return '<'; + } else if (*relationStr == '>') { + return '>'; + } else if (*relationStr == '!') { + return '!'; + } else { + return 0; + } +} commandResult_t CMD_waitFor(const void *context, const char *cmd, const char *args, int cmdFlags) { const char *s; int reqArg, eventCode; @@ -851,15 +900,7 @@ commandResult_t CMD_waitFor(const void *context, const char *cmd, const char *ar return CMD_RES_BAD_ARGUMENT; } s = Tokenizer_GetArg(1); - if (*s == '<') { - relation = '<'; - } else if (*s == '>') { - relation = '>'; - } else if (*s == '!') { - relation = '!'; - } else { - relation = 0; - } + relation = parseRelationChar(s); if (relation) { s = Tokenizer_GetArg(2); } @@ -871,6 +912,7 @@ commandResult_t CMD_waitFor(const void *context, const char *cmd, const char *ar return CMD_RES_OK; } + void CMD_InitScripting(){ //cmddetail:{"name":"startScript","args":"[FileName][Label][UniqueID]", //cmddetail:"descr":"Starts a script thread from given file, at given label - can be * for whole file, with given unique ID", @@ -922,7 +964,4 @@ void CMD_InitScripting(){ //cmddetail:"fn":"CMD_waitFor","file":"cmnds/cmd_script.c","requires":"", //cmddetail:"examples":""} CMD_RegisterCommand("waitFor", CMD_waitFor, NULL); - } - - diff --git a/src/logging/logging.c b/src/logging/logging.c index f14d0f5cd..f5fd800b7 100644 --- a/src/logging/logging.c +++ b/src/logging/logging.c @@ -81,7 +81,7 @@ char* logfeaturenames[] = { "IR:", // = 20 "SENSOR:", // = 21 "DRV:", // = 22, - "ERROR",// = 23, + "BERRY:",// = 23, "ERROR",// = 24, "ERROR",// = 25, "ERROR",// = 26, diff --git a/src/logging/logging.h b/src/logging/logging.h index fd53173a9..3ca959009 100644 --- a/src/logging/logging.h +++ b/src/logging/logging.h @@ -85,7 +85,8 @@ typedef enum { LOG_FEATURE_IR = 20, LOG_FEATURE_SENSOR = 21, LOG_FEATURE_DRV = 22, - LOG_FEATURE_MAX = 23, + LOG_FEATURE_BERRY = 23, + LOG_FEATURE_MAX = 24, } log_features; #endif diff --git a/src/new_pins.h b/src/new_pins.h index 75b0054b7..efa277555 100644 --- a/src/new_pins.h +++ b/src/new_pins.h @@ -1467,6 +1467,7 @@ int CHANNEL_FindMaxValueForChannel(int ch); // cmd_channels.c bool CHANNEL_HasLabel(int ch); const char* CHANNEL_GetLabel(int ch); +void CHANNEL_SetLabel(int ch, const char *s, int bHideTogglePrefix); bool CHANNEL_ShouldAddTogglePrefixToUI(int ch); bool CHANNEL_HasNeverPublishFlag(int ch); //ledRemap_t *CFG_GetLEDRemap(); @@ -1476,6 +1477,7 @@ int h_isChannelPWM(int tg_ch); int h_isChannelRelay(int tg_ch); int h_isChannelDigitalInput(int tg_ch); +int CHANNEL_ParseChannelType(const char* s); const char *ChannelType_GetTitle(int type); const char *ChannelType_GetUnit(int type); int ChannelType_GetDivider(int type); diff --git a/src/obk_config.h b/src/obk_config.h index 89c32b9b6..b7fe778f6 100644 --- a/src/obk_config.h +++ b/src/obk_config.h @@ -133,6 +133,7 @@ #define ENABLE_DRIVER_SGP 1 #define ENABLE_DRIVER_SHIFTREGISTER 1 #define ENABLE_OBK_SCRIPTING 1 +#define ENABLE_OBK_BERRY 1 #elif PLATFORM_BL602 @@ -226,6 +227,7 @@ #if PLATFORM_BEKEN_NEW #define NEW_TCP_SERVER 1 #endif +// #define ENABLE_OBK_BERRY 1 // ENABLE_I2C_ is a syntax for // our I2C system defines for drv_i2c_main.c diff --git a/src/selftest/selftest_berry.c b/src/selftest/selftest_berry.c new file mode 100644 index 000000000..fb012c1d4 --- /dev/null +++ b/src/selftest/selftest_berry.c @@ -0,0 +1,497 @@ +#ifdef WINDOWS + +#include "selftest_local.h" + +void Test_Berry_ChannelSet() { + int i; + + // reset whole device + SIM_ClearOBK(0); + + // Make sure channels start at 0 + CMD_ExecuteCommand("setChannel 1 0", 0); + CMD_ExecuteCommand("setChannel 2 0", 0); + + SELFTEST_ASSERT_CHANNEL(1, 0); + SELFTEST_ASSERT_CHANNEL(2, 0); + + // Run Berry code that sets channels + CMD_ExecuteCommand("berry setChannel(1, 42)", 0); + CMD_ExecuteCommand("berry setChannel(2, 123)", 0); + + // Verify the channels were set correctly + SELFTEST_ASSERT_CHANNEL(1, 42); + SELFTEST_ASSERT_CHANNEL(2, 123); + + // Test setting multiple channels in one script + CMD_ExecuteCommand("berry setChannel(1, 99); setChannel(2, 88)", 0); + + // Verify the updated values + SELFTEST_ASSERT_CHANNEL(1, 99); + SELFTEST_ASSERT_CHANNEL(2, 88); +} + +void Test_Berry_CancelThread() { + int i; + + // reset whole device + SIM_ClearOBK(0); + + // Make sure channels start at 0 + CMD_ExecuteCommand("setChannel 1 0", 0); + CMD_ExecuteCommand("setChannel 2 0", 0); + + SELFTEST_ASSERT_CHANNEL(1, 0); + SELFTEST_ASSERT_CHANNEL(2, 0); + + // Run Berry code that creates a delayed thread that would set channels + // and stores the thread ID in a global variable + CMD_ExecuteCommand("berry thread_id = scriptDelayMs(100, def() setChannel(1, 42); setChannel(2, 123); end); print(thread_id)", 0); + + // Now cancel the thread using the thread_id + CMD_ExecuteCommand("berry cancel(thread_id)", 0); + + // Run scheduler long enough for the original delay to have completed if it wasn't cancelled + for (i = 0; i < 20; i++) { + SVM_RunThreads(10); + } + + // Verify the channels were NOT set (they should still be 0) + SELFTEST_ASSERT_CHANNEL(1, 0); + SELFTEST_ASSERT_CHANNEL(2, 0); + + // Now let's test the positive case - create a thread and let it complete + CMD_ExecuteCommand("berry scriptDelayMs(50, def() setChannel(1, 99); setChannel(2, 88); end)", 0); + + // Run scheduler to let the thread complete + for (i = 0; i < 20; i++) { + SVM_RunThreads(10); + } + + // Verify the channels WERE set this time + SELFTEST_ASSERT_CHANNEL(1, 99); + SELFTEST_ASSERT_CHANNEL(2, 88); + + // One more test + CMD_ExecuteCommand("berry scriptDelayMs(50, def() addChannel(1, 1); addChannel(2, 2); end)", 0); + // Run scheduler to let the thread complete + for (i = 0; i < 20; i++) { + SVM_RunThreads(10); + } + // Verify the channels were added + SELFTEST_ASSERT_CHANNEL(1, 100); + SELFTEST_ASSERT_CHANNEL(2, 90); + // twice + CMD_ExecuteCommand("berry scriptDelayMs(50, def() addChannel(1, 1); addChannel(2, 2); end)", 0); + CMD_ExecuteCommand("berry scriptDelayMs(50, def() addChannel(1, 1); addChannel(2, 2); end)", 0); + // Run scheduler to let the thread complete + for (i = 0; i < 20; i++) { + SVM_RunThreads(10); + } + SELFTEST_ASSERT_CHANNEL(1, 102); + SELFTEST_ASSERT_CHANNEL(2, 94); + +} + +void Test_Berry_Import() { + int i; + + // reset whole device + SIM_ClearOBK(0); + CMD_ExecuteCommand("lfs_format", 0); + + // Create an autoexec.be file with a proper module pattern + Test_FakeHTTPClientPacket_POST("api/lfs/autoexec.be", + "autoexec = module('autoexec')\n" + "\n" + "# Add functions to the module\n" + "autoexec.set_test_channels = def()\n" + " setChannel(1, 42)\n" + " setChannel(2, 84)\n" + "end\n" + "\n" + "# This value can be accessed as autoexec.TEST_VALUE\n" + "autoexec.TEST_VALUE = 123\n" + "\n" + "# Berry modules must return the module object\n" + "return autoexec\n"); + + // Make sure channels start at 0 + CMD_ExecuteCommand("setChannel 1 0", 0); + CMD_ExecuteCommand("setChannel 2 0", 0); + + SELFTEST_ASSERT_CHANNEL(1, 0); + SELFTEST_ASSERT_CHANNEL(2, 0); + + // Import autoexec module and call the function + CMD_ExecuteCommand("berry import autoexec; autoexec.set_test_channels()", 0); + + // Verify the channels were set correctly by the imported module + SELFTEST_ASSERT_CHANNEL(1, 42); + SELFTEST_ASSERT_CHANNEL(2, 84); + + // Test accessing a module constant + CMD_ExecuteCommand("berry import autoexec; setChannel(3, autoexec.TEST_VALUE)", 0); + + // Verify the constant from the module was correctly accessed + SELFTEST_ASSERT_CHANNEL(3, 123); +} +void Test_Berry_Fibonacci() { + int i, n = 5; + long long result = 1; + + // Reset the whole device (for demo) + SIM_ClearOBK(0); + CMD_ExecuteCommand("lfs_format", 0); + + // Create an autoexec.be file with the Fibonacci sequence generator using if and recursion + Test_FakeHTTPClientPacket_POST("api/lfs/tester.be", + "tester = module('tester')\n" + "\n" + "tester.fib = def(n)\n" + " if n <= 1 return n end\n" + " return tester.fib(n - 1) + tester.fib(n - 2)\n" + "end\n" + "\n" + "# Berry modules must return the module object\n" + "return tester\n"); + + // Make sure no channels are set before testing + CMD_ExecuteCommand("setChannel 1 0", 0); + SELFTEST_ASSERT_CHANNEL(1, 0); + + // Import the autoexec module and call the Fibonacci function + CMD_ExecuteCommand("berry import tester; setChannel(1,tester.fib(10))", 0); + // Verify the result of Fibonacci sequence + SELFTEST_ASSERT_CHANNEL(1, 55); + // Import the autoexec module and call the Fibonacci function + CMD_ExecuteCommand("berry import tester; setChannel(1,tester.fib(11))", 0); + // Verify the result of Fibonacci sequence + SELFTEST_ASSERT_CHANNEL(1, 89); +} + +void Test_Berry_ThreadCleanup() { + int i; + + // reset whole device + SIM_ClearOBK(0); + CMD_ExecuteCommand("lfs_format", 0); + + // Clear all channels + CMD_ExecuteCommand("setChannel 1 0", 0); + CMD_ExecuteCommand("setChannel 2 0", 0); + CMD_ExecuteCommand("setChannel 3 0", 0); + + SELFTEST_ASSERT_CHANNEL(1, 0); + SELFTEST_ASSERT_CHANNEL(2, 0); + SELFTEST_ASSERT_CHANNEL(3, 0); + + // First run a Berry script that completes quickly + CMD_ExecuteCommand("berry scriptDelayMs(10, def() setChannel(1, 42); end)", 0); + + // Run the scheduler to let the Berry script complete + for (i = 0; i < 5; i++) { + SVM_RunThreads(10); + } + + // Verify the Berry script did run + SELFTEST_ASSERT_CHANNEL(1, 42); + + // Now create a script file that should run normally as an SVM script + Test_FakeHTTPClientPacket_POST("api/lfs/testSVMScript.txt", + "setChannel 2 123\n" + "delay_ms 50\n" // Add a delay to ensure the thread stays active + "setChannel 3 456\n"); + + // Run the SVM script - this should use a thread from the pool + // If the thread still has isBerry=true, this will execute differently + CMD_ExecuteCommand("startScript testSVMScript.txt", 0); + + // Let the script start running + SVM_RunThreads(20); + + // Channel 2 should be set already (before the delay) + SELFTEST_ASSERT_CHANNEL(2, 123); + + // Channel 3 should not be set yet (after the delay) + SELFTEST_ASSERT_CHANNEL(3, 0); + + // Now let the script complete + for (i = 0; i < 10; i++) { + SVM_RunThreads(10); + } + + // Now channel 3 should be set + SELFTEST_ASSERT_CHANNEL(3, 456); + + // Run one more Berry script to confirm both types still work + CMD_ExecuteCommand("berry setChannel(1, 789)", 0); + SELFTEST_ASSERT_CHANNEL(1, 789); +} +void Test_Berry_AutoloadModule() { + // reset whole device + SIM_ClearOBK(0); + CMD_ExecuteCommand("lfs_format", 0); + + // Create a Berry module file + Test_FakeHTTPClientPacket_POST("api/lfs/autoexec.be", + "autoexec = module('autoexec')\n" + "\n" + "autoexec.init = def (self)\n" + " print('Hello from autoexec.be')\n" + " setChannel(5, 42) # Set a channel so we can verify init ran\n" + " return self\n" + "end\n" + "\n" + "return autoexec\n"); + + // Create an autoexec.txt file that imports the module + Test_FakeHTTPClientPacket_POST("api/lfs/autoexec.txt", + "berry import autoexec\n"); + + // Make sure channel starts at 0 + CMD_ExecuteCommand("setChannel 5 0", 0); + SELFTEST_ASSERT_CHANNEL(5, 0); + + // Simulate a device restart by running the autoexec.txt script + CMD_ExecuteCommand("startScript autoexec.txt", 0); + + // Run scheduler to let any scripts complete + for (int i = 0; i < 5; i++) { + SVM_RunThreads(10); + } + + // Verify that the Berry module was loaded and initialized (it should have set channel 5 to 42) + SELFTEST_ASSERT_CHANNEL(5, 42); + + // Test that we can now use the module functions directly + CMD_ExecuteCommand("berry import autoexec; autoexec.init(); setChannel(6, 99)", 0); + SELFTEST_ASSERT_CHANNEL(6, 99); +} + +void Test_Berry_StartScriptShortcut() { + // reset whole device + SIM_ClearOBK(0); + CMD_ExecuteCommand("lfs_format", 0); + + // Create a Berry module file + Test_FakeHTTPClientPacket_POST("api/lfs/test.be", + "def mySample()\n" + " print('Hello from test.be')\n" + " setChannel(5, 2025) # Set a channel so we can verify init ran\n" + "end\n" + "\n" + "mySample()\n"); + + // Make sure channel starts at 0 + CMD_ExecuteCommand("setChannel 5 0", 0); + SELFTEST_ASSERT_CHANNEL(5, 0); + + // Simulate a device restart by running the autoexec.txt script + CMD_ExecuteCommand("startScript test.be", 0); + + // Run scheduler to let any scripts complete + for (int i = 0; i < 5; i++) { + SVM_RunThreads(10); + } + + // Verify that the Berry module was loaded and initialized (it should have set channel 5 to 2025) + SELFTEST_ASSERT_CHANNEL(5, 2025); +} + + +void Test_Berry_PassArg() { + // reset whole device + SIM_ClearOBK(0); + CMD_ExecuteCommand("lfs_format", 0); + + // Create a Berry module file + Test_FakeHTTPClientPacket_POST("api/lfs/test.be", + "def mySample(x)\n" + " print('Hello from test.be')\n" + " setChannel(5, x) # Set a channel so we can verify init ran\n" + "end\n" + "\n" + "mySample(15)\n"); + + // Make sure channel starts at 0 + CMD_ExecuteCommand("setChannel 5 0", 0); + SELFTEST_ASSERT_CHANNEL(5, 0); + + // Simulate a device restart by running the autoexec.txt script + CMD_ExecuteCommand("startScript test.be", 0); + + // Run scheduler to let any scripts complete + for (int i = 0; i < 5; i++) { + SVM_RunThreads(10); + } + + // Verify that the Berry module was loaded and initialized (it should have set channel 5 to 15) + SELFTEST_ASSERT_CHANNEL(5, 15); +} + + + +void Test_Berry_PassArgFromCommand() { + // reset whole device + SIM_ClearOBK(0); + CMD_ExecuteCommand("lfs_format", 0); + + // Make sure channel starts at 0 + CMD_ExecuteCommand("setChannel 5 0", 0); + SELFTEST_ASSERT_CHANNEL(5, 0); + + // Simulate a device restart by running the autoexec.txt script + CMD_ExecuteCommand("berry def mySample(x) addChannel(5, x) end", 0); + CMD_ExecuteCommand("berry mySample(15)", 0); + SELFTEST_ASSERT_CHANNEL(5, 15); + CMD_ExecuteCommand("berry mySample(15)", 0); + SELFTEST_ASSERT_CHANNEL(5, 30); + CMD_ExecuteCommand("berry mySample(2)", 0); + SELFTEST_ASSERT_CHANNEL(5, 32); + CMD_ExecuteCommand("berry mySample(-5)", 0); + SELFTEST_ASSERT_CHANNEL(5, 27); +} + + + +void Test_Berry_PassArgFromCommandWithoutModule() { + /* + // reset whole device + SIM_ClearOBK(0); + CMD_ExecuteCommand("lfs_format", 0); + + // Create a Berry module file + Test_FakeHTTPClientPacket_POST("api/lfs/test.be", + "def mySample(x)\n" + " print('Hello from test.be')\n" + " addChannel(5, x) # Set a channel so we can verify init ran\n" + "end\n\n"); + + // Make sure channel starts at 0 + CMD_ExecuteCommand("setChannel 5 0", 0); + SELFTEST_ASSERT_CHANNEL(5, 0); + + // Simulate a device restart by running the autoexec.txt script + CMD_ExecuteCommand("berry import test", 0); + CMD_ExecuteCommand("berry mySample(2)", 0); + SELFTEST_ASSERT_CHANNEL(5, 2); + CMD_ExecuteCommand("berry mySample(2)", 0); + SELFTEST_ASSERT_CHANNEL(5, 4); + CMD_ExecuteCommand("berry mySample(2)", 0); + SELFTEST_ASSERT_CHANNEL(5, 6); + */ +} +void Test_Berry_PassArgFromCommandWithModule() { + // reset whole device + SIM_ClearOBK(0); + CMD_ExecuteCommand("lfs_format", 0); + + // Create a Berry module file + Test_FakeHTTPClientPacket_POST("api/lfs/test.be", + "test = module('test')\n" + "\n" + "# Add functions to the module\n" + "test.mySample = def(x)\n" + " addChannel(5, x) # Set a channel so we can verify init ran\n" + "end\n" + "\n" + "return test\n"); + + // Make sure channel starts at 0 + CMD_ExecuteCommand("setChannel 5 0", 0); + SELFTEST_ASSERT_CHANNEL(5, 0); + + // Simulate a device restart by running the autoexec.txt script + CMD_ExecuteCommand("berry import test; test.mySample(2)", 0); + SELFTEST_ASSERT_CHANNEL(5, 2); + CMD_ExecuteCommand("berry import test; test.mySample(2)", 0); + SELFTEST_ASSERT_CHANNEL(5, 4); + CMD_ExecuteCommand("berry import test; test.mySample(2)", 0); + SELFTEST_ASSERT_CHANNEL(5, 6); + CMD_ExecuteCommand("berry test.mySample(2)", 0); + SELFTEST_ASSERT_CHANNEL(5, 8); +} + +void Test_Berry_CommandRunner() { + int i; + + // reset whole device + SIM_ClearOBK(0); + + // Make sure channels start at 0 + CMD_ExecuteCommand("setChannel 1 0", 0); + + SELFTEST_ASSERT_CHANNEL(1, 0); + CMD_ExecuteCommand("berry runCmd(\"setChannel 1 123\")", 0); + SELFTEST_ASSERT_CHANNEL(1, 123); + CMD_ExecuteCommand("berry runCmd(\"addChannel 1 7\")", 0); + SELFTEST_ASSERT_CHANNEL(1, 130); + CMD_ExecuteCommand("berry runCmd(\"addChannel 1 -7\")", 0); + SELFTEST_ASSERT_CHANNEL(1, 123); + + CMD_ExecuteCommand("berry runCmd(\"MQTTHost 192.168.0.213\")", 0); + SELFTEST_ASSERT_STRING("192.168.0.213", CFG_GetMQTTHost()); + CMD_ExecuteCommand("berry runCmd(\"MQTTHost 192.168.0.123\")", 0); + SELFTEST_ASSERT_STRING("192.168.0.123", CFG_GetMQTTHost()); + CMD_ExecuteCommand("berry runCmd(\"MQTTHost 192.168.1.5\")", 0); + SELFTEST_ASSERT_STRING("192.168.1.5", CFG_GetMQTTHost()); + // berry can concat strings + CMD_ExecuteCommand("berry runCmd(\"MQTTHost \" + \"1.2.3.4\")", 0); + SELFTEST_ASSERT_STRING("1.2.3.4", CFG_GetMQTTHost()); +} + +void Test_Berry_MQTTHandler() { + /* + // reset whole device + SIM_ClearOBK(0); + CMD_ExecuteCommand("lfs_format", 0); + + // Create a Berry module file + Test_FakeHTTPClientPacket_POST("api/lfs/test.be", + "test = module('test')\n" + "\n" + "# Add functions to the module\n" + "test.myDataHandler = def(topic, payload)\n" + " printf(\"Got data \" + topic + \" and \" + payload)\n" + "end\n" + "test.init = def (self)\n" + " addBerryHandler(\"onMQTT\",\"test\",\"myDataHandler\")\n" + " return self\n" + "end\n" + "\n" + "return test\n"); + + // Make sure channel starts at 0 + CMD_ExecuteCommand("setChannel 5 0", 0); + SELFTEST_ASSERT_CHANNEL(5, 0); + + // Simulate a device restart by running the autoexec.txt script + CMD_ExecuteCommand("berry import test; test.mySample(2)", 0); + SELFTEST_ASSERT_CHANNEL(5, 2); + CMD_ExecuteCommand("berry import test; test.mySample(2)", 0); + SELFTEST_ASSERT_CHANNEL(5, 4); + CMD_ExecuteCommand("berry import test; test.mySample(2)", 0); + SELFTEST_ASSERT_CHANNEL(5, 6); + CMD_ExecuteCommand("berry test.mySample(2)", 0); + SELFTEST_ASSERT_CHANNEL(5, 8); + */ +} +void Test_Berry() { + Test_Berry_ChannelSet(); + Test_Berry_CancelThread(); + Test_Berry_Import(); + Test_Berry_ThreadCleanup(); + Test_Berry_AutoloadModule(); + Test_Berry_StartScriptShortcut(); + Test_Berry_PassArg(); + Test_Berry_PassArgFromCommand(); + Test_Berry_PassArgFromCommandWithoutModule(); + Test_Berry_PassArgFromCommandWithModule(); + Test_Berry_CommandRunner(); + + Test_Berry_MQTTHandler(); + Test_Berry_Fibonacci(); +} + +#endif diff --git a/src/win_main.c b/src/win_main.c index 8742d1e98..fb26a7980 100644 --- a/src/win_main.c +++ b/src/win_main.c @@ -153,6 +153,9 @@ void SIM_ClearOBK(const char *flashPath) { UART_ResetForSimulator(); CMD_ExecuteCommand("clearAll", 0); CMD_ExecuteCommand("led_expoMode", 0); +#if ENABLE_OBK_BERRY + CMD_ExecuteCommand("stopBerry", 0); +#endif // LOG deinit after main init so commands will be re-added LOG_DeInit(); } @@ -194,6 +197,9 @@ void Win_DoUnitTests() { Test_Commands_Startup(); Test_IF_Inside_Backlog(); Test_WaitFor(); +#if ENABLE_OBK_BERRY + Test_Berry(); +#endif Test_TwoPWMsOneChannel(); Test_ClockEvents(); #if ENABLE_HA_DISCOVERY @@ -553,7 +559,9 @@ int __cdecl main(int argc, char **argv) while (1) { Sleep(DEFAULT_FRAME_TIME); Sim_RunFrame(DEFAULT_FRAME_TIME); - //SIM_RunWindow(); +#if ENABLE_SDL_WINDOW + SIM_RunWindow(); +#endif } } else { @@ -564,7 +572,9 @@ int __cdecl main(int argc, char **argv) if (g_delta <= 0) continue; Sim_RunFrame(g_delta); - //SIM_RunWindow(); +#if ENABLE_SDL_WINDOW + SIM_RunWindow(); +#endif prev_time = cur_time; } } @@ -601,4 +611,6 @@ int ota_total_bytes() { -#endif \ No newline at end of file + +#endif +