JS: Flipboard

This commit is contained in:
Derek Jamison
2024-04-19 18:00:58 -05:00
parent 6b9195fb42
commit 3f74c682b1
35 changed files with 1592 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
let __dirpath = "/ext/apps/Scripts/flipboard";
let loader = load(__dirpath + "/loader_api.js");
// Prompt user for the .fb file to load, this is a FlipBoard script file
let dialog = loader.require("dialog");
let fb = dialog.pickFile(__dirpath, "fb.js");
if (fb === undefined) {
die("No file selected");
}
loader.load("fn", fb);
print("Loaded", loader.fn.title);
// Initialize textbox
let textbox = loader.require("textbox");
textbox.setConfig("end", "text");
textbox.emptyText();
// Initialize access to the Flipboard buttons
let flipboardButton = loader.load("flipboardButton", __dirpath + "/fb_button_api.js");
flipboardButton.init();
// Initialize access to the Flipboard addressable LEDs
let color = loader.load("color", __dirpath + "/color_api.js");
let flipboardLeds = loader.load("flipboardLeds", __dirpath + "/fb_leds_api.js");
flipboardLeds.init(color, [color.green, color.red, color.yellow, color.blue]);
// Initialize the function callback
loader.fn.init(loader);
// Main loop
let buttonNumber = 0;
while (true) {
// Wait for a button press
buttonNumber = flipboardButton.debounceButton(buttonNumber);
// Convert the button press to an array of LEDs to light up
let pressedArray = flipboardButton.buttonNumberToArray(buttonNumber);
// Update the LED brightness.
flipboardLeds.updateLeds(pressedArray);
loader.fn.buttonPressed(buttonNumber, pressedArray);
}

View File

@@ -0,0 +1,56 @@
({
title: "BadUSB Textbox",
primaryAction: function (buttonNumber) {
// Do an action based on the button number.
if (buttonNumber === 1) {
this.api.badusb.println("https://www.youtube.com/@MrDerekJamison/playlists", 10);
} else if (buttonNumber === 2) {
this.api.badusb.print("Flipper Name: ", 10);
this.api.require("flipper");
this.api.badusb.println(this.api.flipper.getName(), 10);
} else if (buttonNumber === 4) {
this.api.badusb.println("I TYPE SLOW!", 250);
} else if (buttonNumber === 8) {
this.api.badusb.altPrintln("This was printed with Alt+Numpad method!");
}
},
init: function (api) {
this.api = api;
// Initialize access to the Flipper Zero speaker
this.api.initSpeaker();
// Initialize access to the BadUSB (virtual keyboard) device
this.api.initBadusb("/ext/badusb/assets/layouts/en-US.kl");
this.initTextbox();
},
initTextbox: function () {
this.api.textbox.addText(this.title + "\n");
this.api.textbox.addText("Press a button.\n");
this.api.textbox.show();
},
updateTextbox: function (buttonNumber, pressedArray) {
let text = "Button " + to_string(buttonNumber) + " pressed: ";
for (let i = 0; i < 4; i++) {
text += pressedArray[i] ? "X" : "_";
}
text += "\n";
this.api.textbox.addText(text);
},
buttonPressed: function (buttonNumber, pressedArray) {
// Redraw the textbox to show the button press.
this.updateTextbox(buttonNumber, pressedArray);
// A button press of 0 means the user released all of the buttons.
if (buttonNumber === 0) {
return;
}
// Play a tone for 100ms when button pressed.
this.api.speaker.play(440 + 100 * buttonNumber, 1.00, 100);
// Perform the primary action.
this.primaryAction(buttonNumber);
}
})

View File

