From 1a22c5d913332958586dce8b960b6d3f7285c83b Mon Sep 17 00:00:00 2001 From: Derek Jamison Date: Sat, 12 Oct 2024 15:40:21 -0600 Subject: [PATCH 01/11] JavaScript: Add I2C support --- applications/system/js_app/application.fam | 8 + .../examples/apps/Scripts/Examples/i2c.js | 45 ++++ applications/system/js_app/modules/js_i2c.c | 253 ++++++++++++++++++ 3 files changed, 306 insertions(+) create mode 100644 applications/system/js_app/examples/apps/Scripts/Examples/i2c.js create mode 100644 applications/system/js_app/modules/js_i2c.c diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index 8c38b6c0dc..9353119e93 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -155,3 +155,11 @@ App( requires=["js_app"], sources=["modules/js_vgm/*.c", "modules/js_vgm/ICM42688P/*.c"], ) + +App( + appid="js_i2c", + apptype=FlipperAppType.PLUGIN, + entry_point="js_i2c_ep", + requires=["js_app"], + sources=["modules/js_i2c.c"], +) diff --git a/applications/system/js_app/examples/apps/Scripts/Examples/i2c.js b/applications/system/js_app/examples/apps/Scripts/Examples/i2c.js new file mode 100644 index 0000000000..c9f5967654 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/Examples/i2c.js @@ -0,0 +1,45 @@ +// Connect an 24C32N EEPROM to the I2C bus of the board. SDA=pin 15, SCL=pin 16, VCC=pin 9, GND=pin 8. +let i2c = require("i2c"); + +function i2c_find_first_device() { + let addr = -1; + for (let try_addr = 0; try_addr !== 0xff; try_addr++) { + if (i2c.isDeviceReady(try_addr, 5)) { + addr = try_addr; + break; + } + } + return addr; +} + +let addr = i2c_find_first_device(); +if (addr === -1) { + print("I2C device not found"); + print("Please connect a 24C32N EEPROM I2C device to the Flipper Zero."); + print("SDA=pin 15, SCL=pin 16, VCC=pin 9, GND=pin 8."); +} else { + print("I2C device found at address: " + to_hex_string(addr)); + delay(1000); + + // first two bytes are the start address (0x0000) + // the remaining bytes are the data to store. + i2c.write(addr, [0x00, 0x00, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47]); + while (i2c.isDeviceReady(addr, 9) === false) { + print("Waiting for device to be ready..."); + } + + // write the address to read from (we start at address 0x0001) + // read 3 bytes - 0x42, 0x43, 0x44 + let data = i2c.writeRead(addr, [0x00, 0x01], 3, 100); + print("Read bytes: " + to_string(data.length)); + for (let i = 0; i < data.length; i++) { + print("data[" + to_string(i) + "] = " + to_hex_string(data[i])); + } + + // read two more bytes (0x45, 0x46) from current address + data = i2c.read(addr, 2); + print("Read bytes: " + to_string(data.length)); + for (let i = 0; i < data.length; i++) { + print("data[" + to_string(i) + "] = " + to_hex_string(data[i])); + } +} diff --git a/applications/system/js_app/modules/js_i2c.c b/applications/system/js_app/modules/js_i2c.c new file mode 100644 index 0000000000..760f46d5c0 --- /dev/null +++ b/applications/system/js_app/modules/js_i2c.c @@ -0,0 +1,253 @@ +#include "../js_modules.h" +#include + +typedef struct { + uint32_t addr; +} JsI2cInst; + +static void ret_bad_args(struct mjs* mjs, const char* error) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); + mjs_return(mjs, MJS_UNDEFINED); +} + +static bool check_arg_count_range(struct mjs* mjs, size_t min_count, size_t max_count) { + size_t num_args = mjs_nargs(mjs); + if(num_args < min_count || num_args > max_count) { + ret_bad_args(mjs, "Wrong argument count"); + return false; + } + return true; +} + +static void js_i2c_is_device_ready(struct mjs* mjs) { + if(!check_arg_count_range(mjs, 1,2)) return; + + mjs_val_t addr_arg = mjs_arg(mjs, 0); + if(!mjs_is_number(addr_arg)) { + ret_bad_args(mjs, "Addr must be a number"); + return; + } + uint32_t addr = mjs_get_int32(mjs, addr_arg); + + uint32_t timeout = 1; + if (mjs_nargs(mjs) > 1) { // Timeout is optional argument + mjs_val_t timeout_arg = mjs_arg(mjs, 1); + if(!mjs_is_number(timeout_arg)) { + ret_bad_args(mjs, "Timeout must be a number"); + return; + } + timeout = mjs_get_int32(mjs, timeout_arg); + } + + furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); + bool ready = furi_hal_i2c_is_device_ready(&furi_hal_i2c_handle_external, addr, timeout); + furi_hal_i2c_release(&furi_hal_i2c_handle_external); + + mjs_return(mjs, mjs_mk_boolean(mjs, ready)); +} + +static void js_i2c_write(struct mjs* mjs) { + if(!check_arg_count_range(mjs, 2, 3)) return; + + mjs_val_t addr_arg = mjs_arg(mjs, 0); + if(!mjs_is_number(addr_arg)) { + ret_bad_args(mjs, "Addr must be a number"); + return; + } + uint32_t addr = mjs_get_int32(mjs, addr_arg); + + mjs_val_t data_arg = mjs_arg(mjs, 1); + if(!mjs_is_array(data_arg)) { + ret_bad_args(mjs, "Data must be an array"); + return; + } + size_t data_len = mjs_array_length(mjs, data_arg); + if(data_len == 0) { + ret_bad_args(mjs, "Data array must not be empty"); + return; + } + uint8_t* data = malloc(data_len); + for(size_t i = 0; i < data_len; i++) { + mjs_val_t val = mjs_array_get(mjs, data_arg, i); + if(!mjs_is_number(val)) { + ret_bad_args(mjs, "Data array must contain only numbers"); + free(data); + return; + } + data[i] = mjs_get_int32(mjs, val); + } + + uint32_t timeout = 1; + if (mjs_nargs(mjs) > 2) { // Timeout is optional argument + mjs_val_t timeout_arg = mjs_arg(mjs, 2); + if(!mjs_is_number(timeout_arg)) { + ret_bad_args(mjs, "Timeout must be a number"); + free(data); + return; + } + timeout = mjs_get_int32(mjs, timeout_arg); + } + + furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); + bool result = furi_hal_i2c_tx(&furi_hal_i2c_handle_external, addr, data, data_len, timeout); + furi_hal_i2c_release(&furi_hal_i2c_handle_external); + + free(data); + mjs_return(mjs, mjs_mk_boolean(mjs, result)); +} + +static void js_i2c_read(struct mjs* mjs) { + if(!check_arg_count_range(mjs, 2, 3)) return; + + mjs_val_t addr_arg = mjs_arg(mjs, 0); + if(!mjs_is_number(addr_arg)) { + ret_bad_args(mjs, "Addr must be a number"); + return; + } + uint32_t addr = mjs_get_int32(mjs, addr_arg); + + mjs_val_t length_arg = mjs_arg(mjs, 1); + if(!mjs_is_number(length_arg)) { + ret_bad_args(mjs, "Length must be a number"); + return; + } + size_t len = mjs_get_int32(mjs, length_arg); + if(len == 0) { + ret_bad_args(mjs, "Length must not zero"); + return; + } + + uint8_t* mem_addr = malloc(len); + memset(mem_addr, 0, len); + + uint32_t timeout = 1; + if (mjs_nargs(mjs) > 2) { // Timeout is optional argument + mjs_val_t timeout_arg = mjs_arg(mjs, 2); + if(!mjs_is_number(timeout_arg)) { + ret_bad_args(mjs, "Timeout must be a number"); + free(mem_addr); + return; + } + timeout = mjs_get_int32(mjs, timeout_arg); + } + + furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); + uint8_t* read_buffer = malloc(3); + bool result = furi_hal_i2c_rx(&furi_hal_i2c_handle_external, addr, mem_addr, len, timeout); + furi_hal_i2c_release(&furi_hal_i2c_handle_external); + + mjs_val_t ret = mjs_mk_array(mjs); + if (result) { + for(size_t i = 0; i < len; i++) { + mjs_array_push(mjs, ret, mjs_mk_number(mjs, mem_addr[i])); + } + } + free(mem_addr); + free(read_buffer); + mjs_return(mjs, ret); +} + +static void js_i2c_write_read(struct mjs* mjs) { + if(!check_arg_count_range(mjs, 3, 4)) return; + + mjs_val_t addr_arg = mjs_arg(mjs, 0); + if(!mjs_is_number(addr_arg)) { + ret_bad_args(mjs, "Addr must be a number"); + return; + } + uint32_t addr = mjs_get_int32(mjs, addr_arg); + + mjs_val_t data_arg = mjs_arg(mjs, 1); + if(!mjs_is_array(data_arg)) { + ret_bad_args(mjs, "Data must be an array"); + return; + } + size_t data_len = mjs_array_length(mjs, data_arg); + if(data_len == 0) { + ret_bad_args(mjs, "Data array must not be empty"); + return; + } + uint8_t* data = malloc(data_len); + for(size_t i = 0; i < data_len; i++) { + mjs_val_t val = mjs_array_get(mjs, data_arg, i); + if(!mjs_is_number(val)) { + ret_bad_args(mjs, "Data array must contain only numbers"); + free(data); + return; + } + data[i] = mjs_get_int32(mjs, val); + } + + mjs_val_t length_arg = mjs_arg(mjs, 2); + if(!mjs_is_number(length_arg)) { + ret_bad_args(mjs, "Length must be a number"); + free(data); + return; + } + size_t len = mjs_get_int32(mjs, length_arg); + if(len == 0) { + ret_bad_args(mjs, "Length must not zero"); + free(data); + return; + } + + uint32_t timeout = 1; + if (mjs_nargs(mjs) > 3) { // Timeout is optional argument + mjs_val_t timeout_arg = mjs_arg(mjs, 3); + if(!mjs_is_number(timeout_arg)) { + ret_bad_args(mjs, "Timeout must be a number"); + free(data); + return; + } + timeout = mjs_get_int32(mjs, timeout_arg); + } + + furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); + uint8_t* mem_addr = malloc(len); + memset(mem_addr, 0, len); + bool result = furi_hal_i2c_trx(&furi_hal_i2c_handle_external, addr, data, data_len, mem_addr, len, timeout); + furi_hal_i2c_release(&furi_hal_i2c_handle_external); + + mjs_val_t ret = mjs_mk_array(mjs); + if (result) { + for(size_t i = 0; i < len; i++) { + mjs_array_push(mjs, ret, mjs_mk_number(mjs, mem_addr[i])); + } + } + free(data); + free(mem_addr); + mjs_return(mjs, ret); +} + +static void* js_i2c_create(struct mjs* mjs, mjs_val_t* object) { + JsI2cInst* i2c = malloc(sizeof(JsI2cInst)); + mjs_val_t i2c_obj = mjs_mk_object(mjs); + mjs_set(mjs, i2c_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, i2c)); + mjs_set(mjs, i2c_obj, "isDeviceReady", ~0, MJS_MK_FN(js_i2c_is_device_ready)); + mjs_set(mjs, i2c_obj, "write", ~0, MJS_MK_FN(js_i2c_write)); + mjs_set(mjs, i2c_obj, "read", ~0, MJS_MK_FN(js_i2c_read)); + mjs_set(mjs, i2c_obj, "writeRead", ~0, MJS_MK_FN(js_i2c_write_read)); + *object = i2c_obj; + return i2c; +} + +static void js_i2c_destroy(void* inst) { + JsI2cInst* i2c = inst; + free(i2c); +} + +static const JsModuleDescriptor js_i2c_desc = { + "i2c", + js_i2c_create, + js_i2c_destroy, +}; + +static const FlipperAppPluginDescriptor i2c_plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_i2c_desc, +}; + +const FlipperAppPluginDescriptor* js_i2c_ep(void) { + return &i2c_plugin_descriptor; +} From 6b89fd237644fcff0aae17d5c972f25f6803b17b Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 13 Oct 2024 01:20:24 +0100 Subject: [PATCH 02/11] Format --- applications/system/js_app/modules/js_i2c.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/applications/system/js_app/modules/js_i2c.c b/applications/system/js_app/modules/js_i2c.c index 760f46d5c0..20e90bae59 100644 --- a/applications/system/js_app/modules/js_i2c.c +++ b/applications/system/js_app/modules/js_i2c.c @@ -20,7 +20,7 @@ static bool check_arg_count_range(struct mjs* mjs, size_t min_count, size_t max_ } static void js_i2c_is_device_ready(struct mjs* mjs) { - if(!check_arg_count_range(mjs, 1,2)) return; + if(!check_arg_count_range(mjs, 1, 2)) return; mjs_val_t addr_arg = mjs_arg(mjs, 0); if(!mjs_is_number(addr_arg)) { @@ -30,7 +30,7 @@ static void js_i2c_is_device_ready(struct mjs* mjs) { uint32_t addr = mjs_get_int32(mjs, addr_arg); uint32_t timeout = 1; - if (mjs_nargs(mjs) > 1) { // Timeout is optional argument + if(mjs_nargs(mjs) > 1) { // Timeout is optional argument mjs_val_t timeout_arg = mjs_arg(mjs, 1); if(!mjs_is_number(timeout_arg)) { ret_bad_args(mjs, "Timeout must be a number"); @@ -78,7 +78,7 @@ static void js_i2c_write(struct mjs* mjs) { } uint32_t timeout = 1; - if (mjs_nargs(mjs) > 2) { // Timeout is optional argument + if(mjs_nargs(mjs) > 2) { // Timeout is optional argument mjs_val_t timeout_arg = mjs_arg(mjs, 2); if(!mjs_is_number(timeout_arg)) { ret_bad_args(mjs, "Timeout must be a number"); @@ -121,7 +121,7 @@ static void js_i2c_read(struct mjs* mjs) { memset(mem_addr, 0, len); uint32_t timeout = 1; - if (mjs_nargs(mjs) > 2) { // Timeout is optional argument + if(mjs_nargs(mjs) > 2) { // Timeout is optional argument mjs_val_t timeout_arg = mjs_arg(mjs, 2); if(!mjs_is_number(timeout_arg)) { ret_bad_args(mjs, "Timeout must be a number"); @@ -137,7 +137,7 @@ static void js_i2c_read(struct mjs* mjs) { furi_hal_i2c_release(&furi_hal_i2c_handle_external); mjs_val_t ret = mjs_mk_array(mjs); - if (result) { + if(result) { for(size_t i = 0; i < len; i++) { mjs_array_push(mjs, ret, mjs_mk_number(mjs, mem_addr[i])); } @@ -192,7 +192,7 @@ static void js_i2c_write_read(struct mjs* mjs) { } uint32_t timeout = 1; - if (mjs_nargs(mjs) > 3) { // Timeout is optional argument + if(mjs_nargs(mjs) > 3) { // Timeout is optional argument mjs_val_t timeout_arg = mjs_arg(mjs, 3); if(!mjs_is_number(timeout_arg)) { ret_bad_args(mjs, "Timeout must be a number"); @@ -205,11 +205,12 @@ static void js_i2c_write_read(struct mjs* mjs) { furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); uint8_t* mem_addr = malloc(len); memset(mem_addr, 0, len); - bool result = furi_hal_i2c_trx(&furi_hal_i2c_handle_external, addr, data, data_len, mem_addr, len, timeout); + bool result = furi_hal_i2c_trx( + &furi_hal_i2c_handle_external, addr, data, data_len, mem_addr, len, timeout); furi_hal_i2c_release(&furi_hal_i2c_handle_external); mjs_val_t ret = mjs_mk_array(mjs); - if (result) { + if(result) { for(size_t i = 0; i < len; i++) { mjs_array_push(mjs, ret, mjs_mk_number(mjs, mem_addr[i])); } From 4704c84592401dae2566179c38b579b669db78cb Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 13 Oct 2024 02:37:33 +0100 Subject: [PATCH 03/11] Check input bytes, allow arraybuf write input --- applications/system/js_app/modules/js_i2c.c | 113 ++++++++++++++------ 1 file changed, 79 insertions(+), 34 deletions(-) diff --git a/applications/system/js_app/modules/js_i2c.c b/applications/system/js_app/modules/js_i2c.c index 20e90bae59..196db9bfe9 100644 --- a/applications/system/js_app/modules/js_i2c.c +++ b/applications/system/js_app/modules/js_i2c.c @@ -57,24 +57,44 @@ static void js_i2c_write(struct mjs* mjs) { uint32_t addr = mjs_get_int32(mjs, addr_arg); mjs_val_t data_arg = mjs_arg(mjs, 1); - if(!mjs_is_array(data_arg)) { - ret_bad_args(mjs, "Data must be an array"); - return; - } - size_t data_len = mjs_array_length(mjs, data_arg); - if(data_len == 0) { - ret_bad_args(mjs, "Data array must not be empty"); - return; - } - uint8_t* data = malloc(data_len); - for(size_t i = 0; i < data_len; i++) { - mjs_val_t val = mjs_array_get(mjs, data_arg, i); - if(!mjs_is_number(val)) { - ret_bad_args(mjs, "Data array must contain only numbers"); - free(data); + bool data_was_allocated = false; + uint8_t* data = NULL; + size_t data_len = 0; + if(mjs_is_array(data_arg)) { + data_len = mjs_array_length(mjs, data_arg); + if(data_len == 0) { + ret_bad_args(mjs, "Data array must not be empty"); + return; + } + data = malloc(data_len); + for(size_t i = 0; i < data_len; i++) { + mjs_val_t val = mjs_array_get(mjs, data_arg, i); + if(!mjs_is_number(val)) { + ret_bad_args(mjs, "Data array must contain only numbers"); + free(data); + return; + } + uint32_t byte_val = mjs_get_int32(mjs, val); + if(byte_val > 0xFF) { + ret_bad_args(mjs, "Data array values must be 0-255"); + free(data); + return; + } + data[i] = byte_val; + } + } else if(mjs_is_typed_array(data_arg)) { + mjs_val_t array_buf = data_arg; + if(mjs_is_data_view(data_arg)) { + array_buf = mjs_dataview_get_buf(mjs, data_arg); + } + data = (uint8_t*)mjs_array_buf_get_ptr(mjs, array_buf, &data_len); + if(data_len == 0) { + ret_bad_args(mjs, "Data array must not be empty"); return; } - data[i] = mjs_get_int32(mjs, val); + } else { + ret_bad_args(mjs, "Data must be an array, arraybuf or dataview"); + return; } uint32_t timeout = 1; @@ -92,7 +112,9 @@ static void js_i2c_write(struct mjs* mjs) { bool result = furi_hal_i2c_tx(&furi_hal_i2c_handle_external, addr, data, data_len, timeout); furi_hal_i2c_release(&furi_hal_i2c_handle_external); - free(data); + if(data_was_allocated) { + free(data); + } mjs_return(mjs, mjs_mk_boolean(mjs, result)); } @@ -158,24 +180,45 @@ static void js_i2c_write_read(struct mjs* mjs) { uint32_t addr = mjs_get_int32(mjs, addr_arg); mjs_val_t data_arg = mjs_arg(mjs, 1); - if(!mjs_is_array(data_arg)) { - ret_bad_args(mjs, "Data must be an array"); - return; - } - size_t data_len = mjs_array_length(mjs, data_arg); - if(data_len == 0) { - ret_bad_args(mjs, "Data array must not be empty"); - return; - } - uint8_t* data = malloc(data_len); - for(size_t i = 0; i < data_len; i++) { - mjs_val_t val = mjs_array_get(mjs, data_arg, i); - if(!mjs_is_number(val)) { - ret_bad_args(mjs, "Data array must contain only numbers"); - free(data); + bool data_was_allocated = false; + uint8_t* data = NULL; + size_t data_len = 0; + if(mjs_is_array(data_arg)) { + data_len = mjs_array_length(mjs, data_arg); + if(data_len == 0) { + ret_bad_args(mjs, "Data array must not be empty"); + return; + } + data = malloc(data_len); + data_was_allocated = true; + for(size_t i = 0; i < data_len; i++) { + mjs_val_t val = mjs_array_get(mjs, data_arg, i); + if(!mjs_is_number(val)) { + ret_bad_args(mjs, "Data array must contain only numbers"); + free(data); + return; + } + uint32_t byte_val = mjs_get_int32(mjs, val); + if(byte_val > 0xFF) { + ret_bad_args(mjs, "Data array values must be 0-255"); + free(data); + return; + } + data[i] = byte_val; + } + } else if(mjs_is_typed_array(data_arg)) { + mjs_val_t array_buf = data_arg; + if(mjs_is_data_view(data_arg)) { + array_buf = mjs_dataview_get_buf(mjs, data_arg); + } + data = (uint8_t*)mjs_array_buf_get_ptr(mjs, array_buf, &data_len); + if(data_len == 0) { + ret_bad_args(mjs, "Data array must not be empty"); return; } - data[i] = mjs_get_int32(mjs, val); + } else { + ret_bad_args(mjs, "Data must be an array, arraybuf or dataview"); + return; } mjs_val_t length_arg = mjs_arg(mjs, 2); @@ -215,7 +258,9 @@ static void js_i2c_write_read(struct mjs* mjs) { mjs_array_push(mjs, ret, mjs_mk_number(mjs, mem_addr[i])); } } - free(data); + if(data_was_allocated) { + free(data); + } free(mem_addr); mjs_return(mjs, ret); } From 03bc05861cf7234974c643d43301f17236f074c9 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 13 Oct 2024 02:39:04 +0100 Subject: [PATCH 04/11] Add comment --- applications/system/js_app/examples/apps/Scripts/Examples/i2c.js | 1 + 1 file changed, 1 insertion(+) diff --git a/applications/system/js_app/examples/apps/Scripts/Examples/i2c.js b/applications/system/js_app/examples/apps/Scripts/Examples/i2c.js index c9f5967654..ddfd0afb39 100644 --- a/applications/system/js_app/examples/apps/Scripts/Examples/i2c.js +++ b/applications/system/js_app/examples/apps/Scripts/Examples/i2c.js @@ -23,6 +23,7 @@ if (addr === -1) { // first two bytes are the start address (0x0000) // the remaining bytes are the data to store. + // can also use Uint8Array([0x00, 0x00, ...]) as write parameter i2c.write(addr, [0x00, 0x00, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47]); while (i2c.isDeviceReady(addr, 9) === false) { print("Waiting for device to be ready..."); From 162b3b6f0103325af1e9e0d5b174dbc7ce90ff0f Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 13 Oct 2024 03:43:31 +0100 Subject: [PATCH 05/11] Return arraybuf, remove unnecessary malloc and memset --- .../examples/apps/Scripts/Examples/i2c.js | 6 ++++-- applications/system/js_app/modules/js_i2c.c | 17 ++++------------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/applications/system/js_app/examples/apps/Scripts/Examples/i2c.js b/applications/system/js_app/examples/apps/Scripts/Examples/i2c.js index ddfd0afb39..aa1ef5e1bb 100644 --- a/applications/system/js_app/examples/apps/Scripts/Examples/i2c.js +++ b/applications/system/js_app/examples/apps/Scripts/Examples/i2c.js @@ -31,14 +31,16 @@ if (addr === -1) { // write the address to read from (we start at address 0x0001) // read 3 bytes - 0x42, 0x43, 0x44 - let data = i2c.writeRead(addr, [0x00, 0x01], 3, 100); + let data_buf = i2c.writeRead(addr, [0x00, 0x01], 3, 100); + let data = Uint8Array(data_buf); print("Read bytes: " + to_string(data.length)); for (let i = 0; i < data.length; i++) { print("data[" + to_string(i) + "] = " + to_hex_string(data[i])); } // read two more bytes (0x45, 0x46) from current address - data = i2c.read(addr, 2); + data_buf = i2c.read(addr, 2); + data = Uint8Array(data_buf); print("Read bytes: " + to_string(data.length)); for (let i = 0; i < data.length; i++) { print("data[" + to_string(i) + "] = " + to_hex_string(data[i])); diff --git a/applications/system/js_app/modules/js_i2c.c b/applications/system/js_app/modules/js_i2c.c index 196db9bfe9..17019106d3 100644 --- a/applications/system/js_app/modules/js_i2c.c +++ b/applications/system/js_app/modules/js_i2c.c @@ -138,9 +138,7 @@ static void js_i2c_read(struct mjs* mjs) { ret_bad_args(mjs, "Length must not zero"); return; } - uint8_t* mem_addr = malloc(len); - memset(mem_addr, 0, len); uint32_t timeout = 1; if(mjs_nargs(mjs) > 2) { // Timeout is optional argument @@ -154,18 +152,14 @@ static void js_i2c_read(struct mjs* mjs) { } furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); - uint8_t* read_buffer = malloc(3); bool result = furi_hal_i2c_rx(&furi_hal_i2c_handle_external, addr, mem_addr, len, timeout); furi_hal_i2c_release(&furi_hal_i2c_handle_external); - mjs_val_t ret = mjs_mk_array(mjs); + mjs_val_t ret = MJS_UNDEFINED; if(result) { - for(size_t i = 0; i < len; i++) { - mjs_array_push(mjs, ret, mjs_mk_number(mjs, mem_addr[i])); - } + ret = mjs_mk_array_buf(mjs, (char*)mem_addr, len); } free(mem_addr); - free(read_buffer); mjs_return(mjs, ret); } @@ -247,16 +241,13 @@ static void js_i2c_write_read(struct mjs* mjs) { furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); uint8_t* mem_addr = malloc(len); - memset(mem_addr, 0, len); bool result = furi_hal_i2c_trx( &furi_hal_i2c_handle_external, addr, data, data_len, mem_addr, len, timeout); furi_hal_i2c_release(&furi_hal_i2c_handle_external); - mjs_val_t ret = mjs_mk_array(mjs); + mjs_val_t ret = MJS_UNDEFINED; if(result) { - for(size_t i = 0; i < len; i++) { - mjs_array_push(mjs, ret, mjs_mk_number(mjs, mem_addr[i])); - } + ret = mjs_mk_array_buf(mjs, (char*)mem_addr, len); } if(data_was_allocated) { free(data); From 8fbcc3154da076281371970929a0d0e896714ee0 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 13 Oct 2024 03:55:32 +0100 Subject: [PATCH 06/11] More meaningful var names for code readability, fix some bugs --- applications/system/js_app/modules/js_i2c.c | 132 ++++++++++---------- 1 file changed, 65 insertions(+), 67 deletions(-) diff --git a/applications/system/js_app/modules/js_i2c.c b/applications/system/js_app/modules/js_i2c.c index 17019106d3..6bed49a6f0 100644 --- a/applications/system/js_app/modules/js_i2c.c +++ b/applications/system/js_app/modules/js_i2c.c @@ -56,39 +56,40 @@ static void js_i2c_write(struct mjs* mjs) { } uint32_t addr = mjs_get_int32(mjs, addr_arg); - mjs_val_t data_arg = mjs_arg(mjs, 1); - bool data_was_allocated = false; - uint8_t* data = NULL; - size_t data_len = 0; - if(mjs_is_array(data_arg)) { - data_len = mjs_array_length(mjs, data_arg); - if(data_len == 0) { + mjs_val_t tx_buf_arg = mjs_arg(mjs, 1); + bool tx_buf_was_allocated = false; + uint8_t* tx_buf = NULL; + size_t tx_len = 0; + if(mjs_is_array(tx_buf_arg)) { + tx_len = mjs_array_length(mjs, tx_buf_arg); + if(tx_len == 0) { ret_bad_args(mjs, "Data array must not be empty"); return; } - data = malloc(data_len); - for(size_t i = 0; i < data_len; i++) { - mjs_val_t val = mjs_array_get(mjs, data_arg, i); + tx_buf = malloc(tx_len); + tx_buf_was_allocated = true; + for(size_t i = 0; i < tx_len; i++) { + mjs_val_t val = mjs_array_get(mjs, tx_buf_arg, i); if(!mjs_is_number(val)) { ret_bad_args(mjs, "Data array must contain only numbers"); - free(data); + free(tx_buf); return; } uint32_t byte_val = mjs_get_int32(mjs, val); if(byte_val > 0xFF) { ret_bad_args(mjs, "Data array values must be 0-255"); - free(data); + free(tx_buf); return; } - data[i] = byte_val; + tx_buf[i] = byte_val; } - } else if(mjs_is_typed_array(data_arg)) { - mjs_val_t array_buf = data_arg; - if(mjs_is_data_view(data_arg)) { - array_buf = mjs_dataview_get_buf(mjs, data_arg); + } else if(mjs_is_typed_array(tx_buf_arg)) { + mjs_val_t array_buf = tx_buf_arg; + if(mjs_is_data_view(tx_buf_arg)) { + array_buf = mjs_dataview_get_buf(mjs, tx_buf_arg); } - data = (uint8_t*)mjs_array_buf_get_ptr(mjs, array_buf, &data_len); - if(data_len == 0) { + tx_buf = (uint8_t*)mjs_array_buf_get_ptr(mjs, array_buf, &tx_len); + if(tx_len == 0) { ret_bad_args(mjs, "Data array must not be empty"); return; } @@ -102,19 +103,17 @@ static void js_i2c_write(struct mjs* mjs) { mjs_val_t timeout_arg = mjs_arg(mjs, 2); if(!mjs_is_number(timeout_arg)) { ret_bad_args(mjs, "Timeout must be a number"); - free(data); + if(tx_buf_was_allocated) free(tx_buf); return; } timeout = mjs_get_int32(mjs, timeout_arg); } furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); - bool result = furi_hal_i2c_tx(&furi_hal_i2c_handle_external, addr, data, data_len, timeout); + bool result = furi_hal_i2c_tx(&furi_hal_i2c_handle_external, addr, tx_buf, tx_len, timeout); furi_hal_i2c_release(&furi_hal_i2c_handle_external); - if(data_was_allocated) { - free(data); - } + if(tx_buf_was_allocated) free(tx_buf); mjs_return(mjs, mjs_mk_boolean(mjs, result)); } @@ -128,38 +127,38 @@ static void js_i2c_read(struct mjs* mjs) { } uint32_t addr = mjs_get_int32(mjs, addr_arg); - mjs_val_t length_arg = mjs_arg(mjs, 1); - if(!mjs_is_number(length_arg)) { + mjs_val_t rx_len_arg = mjs_arg(mjs, 1); + if(!mjs_is_number(rx_len_arg)) { ret_bad_args(mjs, "Length must be a number"); return; } - size_t len = mjs_get_int32(mjs, length_arg); - if(len == 0) { + size_t rx_len = mjs_get_int32(mjs, rx_len_arg); + if(rx_len == 0) { ret_bad_args(mjs, "Length must not zero"); return; } - uint8_t* mem_addr = malloc(len); + uint8_t* rx_buf = malloc(rx_len); uint32_t timeout = 1; if(mjs_nargs(mjs) > 2) { // Timeout is optional argument mjs_val_t timeout_arg = mjs_arg(mjs, 2); if(!mjs_is_number(timeout_arg)) { ret_bad_args(mjs, "Timeout must be a number"); - free(mem_addr); + free(rx_buf); return; } timeout = mjs_get_int32(mjs, timeout_arg); } furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); - bool result = furi_hal_i2c_rx(&furi_hal_i2c_handle_external, addr, mem_addr, len, timeout); + bool result = furi_hal_i2c_rx(&furi_hal_i2c_handle_external, addr, rx_buf, rx_len, timeout); furi_hal_i2c_release(&furi_hal_i2c_handle_external); mjs_val_t ret = MJS_UNDEFINED; if(result) { - ret = mjs_mk_array_buf(mjs, (char*)mem_addr, len); + ret = mjs_mk_array_buf(mjs, (char*)rx_buf, rx_len); } - free(mem_addr); + free(rx_buf); mjs_return(mjs, ret); } @@ -173,40 +172,40 @@ static void js_i2c_write_read(struct mjs* mjs) { } uint32_t addr = mjs_get_int32(mjs, addr_arg); - mjs_val_t data_arg = mjs_arg(mjs, 1); - bool data_was_allocated = false; - uint8_t* data = NULL; - size_t data_len = 0; - if(mjs_is_array(data_arg)) { - data_len = mjs_array_length(mjs, data_arg); - if(data_len == 0) { + mjs_val_t tx_buf_arg = mjs_arg(mjs, 1); + bool tx_buf_was_allocated = false; + uint8_t* tx_buf = NULL; + size_t tx_len = 0; + if(mjs_is_array(tx_buf_arg)) { + tx_len = mjs_array_length(mjs, tx_buf_arg); + if(tx_len == 0) { ret_bad_args(mjs, "Data array must not be empty"); return; } - data = malloc(data_len); - data_was_allocated = true; - for(size_t i = 0; i < data_len; i++) { - mjs_val_t val = mjs_array_get(mjs, data_arg, i); + tx_buf = malloc(tx_len); + tx_buf_was_allocated = true; + for(size_t i = 0; i < tx_len; i++) { + mjs_val_t val = mjs_array_get(mjs, tx_buf_arg, i); if(!mjs_is_number(val)) { ret_bad_args(mjs, "Data array must contain only numbers"); - free(data); + free(tx_buf); return; } uint32_t byte_val = mjs_get_int32(mjs, val); if(byte_val > 0xFF) { ret_bad_args(mjs, "Data array values must be 0-255"); - free(data); + free(tx_buf); return; } - data[i] = byte_val; + tx_buf[i] = byte_val; } - } else if(mjs_is_typed_array(data_arg)) { - mjs_val_t array_buf = data_arg; - if(mjs_is_data_view(data_arg)) { - array_buf = mjs_dataview_get_buf(mjs, data_arg); + } else if(mjs_is_typed_array(tx_buf_arg)) { + mjs_val_t array_buf = tx_buf_arg; + if(mjs_is_data_view(tx_buf_arg)) { + array_buf = mjs_dataview_get_buf(mjs, tx_buf_arg); } - data = (uint8_t*)mjs_array_buf_get_ptr(mjs, array_buf, &data_len); - if(data_len == 0) { + tx_buf = (uint8_t*)mjs_array_buf_get_ptr(mjs, array_buf, &tx_len); + if(tx_len == 0) { ret_bad_args(mjs, "Data array must not be empty"); return; } @@ -215,44 +214,43 @@ static void js_i2c_write_read(struct mjs* mjs) { return; } - mjs_val_t length_arg = mjs_arg(mjs, 2); - if(!mjs_is_number(length_arg)) { + mjs_val_t rx_len_arg = mjs_arg(mjs, 2); + if(!mjs_is_number(rx_len_arg)) { ret_bad_args(mjs, "Length must be a number"); - free(data); + if(tx_buf_was_allocated) free(tx_buf); return; } - size_t len = mjs_get_int32(mjs, length_arg); - if(len == 0) { + size_t rx_len = mjs_get_int32(mjs, rx_len_arg); + if(rx_len == 0) { ret_bad_args(mjs, "Length must not zero"); - free(data); + if(tx_buf_was_allocated) free(tx_buf); return; } + uint8_t* rx_buf = malloc(rx_len); uint32_t timeout = 1; if(mjs_nargs(mjs) > 3) { // Timeout is optional argument mjs_val_t timeout_arg = mjs_arg(mjs, 3); if(!mjs_is_number(timeout_arg)) { ret_bad_args(mjs, "Timeout must be a number"); - free(data); + if(tx_buf_was_allocated) free(tx_buf); + free(rx_buf); return; } timeout = mjs_get_int32(mjs, timeout_arg); } furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); - uint8_t* mem_addr = malloc(len); bool result = furi_hal_i2c_trx( - &furi_hal_i2c_handle_external, addr, data, data_len, mem_addr, len, timeout); + &furi_hal_i2c_handle_external, addr, tx_buf, tx_len, rx_buf, rx_len, timeout); furi_hal_i2c_release(&furi_hal_i2c_handle_external); mjs_val_t ret = MJS_UNDEFINED; if(result) { - ret = mjs_mk_array_buf(mjs, (char*)mem_addr, len); - } - if(data_was_allocated) { - free(data); + ret = mjs_mk_array_buf(mjs, (char*)rx_buf, rx_len); } - free(mem_addr); + if(tx_buf_was_allocated) free(tx_buf); + free(rx_buf); mjs_return(mjs, ret); } From e18cae22e6b9ee733c408a3cd26d464d8216f731 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 13 Oct 2024 04:05:45 +0100 Subject: [PATCH 07/11] Remove unnecessary state --- applications/system/js_app/modules/js_i2c.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/applications/system/js_app/modules/js_i2c.c b/applications/system/js_app/modules/js_i2c.c index 6bed49a6f0..8f5f456796 100644 --- a/applications/system/js_app/modules/js_i2c.c +++ b/applications/system/js_app/modules/js_i2c.c @@ -1,10 +1,6 @@ #include "../js_modules.h" #include -typedef struct { - uint32_t addr; -} JsI2cInst; - static void ret_bad_args(struct mjs* mjs, const char* error) { mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); mjs_return(mjs, MJS_UNDEFINED); @@ -255,26 +251,20 @@ static void js_i2c_write_read(struct mjs* mjs) { } static void* js_i2c_create(struct mjs* mjs, mjs_val_t* object) { - JsI2cInst* i2c = malloc(sizeof(JsI2cInst)); mjs_val_t i2c_obj = mjs_mk_object(mjs); - mjs_set(mjs, i2c_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, i2c)); mjs_set(mjs, i2c_obj, "isDeviceReady", ~0, MJS_MK_FN(js_i2c_is_device_ready)); mjs_set(mjs, i2c_obj, "write", ~0, MJS_MK_FN(js_i2c_write)); mjs_set(mjs, i2c_obj, "read", ~0, MJS_MK_FN(js_i2c_read)); mjs_set(mjs, i2c_obj, "writeRead", ~0, MJS_MK_FN(js_i2c_write_read)); *object = i2c_obj; - return i2c; -} -static void js_i2c_destroy(void* inst) { - JsI2cInst* i2c = inst; - free(i2c); + return (void*)1; } static const JsModuleDescriptor js_i2c_desc = { "i2c", js_i2c_create, - js_i2c_destroy, + NULL, }; static const FlipperAppPluginDescriptor i2c_plugin_descriptor = { From 0b37b193dfdd98538bdb161252940493a1d60af3 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 20 Oct 2024 03:14:47 +0100 Subject: [PATCH 08/11] Fix build --- applications/system/js_app/modules/js_i2c.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/applications/system/js_app/modules/js_i2c.c b/applications/system/js_app/modules/js_i2c.c index 8f5f456796..6f105142cc 100644 --- a/applications/system/js_app/modules/js_i2c.c +++ b/applications/system/js_app/modules/js_i2c.c @@ -250,7 +250,8 @@ static void js_i2c_write_read(struct mjs* mjs) { mjs_return(mjs, ret); } -static void* js_i2c_create(struct mjs* mjs, mjs_val_t* object) { +static void* js_i2c_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); mjs_val_t i2c_obj = mjs_mk_object(mjs); mjs_set(mjs, i2c_obj, "isDeviceReady", ~0, MJS_MK_FN(js_i2c_is_device_ready)); mjs_set(mjs, i2c_obj, "write", ~0, MJS_MK_FN(js_i2c_write)); @@ -265,6 +266,7 @@ static const JsModuleDescriptor js_i2c_desc = { "i2c", js_i2c_create, NULL, + NULL, }; static const FlipperAppPluginDescriptor i2c_plugin_descriptor = { From 852d2b0776489930285be736dd495ecb9d8ac1ca Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 20 Oct 2024 03:16:57 +0100 Subject: [PATCH 09/11] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6179513452..bdeb6e0c76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,8 @@ - Bluray/DVD Universal Remote (#250 by @jaylikesbunda) - Option to "Load from Library File" for Universal Remotes (#255 by @zxkmm) - Updater: New Yappy themed icon while updating (#253 by @the1anonlypr3 & @Kuronons & @nescap) +- JS: + - New `i2c` module (#259 by @jamisonderek) - BadKB: - OFW: Add linux/gnome badusb demo files (by @thomasnemer) - Add older qFlipper install demos for windows and macos (by @DXVVAY & @grugnoymeme) From 7a1e7bc1b2c6d3d4af02e5a4ea7c67225f108d12 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 20 Oct 2024 03:36:52 +0100 Subject: [PATCH 10/11] Add typedefs --- .../system/js_app/types/i2c/index.d.ts | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 applications/system/js_app/types/i2c/index.d.ts diff --git a/applications/system/js_app/types/i2c/index.d.ts b/applications/system/js_app/types/i2c/index.d.ts new file mode 100644 index 0000000000..ea0a640cb5 --- /dev/null +++ b/applications/system/js_app/types/i2c/index.d.ts @@ -0,0 +1,31 @@ +/** + * @brief Check if there is an I2C device ready on the bus + * @param address The device address to check + * @param timeout Timeout in milliseconds + */ +export declare function isDeviceReady(address: number, timeout?: number): boolean; + +/** + * @brief Write data to I2C device and return success status + * @param address The device address to write to + * @param data The data to write to the device + * @param timeout Timeout in milliseconds + */ +export declare function write(address: number, data: number[] | ArrayBuffer, timeout?: number): boolean; + +/** + * @brief Read data from I2C device or return undefined on failure + * @param address The device address to read from + * @param length How many bytes to read + * @param timeout Timeout in milliseconds + */ +export declare function read(address: number, length: number, timeout?: number): ArrayBuffer | undefined; + +/** + * @brief Write data then read from I2C device or return undefined on failure + * @param address The device address to talk to + * @param writeData The data to write to the device + * @param readLength How many bytes to read + * @param timeout Timeout in milliseconds + */ +export declare function writeRead(address: number, writeData: number[] | ArrayBuffer, readLength: number, timeout?: number): ArrayBuffer | undefined; From 5df54cdd03424202e621158cba3f898194114c88 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 20 Oct 2024 03:38:37 +0100 Subject: [PATCH 11/11] toString() updates in example script --- .../js_app/examples/apps/Scripts/Examples/i2c.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/applications/system/js_app/examples/apps/Scripts/Examples/i2c.js b/applications/system/js_app/examples/apps/Scripts/Examples/i2c.js index aa1ef5e1bb..456b44ccda 100644 --- a/applications/system/js_app/examples/apps/Scripts/Examples/i2c.js +++ b/applications/system/js_app/examples/apps/Scripts/Examples/i2c.js @@ -18,7 +18,7 @@ if (addr === -1) { print("Please connect a 24C32N EEPROM I2C device to the Flipper Zero."); print("SDA=pin 15, SCL=pin 16, VCC=pin 9, GND=pin 8."); } else { - print("I2C device found at address: " + to_hex_string(addr)); + print("I2C device found at address: " + addr.toString(16)); delay(1000); // first two bytes are the start address (0x0000) @@ -33,16 +33,16 @@ if (addr === -1) { // read 3 bytes - 0x42, 0x43, 0x44 let data_buf = i2c.writeRead(addr, [0x00, 0x01], 3, 100); let data = Uint8Array(data_buf); - print("Read bytes: " + to_string(data.length)); + print("Read bytes: " + data.length.toString()); for (let i = 0; i < data.length; i++) { - print("data[" + to_string(i) + "] = " + to_hex_string(data[i])); + print("data[" + i.toString() + "] = " + data[i].toString(16)); } // read two more bytes (0x45, 0x46) from current address data_buf = i2c.read(addr, 2); data = Uint8Array(data_buf); - print("Read bytes: " + to_string(data.length)); + print("Read bytes: " + data.length.toString()); for (let i = 0; i < data.length; i++) { - print("data[" + to_string(i) + "] = " + to_hex_string(data[i])); + print("data[" + i.toString() + "] = " + data[i].toString(16)); } }