Merge pull request #957 from LuisMayo/feat/add_ir_js_plugin

feat: add IR capabilities to the JS engine
This commit is contained in:
MMX
2026-02-03 18:25:15 +03:00
committed by GitHub
5 changed files with 226 additions and 0 deletions

View File

@@ -240,6 +240,14 @@ App(
sources=["modules/js_subghz/*.c"],
)
App(
appid="js_infrared",
apptype=FlipperAppType.PLUGIN,
entry_point="js_infrared_ep",
requires=["js_app"],
sources=["modules/js_infrared/*.c"],
)
App(
appid="js_blebeacon",
apptype=FlipperAppType.PLUGIN,

View File

@@ -0,0 +1,47 @@
checkSdkFeatures(["infrared-send"]);
let infrared = require("infrared");
print("Sending Samsung32 signal (lowers volume)...");
infrared.sendSignal("Samsung32", 0x00000007, 0x0000000b);
delay(1000);
print("Sending raw signal... (Fujitsu AC)");
infrared.sendRawSignal(
[
3298, 1571, 442, 368, 442, 367, 443, 1180, 442, 370, 440, 1181, 442, 368,
442, 367, 442, 368, 442, 1180, 443, 1180, 442, 368, 441, 367, 442, 369, 441,
1180, 442, 1180, 443, 368, 442, 369, 441, 368, 442, 368, 442, 368, 442, 368,
441, 368, 441, 368, 442, 368, 442, 368, 442, 367, 442, 368, 442, 366, 444,
1181, 442, 368, 441, 367, 442, 368, 442, 367, 442, 369, 441, 367, 443, 367,
442, 1180, 442, 368, 442, 368, 442, 368, 441, 368, 441, 1180, 442, 1180,
442, 1181, 442, 1181, 441, 1181, 442, 1181, 442, 1181, 442, 1181, 442, 369,
441, 368, 441, 1182, 442, 367, 443, 367, 443, 367, 442, 368, 442, 1181, 441,
369, 441, 369, 441, 369, 441, 1180, 442, 1181, 441, 369, 442, 368, 442, 367,
442, 368, 441, 1181, 441, 1183, 440, 368, 442, 368, 441, 369, 441, 1181,
442, 368, 442, 367, 443, 1181, 442, 368, 442, 369, 441, 368, 441, 369, 440,
368, 442, 367, 443, 367, 442, 367, 442, 367, 442, 368, 442, 369, 441, 369,
441, 368, 442, 369, 441, 368, 441, 367, 443, 368, 442, 367, 442, 369, 441,
369, 441, 368, 441, 367, 442, 367, 442, 368, 442, 368, 441, 368, 442, 368,
442, 368, 441, 367, 442, 367, 442, 368, 442, 368, 441, 368, 442, 369, 441,
368, 441, 367, 442, 368, 441, 368, 442, 368, 442, 368, 442, 368, 442, 367,
442, 1181, 442, 367, 442, 368, 441, 1181, 442, 1182, 441, 1181, 442, 1181,
442, 1181, 441, 368, 442, 368, 442, 369, 441,
],
true,
{ frequency: 38000, dutyCycle: 0.33 },
);
delay(1000);
print(
"Sending raw signal... (Fujitsu AC) with default frequency and duty cycle",
);
infrared.sendRawSignal([
3300, 1596, 416, 362, 448, 363, 446, 1177, 445, 363, 446, 1177, 445, 362, 448,
362, 448, 364, 446, 1178, 444, 1207, 415, 362, 448, 362, 448, 363, 447, 1177,
445, 1177, 446, 362, 448, 362, 447, 362, 447, 362, 448, 363, 447, 362, 447,
363, 447, 363, 447, 363, 446, 363, 446, 362, 447, 362, 447, 363, 446, 1177,
445, 363, 447, 364, 446, 362, 448, 363, 447, 363, 446, 362, 447, 362, 448,
1175, 447, 363, 447, 364, 446, 362, 448, 362, 448, 1176, 446, 362, 448, 362,
448, 363, 446, 362, 448, 362, 448, 363, 447, 1175, 446, 394, 415, 1176, 446,
1178, 444, 1174, 449, 1177, 445, 1180, 443, 1179, 443,
]);
print("Success");

View File

@@ -281,6 +281,7 @@ static const char* extra_features[] = {
"blebeacon",
"i2c",
"spi",
"infrared-send",
"subghz",
"usbdisk",
"vgm",

View File

@@ -0,0 +1,129 @@
#include "../../js_modules.h"
#include <infrared_worker.h>
#include <lib/infrared/signal/infrared_signal.h>
#include <lib/infrared/worker/infrared_transmit.h>
#define TAG "JsMath"
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);
}
void js_send_protocol_signal(struct mjs* mjs) {
size_t num_args = mjs_nargs(mjs);
if(num_args < 3 || num_args > 4) {
ret_bad_args(mjs, "Wrong argument count");
return;
}
if(!mjs_is_string(mjs_arg(mjs, 0)) || !mjs_is_number(mjs_arg(mjs, 1)) ||
!mjs_is_number(mjs_arg(mjs, 2)) || (num_args == 4 && !mjs_is_object(mjs_arg(mjs, 3)))) {
ret_bad_args(mjs, "Wrong argument type");
return;
}
bool repeat = false;
int times = 1;
if(num_args == 4) {
mjs_val_t options_obj = mjs_arg(mjs, 3);
mjs_val_t repeat_val = mjs_get(mjs, options_obj, "repeat", ~0);
if(mjs_is_boolean(repeat_val)) {
repeat = mjs_get_bool(mjs, repeat_val);
} else if(!mjs_is_undefined(repeat_val)) {
ret_bad_args(mjs, "Wrong 'repeat' option type");
return;
}
mjs_val_t times_val = mjs_get(mjs, options_obj, "times", ~0);
if(mjs_is_number(times_val)) {
times = mjs_get_int(mjs, times_val);
} else if(!mjs_is_undefined(times_val)) {
ret_bad_args(mjs, "Wrong 'times' option type");
return;
}
}
InfraredMessage message;
message.repeat = repeat;
mjs_val_t protocol_arg = mjs_arg(mjs, 0);
message.protocol = infrared_get_protocol_by_name(mjs_get_cstring(mjs, &protocol_arg));
message.address = mjs_get_int(mjs, mjs_arg(mjs, 1));
message.command = mjs_get_int(mjs, mjs_arg(mjs, 2));
infrared_send(&message, times);
}
void js_send_raw_signal(struct mjs* mjs) {
size_t num_args = mjs_nargs(mjs);
if(num_args < 1 || num_args > 3) {
ret_bad_args(mjs, "Wrong argument count");
return;
}
if(!mjs_is_array(mjs_arg(mjs, 0)) || (num_args > 1 && !mjs_is_boolean(mjs_arg(mjs, 1))) ||
(num_args > 2 && !mjs_is_object(mjs_arg(mjs, 2)))) {
ret_bad_args(mjs, "Wrong argument type");
return;
}
int array_length = mjs_array_length(mjs, mjs_arg(mjs, 0));
uint32_t timings[array_length];
for(int i = 0; i < array_length; i++) {
mjs_val_t elem = mjs_array_get(mjs, mjs_arg(mjs, 0), i);
if(!mjs_is_number(elem)) {
ret_bad_args(mjs, "Timings array must contain only numbers");
return;
}
timings[i] = mjs_get_int(mjs, elem);
}
bool start_from_mark = true;
if(num_args > 1) {
start_from_mark = mjs_get_bool(mjs, mjs_arg(mjs, 1));
}
if(num_args > 2) {
mjs_val_t options_obj = mjs_arg(mjs, 2);
mjs_val_t frequency_val = mjs_get(mjs, options_obj, "frequency", ~0);
if(!mjs_is_number(frequency_val)) {
ret_bad_args(mjs, "Wrong 'frequency' option type");
return;
}
mjs_val_t duty_val = mjs_get(mjs, options_obj, "dutyCycle", ~0);
if(!mjs_is_number(duty_val)) {
ret_bad_args(mjs, "Wrong 'dutyCycle' option type");
return;
}
uint32_t frequency = mjs_get_int(mjs, frequency_val);
float duty_cycle = mjs_get_double(mjs, duty_val);
infrared_send_raw_ext(timings, array_length, start_from_mark, frequency, duty_cycle);
} else {
infrared_send_raw(timings, array_length, start_from_mark);
}
return;
}
static void* js_infrared_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
UNUSED(modules);
mjs_val_t infrared_object = mjs_mk_object(mjs);
mjs_set(mjs, infrared_object, "sendSignal", ~0, MJS_MK_FN(js_send_protocol_signal));
mjs_set(mjs, infrared_object, "sendRawSignal", ~0, MJS_MK_FN(js_send_raw_signal));
*object = infrared_object;
return (void*)1;
}
static const JsModuleDescriptor js_infrared_desc = {
"infrared",
js_infrared_create,
NULL,
NULL,
};
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = PLUGIN_APP_ID,
.ep_api_version = PLUGIN_API_VERSION,
.entry_point = &js_infrared_desc,
};
const FlipperAppPluginDescriptor* js_infrared_ep(void) {
return &plugin_descriptor;
}

View File

@@ -0,0 +1,41 @@
/**
* Module for using Infrared blaster/receptor
* @version Available with JS feature `infrared-send`
* @module
*/
/**
* Sends an IR signal using a known protocol by Flipper Firmware
* @param address Note that the address expects a number. If you're reading from Flipper's IR files, the address is usually in little-endian hex format. Javascript numbers are defined as big endian by default.
* @param command Note that the command expects a number. If you're reading from Flipper's IR files, the command is usually in little-endian hex format. Javascript numbers are defined as big endian by default.
* @param options Repeat marks the signal as a repeat signal, times indicates how many times to send the signal
*/
export declare function sendSignal(
protocol:
| "NEC"
| "NECext"
| "NEC42"
| "NEC42ext"
| "Samsung32"
| "RC6"
| "RC5"
| "RC5X"
| "SIRC"
| "SIRC15"
| "SIRC20"
| "Kaseikyo"
| "RCA",
address: number,
command: number,
options?: { repeat?: boolean; times?: number },
): void;
/**
* Sends a signal from an unknown protocol
* @param startFromMark defaults to true
*/
export declare function sendRawSignal(
timings: number[],
startFromMark?: boolean,
advancedSettings?: { frequency: number; dutyCycle: number },
): void;