@@ -0,0 +1,96 @@
({
title: "BadUSB Widget",
primaryAction: function (buttonNumber) {
// Do an action based on the button number.
if (buttonNumber === 1) {
this.api.badusb.hold("CTRL", "ALT");
this.api.badusb.press("DELETE");
this.api.badusb.release("CTRL", "ALT");
delay(1000);
this.api.badusb.press("DOWN");
this.api.badusb.press("DOWN");
delay(2000);
this.api.badusb.press("DOWN");
this.api.badusb.press("ENTER");
} else if (buttonNumber === 2) {
// Trigger sticky keys by pressing shift 5 times.
for (let i = 0; i < 5; i++) {
this.api.badusb.press("SHIFT");
}
} else if (buttonNumber === 4) {
this.api.badusb.println("SYMBOL TEST `!@#$%^&*()_+-=[]{};'\\:\"|,./<>?", 10);
} else if (buttonNumber === 8) {
this.api.badusb.altPrintln("Alt+Numpad `!@#$%^&*()_+-=[]{};'\\:\"|,./<>?");
}
},
init: function (api) {
this.api = api;
this.splashScreen();
// Initialize access to the Flipper Zero speaker
this.api.initSpeaker();
// Initialize access to the BadUSB (virtual keyboard) device
this.api.initBadusb("prompt");
this.initWidget();
},
splashScreen: function () {
this.api.require("widget");
this.api.widget.show();
let fxbmFlippy = this.api.widget.loadImageXbm(__dirpath + "/flippy.fxbm");
let splash = [];
splash.push(this.api.widget.addXbm(0, 0, fxbmFlippy));
splash.push(this.api.widget.addText(70, 10, "Secondary", "Be sure"));
splash.push(this.api.widget.addText(70, 20, "Secondary", "to attach"));
splash.push(this.api.widget.addText(70, 30, "Secondary", "FlipBoard."));
splash.push(this.api.widget.addText(70, 44, "Secondary", "Connect USB"));
splash.push(this.api.widget.addText(70, 54, "Secondary", "data cable"));
splash.push(this.api.widget.addText(70, 64, "Secondary", "to PC."));
delay(5000);
for (let i = 0; i < splash.length; i++) {
this.api.widget.remove(splash[i]);
}
},
initWidget: function () {
this.api.widget.addText(25, 15, "Primary", this.title);
this.status = this.api.widget.addText(10, 60, "Secondary", "Press a button!");
this.fxbmUp = this.api.widget.loadImageXbm(__dirpath + "/up.fxbm");
this.fxbmDown = this.api.widget.loadImageXbm(__dirpath + "/down.fxbm");
this.icons = [];
for (let i = 0; i < 4; i++) {
this.icons.push(this.api.widget.addXbm(9 + i * 30, 32, this.fxbmUp));
this.api.widget.addCircle(10 + i * 30 + 3, 36, 10);
}
},
updateWidget: function (buttonNumber, pressedArray) {
for (let i = 0; i < 4; i++) {
this.api.widget.remove(this.icons[i]);
if (pressedArray[i]) {
this.icons[i] = this.api.widget.addXbm(9 + i * 30, 32, this.fxbmDown);
} else {
this.icons[i] = this.api.widget.addXbm(9 + i * 30, 32, this.fxbmUp);
}
}
this.api.widget.remove(this.status);
this.status = this.api.widget.addText(10, 60, "Secondary", "Button " + to_string(buttonNumber));
},
buttonPressed: function (buttonNumber, pressedArray) {
// Redraw the widget to show the button press.
this.updateWidget(buttonNumber, pressedArray);
// A button press of 0 means the user released all of the buttons.
if (buttonNumber === 0) {
return;
}
// Play a tone for 100ms when button pressed.
this.api.speaker.play(440 + 100 * buttonNumber, 1.00, 100);
// Perform the primary action.
this.primaryAction(buttonNumber);
}
})

View File

@@ -0,0 +1,15 @@
({
green: { red: 0x00, green: 0xFF, blue: 0x00 },
red: { red: 0xFF, green: 0x00, blue: 0x00 },
yellow: { red: 0xFF, green: 0x7F, blue: 0x00 },
blue: { red: 0x00, green: 0x00, blue: 0xFF },
default_glow: 0.10,
bright_glow: 0.90,
brightness: function (color, brightness) {
return {
red: color.red * brightness,
green: color.green * brightness,
blue: color.blue * brightness
};
}
})

