Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript: Add I2C support #259

Merged
merged 13 commits into from
Oct 20, 2024
8 changes: 8 additions & 0 deletions applications/system/js_app/application.fam
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// 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.
// 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...");
}

// write the address to read from (we start at address 0x0001)
// 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));
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_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]));
}
}
278 changes: 278 additions & 0 deletions applications/system/js_app/modules/js_i2c.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
#include "../js_modules.h"
#include <furi_hal_i2c.h>

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 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;
}
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(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(tx_buf);
return;
}
tx_buf[i] = byte_val;
}
} 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);
}
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;
}
} else {
ret_bad_args(mjs, "Data must be an array, arraybuf or dataview");
return;
}

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");
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, tx_buf, tx_len, timeout);
furi_hal_i2c_release(&furi_hal_i2c_handle_external);

if(tx_buf_was_allocated) free(tx_buf);
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 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 rx_len = mjs_get_int32(mjs, rx_len_arg);
if(rx_len == 0) {
ret_bad_args(mjs, "Length must not zero");
return;
}
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(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, 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*)rx_buf, rx_len);
}
free(rx_buf);
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 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;
}
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(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(tx_buf);
return;
}
tx_buf[i] = byte_val;
}
} 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);
}
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;
}
} else {
ret_bad_args(mjs, "Data must be an array, arraybuf or dataview");
return;
}

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");
if(tx_buf_was_allocated) free(tx_buf);
return;
}
size_t rx_len = mjs_get_int32(mjs, rx_len_arg);
if(rx_len == 0) {
ret_bad_args(mjs, "Length must not zero");
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");
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);
bool result = furi_hal_i2c_trx(
&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*)rx_buf, rx_len);
}
if(tx_buf_was_allocated) free(tx_buf);
free(rx_buf);
mjs_return(mjs, ret);
}

static void* js_i2c_create(struct mjs* mjs, mjs_val_t* object) {
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));
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 (void*)1;
}

static const JsModuleDescriptor js_i2c_desc = {
"i2c",
js_i2c_create,
NULL,
};

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;
}
Loading