Skip to content
Open
1 change: 1 addition & 0 deletions README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Changes from recovered fw dir. Not sure if this is latest
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Framework Laptop 16 LED Matrix Input Module Control
# Framework Laptop 16 LED Matrix Input Module Controlh
Comment thread
timoteuszelle marked this conversation as resolved.
Outdated

## The upstream repo is apparently no longer monitored, so this fork will probably never be merged.

The following enhancements are provided:

- Import export capability
- Matrix values are saved as a 39 by 9 byte array
- Persist button
- Continually wakes the matrix when selected, so the display does not turn off
Comment thread
timoteuszelle marked this conversation as resolved.
Outdated

[View it in your browser.](https://ledmatrix.frame.work)

This little web app can directly connect to the Framework Laptop 16 LED matrix
Expand Down
268 changes: 267 additions & 1 deletion app.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const VERSION_CMD = 0x20;

const WIDTH = 9;
const HEIGHT = 34;
const WAKE_LOOP_INTERVAL_MSEC = 50_000

const PATTERNS = [
'Custom',
Expand All @@ -42,15 +43,19 @@ var msbendian = false;
let portLeft = null;
let portRight = null;
let swap = false;
let persist = false

$(function() {
matrix_left = createArray(34, 9);
matrix_right = createArray(34, 9);

updateTableLeft();
updateTableRight();
initOptions();
initSoftwareExportOptions();
startWakeLoop()

for (pattern of PATTERNS) {
for (const pattern of PATTERNS) {
$("#select-left").append(`<option value="${pattern}">${pattern}</option>`);
$("#select-left").on("change", async function() {
if (pattern == 'Custom') return;
Expand All @@ -67,6 +72,8 @@ $(function() {
}
});

// startWakeLoop()
Comment thread
timoteuszelle marked this conversation as resolved.
Outdated

function drawPattern(matrix, pattern, pos) {
for (let col = 0; col < WIDTH; col++) {
for (let row = 0; row < HEIGHT; row++) {
Expand Down Expand Up @@ -144,6 +151,24 @@ function initOptions() {
matrix_left = createArray(matrix_left.length, matrix_left[0].length);
updateTableLeft();
sendToDisplay(true);
});
$('#importLeftBtn').click(function() {
importMatrixLeft();
});
$('#importRightBtn').click(function() {
importMatrixRight();
});
$('#exportLeftBtn').click(function() {
//Export raw data (i.e. a 2D array instead of a 1d array of encoded bits)
exportMatrixLeft(true);
// No need to suport export of encoded file since import can handle either type
// exportMatrixLeft(false)
});
$('#exportRightBtn').click(function() {
//Export raw data (i.e. a 2D array instead of a 1d array of encoded bits)
exportMatrixRight(true);
// No need to suport export of encoded file since import can handle either type
// exportMatrixRight(false)
});
$('#wakeBtn').click(function() {
wake(portLeft, true);
Expand All @@ -152,6 +177,9 @@ function initOptions() {
$('#sleepBtn').click(function() {
wake(portLeft, false);
wake(portRight, false);
});
$('#persistCb').click(function() {
persist = !persist;
});
$('#bootloaderBtn').click(function() {
bootloader(portLeft);
Expand All @@ -178,6 +206,49 @@ function initOptions() {
});
}

function initSoftwareExportOptions() {
$('#exportLeftSoftwareBtn').click(function() {
const grayscale = $('input[name="exportFormat"]:checked').val() !== 'binary';
exportMatrixSoftware(matrix_left, 'left', grayscale);
});

$('#exportRightSoftwareBtn').click(function() {
const grayscale = $('input[name="exportFormat"]:checked').val() !== 'binary';
exportMatrixSoftware(matrix_right, 'right', grayscale);
});
}

function exportMatrixSoftware(matrix, side, grayscale = true) {
const width = matrix[0].length; // 9
const height = matrix.length; // 34

// Fixed column-major: 9 columns x 34 rows
const vals = Array(width).fill(0).map(() => Array(height).fill(0));
for (let col = 0; col < width; col++) {
for (let row = 0; row < height; row++) {
const isLit = matrix[row][col] === 0; // LED on
if (grayscale) {
vals[col][row] = isLit ? 255 : 0;
} else {
vals[col][row] = isLit ? 1 : 0;
}
}
}

const formatStr = grayscale ? 'grayscale' : 'binary';
const filename = `matrix_${side}_${formatStr}_colmajor.json`;

const blob = new Blob([JSON.stringify(vals, null, 2)], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}

async function command(port, id, params) {
const writer = port.writable.getWriter();

Expand Down Expand Up @@ -228,6 +299,7 @@ async function checkFirmwareVersion(port, side) {
reader.releaseLock();
}

//Get matrix values encoded as a 39-byte array
function prepareValsForDrawingLeft() {
const width = matrix_left[0].length;
const height = matrix_left.length;
Expand All @@ -246,6 +318,7 @@ function prepareValsForDrawingLeft() {
return vals;
}

//Get matrix values encoded as a 39-byte array
function prepareValsForDrawingRight() {
const width = matrix_right[0].length;
const height = matrix_right.length;
Expand All @@ -264,6 +337,190 @@ function prepareValsForDrawingRight() {
return vals;
}

//Get matrix values set directly in a 39 x 9 item array
Comment thread
timoteuszelle marked this conversation as resolved.
Outdated
function getRawValsMatrixRight() {
const width = matrix_right[0].length;
const height = matrix_right.length;

let vals = new Array(height)
for (const i in [...Array(height).keys()]) {
vals[i] = Array(width).fill(0)
}

for (let col = 0; col < width; col++) {
for (let row = 0; row < height; row++) {
const cell = matrix_right[row][col];
vals[row][col] = (cell == null || cell == 1) ? 0 : 1
}
}
return vals;
}

//Get matrix values set directly in a 39 x 9 item array
Comment thread
timoteuszelle marked this conversation as resolved.
Outdated
function getRawValsMatrixLeft() {
const width = matrix_left[0].length;
const height = matrix_left.length;

let vals = new Array(height)
for (const i in [...Array(height).keys()]) {
vals[i] = Array(width).fill(0)
}

for (let col = 0; col < width; col++) {
for (let row = 0; row < height; row++) {
const cell = matrix_left[row][col];
vals[row][col] = (cell == null || cell == 1) ? 0 : 1
}
}
return vals;
}

//Set matrix values by decoding a 39-byte array
function setMatrixLeftFromVals(vals) {
const width = matrix_left[0].length;
const height = matrix_left.length;

for (let row = 0; row < height; row++) {
for (let col = 0; col < width; col++) {
const i = col + row * width;
const val = vals[Math.trunc(i/8)]
const bit = (val >> i % 8) & 1;
matrix_left[row][col] = (bit + 1) % 2;
}
}
}

//Set matrix values by decoding a 39-byte array
function setMatrixRightFromVals(vals) {
const width = matrix_right[0].length;
const height = matrix_right.length;

for (let row = 0; row < height; row++) {
for (let col = 0; col < width; col++) {
const i = col + row * width;
const val = vals[Math.trunc(i/8)]
const bit = (val >> i % 8) & 1;
matrix_right[row][col] = (bit + 1) % 2;
}
}
}
Comment thread
timoteuszelle marked this conversation as resolved.

//Set matrix values from a 39 x 9 item array
function setMatrixRightFromRawVals(vals) {
const width = matrix_right[0].length;
const height = matrix_right.length;

for (let row = 0; row < height; row++) {
for (let col = 0; col < width; col++) {
matrix_right[row][col] = !!!vals[row][col]
}
}
}

//Set matrix values from a 39 x 9 item array
function setMatrixLeftFromRawVals(vals) {
const width = matrix_left[0].length;
const height = matrix_left.length;

for (let row = 0; row < height; row++) {
for (let col = 0; col < width; col++) {
matrix_left[row][col] = !!!vals[row][col]
}
}
}

function exportMatrixLeft(raw) {
let vals
if (raw) {
//set vals as a 39 by 9 byte array
vals = getRawValsMatrixLeft()
} else {
//encode vals into a 39-byte array
vals = prepareValsForDrawingLeft();
}
//save json file
const blob = new Blob([JSON.stringify(vals)], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "matrix_left.json";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}

function exportMatrixRight(raw) {
let vals
if (raw) {
//set vals as a 39 by 9 byte array
vals = getRawValsMatrixRight()
} else {
//encode vals into a 39-byte array
vals = prepareValsForDrawingRight();
}
Comment thread
timoteuszelle marked this conversation as resolved.
Outdated
console.log('Exported values')
console.log(vals)
//save json file
const blob = new Blob([JSON.stringify(vals)], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `${raw ? 'matrix_right(raw).json' : 'matrix_right.json'}`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}

function importMatrixLeft() {
const input = document.createElement("input");
input.type = "file";
input.accept = ".json";
input.onchange = async function (event) {
const file = event.target.files[0];
if (!file) return;

const reader = new FileReader();
reader.onload = function (e) {
const vals = JSON.parse(e.target.result);
if (vals[0].length > 1) {
setMatrixLeftFromRawVals(vals)
} else {
setMatrixLeftFromVals(vals);
}
updateMatrix(matrix_left, 'left')
sendToDisplay(true);
$("#select-left").val('Custom');
};
reader.readAsText(file);
};
input.click();
}
function importMatrixRight() {
const input = document.createElement("input");
input.type = "file";
input.accept = ".json";
input.onchange = async function (event) {
const file = event.target.files[0];
if (!file) return;

const reader = new FileReader();
reader.onload = function (e) {
const vals = JSON.parse(e.target.result);
if (vals[0].length > 1) {
setMatrixRightFromRawVals(vals)
} else {
setMatrixRightFromVals(vals);
}
updateMatrix(matrix_right, 'right')
sendToDisplay(true);
$("#select-right").val('Custom');
};
reader.readAsText(file);
};
input.click();
}

async function sendToDisplay(recurse) {
await sendToDisplayLeft(recurse);
Expand Down Expand Up @@ -386,6 +643,15 @@ function createArray(length) {
return arr;
}

function startWakeLoop() {
setInterval(() => {
if (persist) {
wake(portLeft, true);
wake(portRight, true);
}
}, WAKE_LOOP_INTERVAL_MSEC)
}

async function wake(port, wake) {
await sendCommand(port, 0x03, [wake ? 0 : 1]);
}
Expand Down
Loading