Binary file not shown.

View File

@@ -0,0 +1,55 @@
({
gpio: require("gpio"),
button_pins: ["PB2", "PB3", "PA4", "PA6"],
repeat: false,
init: function () {
for (let i = 0; i < this.button_pins.length; i++) {
this.gpio.init(this.button_pins[i], "input", "up");
}
},
getButtons: function () {
let n = 0;
for (let i = 0; i < this.button_pins.length; i++) {
let isPressed = !this.gpio.read(this.button_pins[i]);
n += isPressed ? 1 << i : 0;
}
return n;
},
debounceButton: function (button) {
let threshold = 3;
let repeatThreshold = 5;
let debounce = { counter: threshold, button: button & ~16 };
while (true) {
let button = this.getButtons();
if (button !== debounce.button) {
debounce.counter = 0;
debounce.button = button;
continue;
} else {
debounce.counter++;
if (debounce.counter === threshold) {
break;
} else if (debounce.counter < threshold) {
continue;
} else if (debounce.counter > threshold) {
if (this.repeat && debounce.counter > repeatThreshold) {
debounce.button |= 16;
break;
}
delay(1);
continue;
}
}
}
return debounce.button;
},
buttonNumberToArray: function (button_number) {
let binary_array = [];
for (let i = 0; i < this.button_pins.length; i++) {
binary_array.push((button_number >> i) & 1);
}
return binary_array;
}
})

View File

@@ -0,0 +1,28 @@
({
color_api: undefined,
rgbLeds: require("rgbleds"),
led_colors: [],
updateLeds: function (bright) {
let isChanged = false;
for (let i = 0; i < this.led_colors.length; i++) {
let b = bright[i] ? this.color_api.bright_glow : this.color_api.default_glow;
let c = this.color_api.brightness(this.led_colors[i], b); // using global 'color' object.
if (this.rgbLeds.set(i, c) !== c) {
isChanged = true;
}
}
// We always call update, so LEDs can be unplugged and reconnected.
this.rgbLeds.update();
return isChanged;
},
init: function (color_api, colors) {
this.color_api = color_api;
this.led_colors = colors;
this.rgbLeds.setup({ "pin": "PC3", "count": this.led_colors.length, "spec": "WS2812B" });
let state = [];
for (let i = 0; i < this.led_colors.length; i++) {
state.push(false);
}
this.updateLeds(state);
}
})

Binary file not shown.

View File

@@ -0,0 +1,66 @@
({
title: "Infrared Blast",
primaryAction: function (buttonNumber) {
// Do an action based on the button number.
if (buttonNumber === 1) {
this.api.textbox.addText("\nPower");
this.api.infrared.sendProtocol("Samsung32", 0x07, 0x02);
} else if (buttonNumber === 2) {
this.api.textbox.addText("\nVolume Up");
this.api.infrared.sendProtocol("Samsung32", 0x07, 0x07);
} else if (buttonNumber === 4) {
this.api.textbox.addText("\nVolume Down");
this.api.infrared.sendProtocol("Samsung32", 0x07, 0x0B);
} else if (buttonNumber === (2 | 8)) {
this.api.textbox.addText("\nChannel Up");
this.api.infrared.sendProtocol("Samsung32", 0x07, 0x12);
} else if (buttonNumber === (4 | 8)) {
this.api.textbox.addText("\nChannel Down");
this.api.infrared.sendProtocol("Samsung32", 0x07, 0x10);
}
},
init: function (api) {
this.api = api;
// Allow the Flipboard button to repeat action [adding button 16] when held down.
this.api.flipboardButton.repeat = true;
// Initialize access to the Flipper Zero speaker
this.api.initSpeaker();
// Initialize access to the Infrared module
this.api.require("infrared");
this.initTextbox();
},
initTextbox: function () {
this.api.textbox.addText(this.title + "\n");
this.api.textbox.addText("Green: Power.\nRed = Volume +\nYellow = Volume -\nBlue + Red = Channel +\nBlue + Yellow = Channel -");
this.api.textbox.show();
},
buttonPressed: function (buttonNumber, _pressedArray) {
// Ignore button 1 [Power] if repeat (16).
if (buttonNumber === (1 | 16)) {
return;
}
// Remove the repeat flag.
buttonNumber = buttonNumber & ~16;
// A button press of 0 means the user released all of the buttons.
if (buttonNumber === 0) {
return;
}
// Ignore holding button 8
if (buttonNumber === 8) {
return;
}
// Play a tone for 100ms when button pressed.
this.api.speaker.play(440 + 100 * buttonNumber, 1.00, 100);
// Perform the primary action.
this.primaryAction(buttonNumber);
}
})

