diff --git a/applications/system/js_app/examples/apps/Scripts/textbox.js b/applications/system/js_app/examples/apps/Scripts/textbox.js index a260e8a21e..bb6c4fc23a 100644 --- a/applications/system/js_app/examples/apps/Scripts/textbox.js +++ b/applications/system/js_app/examples/apps/Scripts/textbox.js @@ -1,11 +1,15 @@ let textbox = require("textbox"); -// Set config before setting text +// You should set config before adding text // Focus (start / end), Font (text / hex) textbox.setConfig("end", "text"); -let text = "Example dynamic updating textbox\n"; -textbox.setText(text); +// Can make sure it's empty before showing, in case of reusing in same script +// (Closing textbox already empties the text, but maybe you added more in a loop for example) +textbox.emptyText(); + +// Add default text +textbox.addText("Example dynamic updating textbox\n"); // Non-blocking, can keep updating text after, can close in JS or in GUI textbox.show(); @@ -13,8 +17,10 @@ textbox.show(); let i = 0; while (textbox.isOpen() && i < 20) { print("console", i++); - text += "textbox " + to_string(i) + "\n"; - textbox.setText(text); + + // Add text to textbox buffer + textbox.addText("textbox " + to_string(i) + "\n"); + delay(500); } diff --git a/applications/system/js_app/examples/apps/Scripts/uart_echo.js b/applications/system/js_app/examples/apps/Scripts/uart_echo.js index 60d44d078d..1cc0d8e62e 100644 --- a/applications/system/js_app/examples/apps/Scripts/uart_echo.js +++ b/applications/system/js_app/examples/apps/Scripts/uart_echo.js @@ -8,4 +8,7 @@ while (1) { let data_view = Uint8Array(rx_data); print("0x" + to_hex_string(data_view[0])); } -} \ No newline at end of file +} + +// There's also serial.end(), so you can serial.setup() again in same script +// You can also use serial.readAny(timeout), will avoid starving your loop with single byte reads diff --git a/applications/system/js_app/modules/js_badusb.c b/applications/system/js_app/modules/js_badusb.c index 349fde7ba2..a380141e84 100644 --- a/applications/system/js_app/modules/js_badusb.c +++ b/applications/system/js_app/modules/js_badusb.c @@ -81,6 +81,7 @@ static void js_badusb_quit_free(JsBadusbInst* badusb) { if(badusb->usb_if_prev) { furi_hal_hid_kb_release_all(); furi_check(furi_hal_usb_set_config(badusb->usb_if_prev, NULL)); + badusb->usb_if_prev = NULL; } if(badusb->hid_cfg) { free(badusb->hid_cfg); diff --git a/applications/system/js_app/modules/js_serial.c b/applications/system/js_app/modules/js_serial.c index 4f1b14f98c..a7d1398956 100644 --- a/applications/system/js_app/modules/js_serial.c +++ b/applications/system/js_app/modules/js_serial.c @@ -1,4 +1,5 @@ #include +#include #include #include "../js_modules.h" #include @@ -88,14 +89,49 @@ static void js_serial_setup(struct mjs* mjs) { return; } - serial->rx_stream = furi_stream_buffer_alloc(RX_BUF_LEN, 1); + expansion_disable(furi_record_open(RECORD_EXPANSION)); + furi_record_close(RECORD_EXPANSION); + serial->serial_handle = furi_hal_serial_control_acquire(serial_id); if(serial->serial_handle) { + serial->rx_stream = furi_stream_buffer_alloc(RX_BUF_LEN, 1); furi_hal_serial_init(serial->serial_handle, baudrate); furi_hal_serial_async_rx_start( serial->serial_handle, js_serial_on_async_rx, serial, false); serial->setup_done = true; + } else { + expansion_enable(furi_record_open(RECORD_EXPANSION)); + furi_record_close(RECORD_EXPANSION); + } +} + +static void js_serial_deinit(JsSerialInst* js_serial) { + if(js_serial->setup_done) { + furi_hal_serial_async_rx_stop(js_serial->serial_handle); + furi_hal_serial_deinit(js_serial->serial_handle); + furi_hal_serial_control_release(js_serial->serial_handle); + js_serial->serial_handle = NULL; + furi_stream_buffer_free(js_serial->rx_stream); + + expansion_enable(furi_record_open(RECORD_EXPANSION)); + furi_record_close(RECORD_EXPANSION); + + js_serial->setup_done = false; + } +} + +static void js_serial_end(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); + furi_assert(serial); + + if(!serial->setup_done) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); + mjs_return(mjs, MJS_UNDEFINED); + return; } + + js_serial_deinit(serial); } static void js_serial_write(struct mjs* mjs) { @@ -345,6 +381,55 @@ static void js_serial_read_bytes(struct mjs* mjs) { free(read_buf); } +static char* js_serial_receive_any(JsSerialInst* serial, size_t* len, uint32_t timeout) { + uint32_t flags = ThreadEventCustomDataRx; + if(furi_stream_buffer_is_empty(serial->rx_stream)) { + flags = js_flags_wait(serial->mjs, ThreadEventCustomDataRx, timeout); + } + if(flags & ThreadEventCustomDataRx) { // New data received + *len = furi_stream_buffer_bytes_available(serial->rx_stream); + if(!*len) return NULL; + char* buf = malloc(*len); + furi_stream_buffer_receive(serial->rx_stream, buf, *len, 0); + return buf; + } + return NULL; +} + +static void js_serial_read_any(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); + furi_assert(serial); + if(!serial->setup_done) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + uint32_t timeout = FuriWaitForever; + + do { + size_t num_args = mjs_nargs(mjs); + if(num_args == 1) { + mjs_val_t timeout_arg = mjs_arg(mjs, 0); + if(!mjs_is_number(timeout_arg)) { + break; + } + timeout = mjs_get_int32(mjs, timeout_arg); + } + } while(0); + + size_t bytes_read = 0; + char* read_buf = js_serial_receive_any(serial, &bytes_read, timeout); + + mjs_val_t return_obj = MJS_UNDEFINED; + if(bytes_read > 0 && read_buf) { + return_obj = mjs_mk_string(mjs, read_buf, bytes_read, true); + } + mjs_return(mjs, return_obj); + free(read_buf); +} + static bool js_serial_expect_parse_string(struct mjs* mjs, mjs_val_t arg, PatternArray_t patterns) { size_t str_len = 0; @@ -578,10 +663,12 @@ static void* js_serial_create(struct mjs* mjs, mjs_val_t* object) { mjs_val_t serial_obj = mjs_mk_object(mjs); mjs_set(mjs, serial_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, js_serial)); mjs_set(mjs, serial_obj, "setup", ~0, MJS_MK_FN(js_serial_setup)); + mjs_set(mjs, serial_obj, "end", ~0, MJS_MK_FN(js_serial_end)); mjs_set(mjs, serial_obj, "write", ~0, MJS_MK_FN(js_serial_write)); mjs_set(mjs, serial_obj, "read", ~0, MJS_MK_FN(js_serial_read)); mjs_set(mjs, serial_obj, "readln", ~0, MJS_MK_FN(js_serial_readln)); mjs_set(mjs, serial_obj, "readBytes", ~0, MJS_MK_FN(js_serial_read_bytes)); + mjs_set(mjs, serial_obj, "readAny", ~0, MJS_MK_FN(js_serial_read_any)); mjs_set(mjs, serial_obj, "expect", ~0, MJS_MK_FN(js_serial_expect)); *object = serial_obj; @@ -590,14 +677,7 @@ static void* js_serial_create(struct mjs* mjs, mjs_val_t* object) { static void js_serial_destroy(void* inst) { JsSerialInst* js_serial = inst; - if(js_serial->setup_done) { - furi_hal_serial_async_rx_stop(js_serial->serial_handle); - furi_hal_serial_deinit(js_serial->serial_handle); - furi_hal_serial_control_release(js_serial->serial_handle); - js_serial->serial_handle = NULL; - } - - furi_stream_buffer_free(js_serial->rx_stream); + js_serial_deinit(js_serial); free(js_serial); } diff --git a/applications/system/js_app/modules/js_textbox.c b/applications/system/js_app/modules/js_textbox.c index 199cd2a0eb..cf4e8dbbfc 100644 --- a/applications/system/js_app/modules/js_textbox.c +++ b/applications/system/js_app/modules/js_textbox.c @@ -7,6 +7,7 @@ typedef struct { TextBox* text_box; ViewDispatcher* view_dispatcher; FuriThread* thread; + FuriString* text; } JsTextboxInst; static JsTextboxInst* get_this_ctx(struct mjs* mjs) { @@ -74,18 +75,37 @@ static void js_textbox_set_config(struct mjs* mjs) { mjs_return(mjs, MJS_UNDEFINED); } -static void js_textbox_set_text(struct mjs* mjs) { +static void js_textbox_add_text(struct mjs* mjs) { JsTextboxInst* textbox = get_this_ctx(mjs); if(!check_arg_count(mjs, 1)) return; mjs_val_t text_arg = mjs_arg(mjs, 0); - const char* text = mjs_get_string(mjs, &text_arg, NULL); + size_t text_len = 0; + const char* text = mjs_get_string(mjs, &text_arg, &text_len); if(!text) { ret_bad_args(mjs, "Text must be a string"); return; } - text_box_set_text(textbox->text_box, text); + size_t new_len = furi_string_size(textbox->text) + text_len; + if(new_len >= 4096) { + furi_string_right(textbox->text, new_len / 2); + } + + furi_string_cat(textbox->text, text); + + text_box_set_text(textbox->text_box, furi_string_get_cstr(textbox->text)); + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_textbox_empty_text(struct mjs* mjs) { + JsTextboxInst* textbox = get_this_ctx(mjs); + if(!check_arg_count(mjs, 0)) return; + + furi_string_reset(textbox->text); + + text_box_set_text(textbox->text_box, furi_string_get_cstr(textbox->text)); mjs_return(mjs, MJS_UNDEFINED); } @@ -109,6 +129,7 @@ static void textbox_deinit(void* context) { furi_record_close(RECORD_GUI); text_box_reset(textbox->text_box); + furi_string_reset(textbox->text); } static void textbox_callback(void* context, uint32_t arg) { @@ -166,11 +187,13 @@ static void* js_textbox_create(struct mjs* mjs, mjs_val_t* object) { mjs_val_t textbox_obj = mjs_mk_object(mjs); mjs_set(mjs, textbox_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, textbox)); mjs_set(mjs, textbox_obj, "setConfig", ~0, MJS_MK_FN(js_textbox_set_config)); - mjs_set(mjs, textbox_obj, "setText", ~0, MJS_MK_FN(js_textbox_set_text)); + mjs_set(mjs, textbox_obj, "addText", ~0, MJS_MK_FN(js_textbox_add_text)); + mjs_set(mjs, textbox_obj, "emptyText", ~0, MJS_MK_FN(js_textbox_empty_text)); mjs_set(mjs, textbox_obj, "isOpen", ~0, MJS_MK_FN(js_textbox_is_open)); mjs_set(mjs, textbox_obj, "show", ~0, MJS_MK_FN(js_textbox_show)); mjs_set(mjs, textbox_obj, "close", ~0, MJS_MK_FN(js_textbox_close)); textbox->text_box = text_box_alloc(); + textbox->text = furi_string_alloc(); *object = textbox_obj; return textbox; } @@ -182,6 +205,7 @@ static void js_textbox_destroy(void* inst) { textbox_deinit(textbox); } text_box_free(textbox->text_box); + furi_string_free(textbox->text); free(textbox); }