View File

@@ -0,0 +1,67 @@
({
isDefined: function (name) {
return this[name] !== undefined;
},
require: function (name) {
let lib = undefined;
if (!this.isDefined(name)) {
lib = require(name);
this[name] = lib;
} else {
lib = this[name];
}
return lib;
},
load: function (name, path) {
let lib = undefined;
if (!this.isDefined(name)) {
lib = load(path);
this[name] = lib;
} else {
lib = this[name];
}
return lib;
},
defaultBadusbLayout: "/ext/badusb/assets/layouts/en-US.kl",
initBadusb: function (layout_path) {
// Initialize access to the BadUSB (virtual keyboard) device
if (!this.isDefined("badusb")) {
this.require("badusb");
if (layout_path === undefined) {
layout_path = this.defaultBadusbLayout;
} else if (layout_path.charCodeAt(0) !== 0x2F) { // If not an absolute path, prompt for file.
if (!this.isDefined("dialog")) {
this.require("dialog");
}
layout_path = this.dialog.pickFile("/ext/badusb/assets/layouts", ".kl");
if (layout_path === undefined) {
layout_path = this.defaultBadusbLayout;
}
}
this.badusb.setup({
vid: 0x05ac,
pid: 0x021e,
mfr_name: "Apple",
prod_name: "Keyboard",
layout_path: layout_path
});
}
},
initSpeaker: function () {
// Initialize access to the speaker
if (!this.isDefined("speaker")) {
this.require("speaker");
this.speaker.acquire(1000); // NOTE: it will be released when the script exits.
}
},
initSubghz: function () {
// Initialize access to the Sub-GHz radio
if (!this.isDefined("subghz")) {
this.require("subghz");
this.subghz.setup();
// For some reason subghz impacts our GPIO pins (so reset them).
this.flipboardButton.init();
}
}
})

View File

@@ -0,0 +1,41 @@
({
title: "SubGHz Sender",
primaryAction: function (buttonNumber) {
// Do an action based on the button number.
if (buttonNumber === 1) {
this.api.speaker.start(540, 1.00);
this.api.subghz.transmitFile("/ext/subghz/Light_on.sub");
this.api.speaker.stop();
} else if (buttonNumber === 2) {
this.api.speaker.start(640, 1.00);
this.api.subghz.transmitFile("/ext/subghz/Light_off.sub");
this.api.speaker.stop();
}
},
init: function (api) {
this.api = api;
// Initialize access to the Flipper Zero speaker
this.api.initSpeaker();
// Initialize access to the SubGHz module
this.api.initSubghz();
this.initTextbox();
},
initTextbox: function () {
this.api.textbox.addText(this.title + "\n");
this.api.textbox.addText("Button 1 = Light on.\n");
this.api.textbox.addText("Button 2 = Light off.\n");
this.api.textbox.show();
},
buttonPressed: function (buttonNumber, pressedArray) {
// We only use the first two buttons.
if (buttonNumber !== 1 && buttonNumber !== 2) {
return;
}
// Perform the primary action.
this.primaryAction(buttonNumber);
}
})

Binary file not shown.