From ba8e89a46287f49b7c55fbb502ce2629ba25b3f8 Mon Sep 17 00:00:00 2001 From: "Leone, Mark A [LGS]" Date: Mon, 26 May 2025 18:47:09 -0400 Subject: [PATCH 01/11] Import and export capability --- app.js | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ index.html | 13 +++++-- 2 files changed, 110 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index 94c437e..eb63c55 100644 --- a/app.js +++ b/app.js @@ -144,6 +144,18 @@ function initOptions() { matrix_left = createArray(matrix_left.length, matrix_left[0].length); updateTableLeft(); sendToDisplay(true); + }); + $('#importLeftBtn').click(function() { + importMatrixLeft(); + }); + $('#imporRighttBtn').click(function() { + importMatrixRight(); + }); + $('#exportLeftBtn').click(function() { + exportMatrixLeft(); + }); + $('#exporRighttBtn').click(function() { + exportMatrixRight(); }); $('#wakeBtn').click(function() { wake(portLeft, true); @@ -264,6 +276,93 @@ function prepareValsForDrawingRight() { return vals; } +function setMatrixLeftFromVals(vals) { + const width = matrix_left[0].length; + const height = matrix_left.length; + + for (let col = 0; col < width; col++) { + for (let row = 0; row < height; row++) { + matrix_left[row][col] = vals.shift(); + } + } + +function setMatrixRightFromVals(vals) { + const width = matrix_right[0].length; + const height = matrix_right.length; + + for (let col = 0; col < width; col++) { + for (let row = 0; row < height; row++) { + matrix_right[row][col] = vals.shift(); + } + } +} + +function exportMatrixLeft() { + const 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() { + const vals = prepareValsForDrawingRight(); + //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_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); + setMatrixLeftFromVals(vals); + updateTableLeft(); + sendToDisplay(true); + }; + 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); + setMatrixRightFromVals(vals); + updateTableRight(); + sendToDisplay(true); + }; + reader.readAsText(file); + }; + input.click(); +} async function sendToDisplay(recurse) { await sendToDisplayLeft(recurse); diff --git a/index.html b/index.html index 4c3bfaa..afd6c2b 100644 --- a/index.html +++ b/index.html @@ -80,8 +80,17 @@

Device Information

- - +
+ + +
+
+ + +
+
+ +

Learn More

From a337c6614540fd8ecad6a88ef3fb58f0e8b3962e Mon Sep 17 00:00:00 2001 From: "Leone, Mark A [LGS]" Date: Wed, 28 May 2025 00:08:53 -0400 Subject: [PATCH 02/11] Import and export --- app.js | 45 +++++++++++++++++++++++++++++++++++---------- index.html | 1 - 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/app.js b/app.js index eb63c55..2ada4da 100644 --- a/app.js +++ b/app.js @@ -148,13 +148,13 @@ function initOptions() { $('#importLeftBtn').click(function() { importMatrixLeft(); }); - $('#imporRighttBtn').click(function() { + $('#importRightBtn').click(function() { importMatrixRight(); }); $('#exportLeftBtn').click(function() { exportMatrixLeft(); }); - $('#exporRighttBtn').click(function() { + $('#exportRightBtn').click(function() { exportMatrixRight(); }); $('#wakeBtn').click(function() { @@ -258,6 +258,22 @@ function prepareValsForDrawingLeft() { return vals; } +function getCellVals(vals) { + const cellVals = [] + for (const val of vals) { + const bVal = val.toString(2).split('').reverse().join(''); + const s = bVal.padEnd(8, "0") + for (const c of s) { + if (cellVals.length < 306) { + cellVals.push(parseInt(c)) + } else { + break; + } + } + } + return cellVals; +} + function prepareValsForDrawingRight() { const width = matrix_right[0].length; const height = matrix_right.length; @@ -279,20 +295,25 @@ function prepareValsForDrawingRight() { function setMatrixLeftFromVals(vals) { const width = matrix_left[0].length; const height = matrix_left.length; + const cellVals = getCellVals(vals); - for (let col = 0; col < width; col++) { - for (let row = 0; row < height; row++) { - matrix_left[row][col] = vals.shift(); + for (let row = 0; row < height; row++) { + for (let col = 0; col < width; col++) { + const val = cellVals.shift() + matrix_left[row][col] = (val + 1) % 2; } } +} function setMatrixRightFromVals(vals) { const width = matrix_right[0].length; const height = matrix_right.length; + const cellVals = getCellVals(vals); - for (let col = 0; col < width; col++) { - for (let row = 0; row < height; row++) { - matrix_right[row][col] = vals.shift(); + for (let row = 0; row < height; row++) { + for (let col = 0; col < width; col++) { + const val = cellVals.shift() + matrix_right[row][col] = (val + 1) % 2; } } } @@ -313,6 +334,8 @@ function exportMatrixLeft() { function exportMatrixRight() { const vals = prepareValsForDrawingRight(); + 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); @@ -337,7 +360,8 @@ function importMatrixLeft() { reader.onload = function (e) { const vals = JSON.parse(e.target.result); setMatrixLeftFromVals(vals); - updateTableLeft(); + // updateTableLeft(); + updateMatrix(matrix_left, 'left') sendToDisplay(true); }; reader.readAsText(file); @@ -356,7 +380,8 @@ function importMatrixRight() { reader.onload = function (e) { const vals = JSON.parse(e.target.result); setMatrixRightFromVals(vals); - updateTableRight(); + // updateTableRight(); + updateMatrix(matrix_right, 'right') sendToDisplay(true); }; reader.readAsText(file); diff --git a/index.html b/index.html index afd6c2b..793df9c 100644 --- a/index.html +++ b/index.html @@ -88,7 +88,6 @@

Device Information

-
From 6829123ec4126b1a002f407dfb46508de23f8039 Mon Sep 17 00:00:00 2001 From: Mark Leone Date: Wed, 28 May 2025 10:58:15 -0400 Subject: [PATCH 03/11] Use bit mask for setting matrix vals on import --- app.js | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/app.js b/app.js index 2ada4da..b1745a0 100644 --- a/app.js +++ b/app.js @@ -258,22 +258,6 @@ function prepareValsForDrawingLeft() { return vals; } -function getCellVals(vals) { - const cellVals = [] - for (const val of vals) { - const bVal = val.toString(2).split('').reverse().join(''); - const s = bVal.padEnd(8, "0") - for (const c of s) { - if (cellVals.length < 306) { - cellVals.push(parseInt(c)) - } else { - break; - } - } - } - return cellVals; -} - function prepareValsForDrawingRight() { const width = matrix_right[0].length; const height = matrix_right.length; @@ -295,12 +279,13 @@ function prepareValsForDrawingRight() { function setMatrixLeftFromVals(vals) { const width = matrix_left[0].length; const height = matrix_left.length; - const cellVals = getCellVals(vals); for (let row = 0; row < height; row++) { for (let col = 0; col < width; col++) { - const val = cellVals.shift() - matrix_left[row][col] = (val + 1) % 2; + 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; } } } @@ -308,12 +293,13 @@ function setMatrixLeftFromVals(vals) { function setMatrixRightFromVals(vals) { const width = matrix_right[0].length; const height = matrix_right.length; - const cellVals = getCellVals(vals); for (let row = 0; row < height; row++) { for (let col = 0; col < width; col++) { - const val = cellVals.shift() - matrix_right[row][col] = (val + 1) % 2; + 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; } } } From b4f86e8f15e42ee3fcda1e3f37457b6405bf97de Mon Sep 17 00:00:00 2001 From: Mark Leone Date: Wed, 28 May 2025 11:11:56 -0400 Subject: [PATCH 04/11] Set select val to custom on import --- app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index b1745a0..e1eed10 100644 --- a/app.js +++ b/app.js @@ -346,9 +346,9 @@ function importMatrixLeft() { reader.onload = function (e) { const vals = JSON.parse(e.target.result); setMatrixLeftFromVals(vals); - // updateTableLeft(); updateMatrix(matrix_left, 'left') sendToDisplay(true); + $("#select-left").val('Custom'); }; reader.readAsText(file); }; @@ -366,9 +366,9 @@ function importMatrixRight() { reader.onload = function (e) { const vals = JSON.parse(e.target.result); setMatrixRightFromVals(vals); - // updateTableRight(); updateMatrix(matrix_right, 'right') sendToDisplay(true); + $("#select-right").val('Custom'); }; reader.readAsText(file); }; From 1d4b791c7582687ae2649fc0b730b985e6f9b79c Mon Sep 17 00:00:00 2001 From: "Leone, Mark A [LGS]" Date: Sat, 31 May 2025 22:32:34 -0400 Subject: [PATCH 05/11] Add persistence option --- app.js | 19 +++++++++++++++++++ index.html | 4 +++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app.js b/app.js index e1eed10..301fc1c 100644 --- a/app.js +++ b/app.js @@ -20,6 +20,7 @@ const VERSION_CMD = 0x20; const WIDTH = 9; const HEIGHT = 34; +const WAKE_LOOP_INTERVAL_MSEC = 50_000 const PATTERNS = [ 'Custom', @@ -42,13 +43,16 @@ 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(); + startWakeLoop() for (pattern of PATTERNS) { $("#select-left").append(``); @@ -67,6 +71,8 @@ $(function() { } }); +// startWakeLoop() + function drawPattern(matrix, pattern, pos) { for (let col = 0; col < WIDTH; col++) { for (let row = 0; row < HEIGHT; row++) { @@ -164,6 +170,9 @@ function initOptions() { $('#sleepBtn').click(function() { wake(portLeft, false); wake(portRight, false); + }); + $('#persistCb').click(function() { + persist = !persist; }); $('#bootloaderBtn').click(function() { bootloader(portLeft); @@ -496,6 +505,16 @@ function createArray(length) { return arr; } +function startWakeLoop() { + setInterval(() => { + if (persist) { + console.log('WAKE') + wake(portLeft, true); + wake(portRight, true); + } + }, WAKE_LOOP_INTERVAL_MSEC) +} + async function wake(port, wake) { await sendCommand(port, 0x03, [wake ? 0 : 1]); } diff --git a/index.html b/index.html index 793df9c..416f631 100644 --- a/index.html +++ b/index.html @@ -70,7 +70,9 @@

Device Information

- +
+ Persist +

From 25fafeac754eb037a0e2fb7429709c4b3e9128bf Mon Sep 17 00:00:00 2001 From: "Leone, Mark A [LGS]" Date: Sat, 9 Aug 2025 16:39:53 -0400 Subject: [PATCH 06/11] Support 2d or 1d arrray for import and export. Provide persist option to keep matrix awake --- README | 1 + app.js | 141 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 137 insertions(+), 5 deletions(-) create mode 100644 README diff --git a/README b/README new file mode 100644 index 0000000..3f14138 --- /dev/null +++ b/README @@ -0,0 +1 @@ +Changes from recovered fw dir. Not sure if this is latest diff --git a/app.js b/app.js index 301fc1c..ac44c8b 100644 --- a/app.js +++ b/app.js @@ -54,7 +54,7 @@ $(function() { initOptions(); startWakeLoop() - for (pattern of PATTERNS) { + for (const pattern of PATTERNS) { $("#select-left").append(``); $("#select-left").on("change", async function() { if (pattern == 'Custom') return; @@ -158,10 +158,20 @@ function initOptions() { importMatrixRight(); }); $('#exportLeftBtn').click(function() { - exportMatrixLeft(); + //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() { +<<<<<<< HEAD exportMatrixRight(); +======= + //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) +>>>>>>> 5918936 (Support 2d or 1d arrray for import and export. Provide persist option to keep matrix awake) }); $('#wakeBtn').click(function() { wake(portLeft, true); @@ -249,6 +259,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; @@ -267,6 +278,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; @@ -285,6 +297,45 @@ function prepareValsForDrawingRight() { return vals; } +//Get matrix values set directly in a 39 x 9 item array +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 +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; @@ -295,10 +346,28 @@ function setMatrixLeftFromVals(vals) { const val = vals[Math.trunc(i/8)] const bit = (val >> i % 8) & 1; matrix_left[row][col] = (bit + 1) % 2; +<<<<<<< HEAD + } + } +} + +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; +======= +>>>>>>> 5918936 (Support 2d or 1d arrray for import and export. Provide persist option to keep matrix awake) } } } +//Set matrix values by decoding a 39-byte array function setMatrixRightFromVals(vals) { const width = matrix_right[0].length; const height = matrix_right.length; @@ -313,8 +382,39 @@ function setMatrixRightFromVals(vals) { } } -function exportMatrixLeft() { - const vals = prepareValsForDrawingLeft(); +//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); @@ -327,8 +427,20 @@ function exportMatrixLeft() { URL.revokeObjectURL(url); } +<<<<<<< HEAD function exportMatrixRight() { const vals = prepareValsForDrawingRight(); +======= +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(); + } +>>>>>>> 5918936 (Support 2d or 1d arrray for import and export. Provide persist option to keep matrix awake) console.log('Exported values') console.log(vals) //save json file @@ -336,7 +448,7 @@ function exportMatrixRight() { const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; - a.download = "matrix_right.json"; + a.download = `${raw ? 'matrix_right(raw).json' : 'matrix_right.json'}`; document.body.appendChild(a); a.click(); document.body.removeChild(a); @@ -354,7 +466,15 @@ function importMatrixLeft() { const reader = new FileReader(); reader.onload = function (e) { const vals = JSON.parse(e.target.result); +<<<<<<< HEAD setMatrixLeftFromVals(vals); +======= + if (vals[0].length > 1) { + setMatrixLeftFromRawVals(vals) + } else { + setMatrixLeftFromVals(vals); + } +>>>>>>> 5918936 (Support 2d or 1d arrray for import and export. Provide persist option to keep matrix awake) updateMatrix(matrix_left, 'left') sendToDisplay(true); $("#select-left").val('Custom'); @@ -374,7 +494,15 @@ function importMatrixRight() { const reader = new FileReader(); reader.onload = function (e) { const vals = JSON.parse(e.target.result); +<<<<<<< HEAD setMatrixRightFromVals(vals); +======= + if (vals[0].length > 1) { + setMatrixRightFromRawVals(vals) + } else { + setMatrixRightFromVals(vals); + } +>>>>>>> 5918936 (Support 2d or 1d arrray for import and export. Provide persist option to keep matrix awake) updateMatrix(matrix_right, 'right') sendToDisplay(true); $("#select-right").val('Custom'); @@ -508,7 +636,10 @@ function createArray(length) { function startWakeLoop() { setInterval(() => { if (persist) { +<<<<<<< HEAD console.log('WAKE') +======= +>>>>>>> 5918936 (Support 2d or 1d arrray for import and export. Provide persist option to keep matrix awake) wake(portLeft, true); wake(portRight, true); } From 87ef0ea755d66d8f1d55502fc935fe8211e04b3b Mon Sep 17 00:00:00 2001 From: Mark Leone Date: Sat, 9 Aug 2025 17:01:30 -0400 Subject: [PATCH 07/11] Update README.md --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b704690..efaae75 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,14 @@ -# Framework Laptop 16 LED Matrix Input Module Control +# Framework Laptop 16 LED Matrix Input Module Controlh +## 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 + [View it in your browser.](https://ledmatrix.frame.work) This little web app can directly connect to the Framework Laptop 16 LED matrix From 4abfd8476fc5e6a072e21d6e918e4e3c97aefe12 Mon Sep 17 00:00:00 2001 From: "Leone, Mark A [LGS]" Date: Sat, 9 Aug 2025 17:07:02 -0400 Subject: [PATCH 08/11] Commit merge changes not saved in editor before previous commit --- app.js | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/app.js b/app.js index ac44c8b..6a05a7c 100644 --- a/app.js +++ b/app.js @@ -164,14 +164,10 @@ function initOptions() { // exportMatrixLeft(false) }); $('#exportRightBtn').click(function() { -<<<<<<< HEAD - exportMatrixRight(); -======= //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) ->>>>>>> 5918936 (Support 2d or 1d arrray for import and export. Provide persist option to keep matrix awake) }); $('#wakeBtn').click(function() { wake(portLeft, true); @@ -346,23 +342,6 @@ function setMatrixLeftFromVals(vals) { const val = vals[Math.trunc(i/8)] const bit = (val >> i % 8) & 1; matrix_left[row][col] = (bit + 1) % 2; -<<<<<<< HEAD - } - } -} - -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; -======= ->>>>>>> 5918936 (Support 2d or 1d arrray for import and export. Provide persist option to keep matrix awake) } } } @@ -427,10 +406,6 @@ function exportMatrixLeft(raw) { URL.revokeObjectURL(url); } -<<<<<<< HEAD -function exportMatrixRight() { - const vals = prepareValsForDrawingRight(); -======= function exportMatrixRight(raw) { let vals if (raw) { @@ -440,7 +415,6 @@ function exportMatrixRight(raw) { //encode vals into a 39-byte array vals = prepareValsForDrawingRight(); } ->>>>>>> 5918936 (Support 2d or 1d arrray for import and export. Provide persist option to keep matrix awake) console.log('Exported values') console.log(vals) //save json file @@ -466,15 +440,11 @@ function importMatrixLeft() { const reader = new FileReader(); reader.onload = function (e) { const vals = JSON.parse(e.target.result); -<<<<<<< HEAD - setMatrixLeftFromVals(vals); -======= if (vals[0].length > 1) { setMatrixLeftFromRawVals(vals) } else { setMatrixLeftFromVals(vals); } ->>>>>>> 5918936 (Support 2d or 1d arrray for import and export. Provide persist option to keep matrix awake) updateMatrix(matrix_left, 'left') sendToDisplay(true); $("#select-left").val('Custom'); @@ -494,15 +464,11 @@ function importMatrixRight() { const reader = new FileReader(); reader.onload = function (e) { const vals = JSON.parse(e.target.result); -<<<<<<< HEAD - setMatrixRightFromVals(vals); -======= if (vals[0].length > 1) { setMatrixRightFromRawVals(vals) } else { setMatrixRightFromVals(vals); } ->>>>>>> 5918936 (Support 2d or 1d arrray for import and export. Provide persist option to keep matrix awake) updateMatrix(matrix_right, 'right') sendToDisplay(true); $("#select-right").val('Custom'); @@ -636,10 +602,6 @@ function createArray(length) { function startWakeLoop() { setInterval(() => { if (persist) { -<<<<<<< HEAD - console.log('WAKE') -======= ->>>>>>> 5918936 (Support 2d or 1d arrray for import and export. Provide persist option to keep matrix awake) wake(portLeft, true); wake(portRight, true); } From 2c10dd7a4112d8ec89e0ce4e216039988e8ac68f Mon Sep 17 00:00:00 2001 From: timoteuszelle Date: Tue, 20 Jan 2026 20:27:11 +0100 Subject: [PATCH 09/11] Add software JSON export on top of hardware import/export --- app.js | 44 ++++++++++++++++++++++++++++++++++++++++++++ index.html | 15 +++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/app.js b/app.js index 6a05a7c..4ea06ee 100644 --- a/app.js +++ b/app.js @@ -52,6 +52,7 @@ $(function() { updateTableLeft(); updateTableRight(); initOptions(); + initSoftwareExportOptions(); startWakeLoop() for (const pattern of PATTERNS) { @@ -205,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(); diff --git a/index.html b/index.html index 416f631..a131f2e 100644 --- a/index.html +++ b/index.html @@ -89,6 +89,21 @@

Device Information

+
+
+

Export for Software

+ +
+ + +
+ +

Layout: fixed column-major (9x34), matching the LED matrix orientation.

+ +
+ + +
From ff70160bf0c395e59e40dd0b8e6395fdc6da464e Mon Sep 17 00:00:00 2001 From: NixOS User Date: Mon, 26 Jan 2026 11:03:25 +0100 Subject: [PATCH 10/11] Address code review feedback - Fix README.md typo (Controlh -> Control) - Move feature documentation to proper Features section - Remove commented-out startWakeLoop() line - Fix comment: '39 x 9' -> '34 x 9' (correct dimensions) - Deduplicate matrix helper functions: - Combine getRawValsMatrixLeft/Right into getRawVals(matrix) - Combine setMatrixLeftFromVals/setMatrixRightFromVals into setMatrixFromVals(matrix, vals) - Combine setMatrixLeftFromRawVals/setMatrixRightFromRawVals into setMatrixFromRawVals(matrix, vals) - Change UI label from 'Persist' to 'Keep Awake' for clarity --- README.md | 20 ++++++------ app.js | 92 +++++++++++++----------------------------------------- index.html | 2 +- 3 files changed, 33 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index efaae75..7b290db 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,5 @@ -# Framework Laptop 16 LED Matrix Input Module Controlh +# Framework Laptop 16 LED Matrix Input Module Control -## 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 - [View it in your browser.](https://ledmatrix.frame.work) This little web app can directly connect to the Framework Laptop 16 LED matrix @@ -26,6 +17,15 @@ Click and drag to draw, CTRL + click to erase. Brightness can also be adjusted using the slider. +## Features + +- Import/export capability for matrix patterns + - Matrix values are saved as a 34 by 9 byte array +- Persist button + - Continually wakes the matrix when selected, so the display does not turn off +- Export for Software + - Export patterns in column-major format (9x34) with binary or grayscale values + ## More Information - [Framework Laptop 16](https://frame.work/products/laptop16-diy-amd-7040) diff --git a/app.js b/app.js index 4ea06ee..85b8309 100644 --- a/app.js +++ b/app.js @@ -72,8 +72,6 @@ $(function() { } }); -// startWakeLoop() - function drawPattern(matrix, pattern, pos) { for (let col = 0; col < WIDTH; col++) { for (let row = 0; row < HEIGHT; row++) { @@ -337,29 +335,10 @@ function prepareValsForDrawingRight() { return vals; } -//Get matrix values set directly in a 39 x 9 item array -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 -function getRawValsMatrixLeft() { - const width = matrix_left[0].length; - const height = matrix_left.length; +//Get matrix values set directly in a 34 x 9 item array +function getRawVals(matrix) { + const width = matrix[0].length; + const height = matrix.length; let vals = new Array(height) for (const i in [...Array(height).keys()]) { @@ -368,7 +347,7 @@ function getRawValsMatrixLeft() { for (let col = 0; col < width; col++) { for (let row = 0; row < height; row++) { - const cell = matrix_left[row][col]; + const cell = matrix[row][col]; vals[row][col] = (cell == null || cell == 1) ? 0 : 1 } } @@ -376,55 +355,28 @@ function getRawValsMatrixLeft() { } //Set matrix values by decoding a 39-byte array -function setMatrixLeftFromVals(vals) { - const width = matrix_left[0].length; - const height = matrix_left.length; +function setMatrixFromVals(matrix, vals) { + const width = matrix[0].length; + const height = matrix.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; - } - } -} - -//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] + matrix[row][col] = (bit + 1) % 2; } } } -//Set matrix values from a 39 x 9 item array -function setMatrixLeftFromRawVals(vals) { - const width = matrix_left[0].length; - const height = matrix_left.length; +//Set matrix values from a 34 x 9 item array +function setMatrixFromRawVals(matrix, vals) { + const width = matrix[0].length; + const height = matrix.length; for (let row = 0; row < height; row++) { for (let col = 0; col < width; col++) { - matrix_left[row][col] = !!!vals[row][col] + matrix[row][col] = !!!vals[row][col] } } } @@ -432,8 +384,8 @@ function setMatrixLeftFromRawVals(vals) { function exportMatrixLeft(raw) { let vals if (raw) { - //set vals as a 39 by 9 byte array - vals = getRawValsMatrixLeft() + //set vals as a 34 by 9 byte array + vals = getRawVals(matrix_left) } else { //encode vals into a 39-byte array vals = prepareValsForDrawingLeft(); @@ -453,8 +405,8 @@ function exportMatrixLeft(raw) { function exportMatrixRight(raw) { let vals if (raw) { - //set vals as a 39 by 9 byte array - vals = getRawValsMatrixRight() + //set vals as a 34 by 9 byte array + vals = getRawVals(matrix_right) } else { //encode vals into a 39-byte array vals = prepareValsForDrawingRight(); @@ -485,9 +437,9 @@ function importMatrixLeft() { reader.onload = function (e) { const vals = JSON.parse(e.target.result); if (vals[0].length > 1) { - setMatrixLeftFromRawVals(vals) + setMatrixFromRawVals(matrix_left, vals) } else { - setMatrixLeftFromVals(vals); + setMatrixFromVals(matrix_left, vals); } updateMatrix(matrix_left, 'left') sendToDisplay(true); @@ -509,9 +461,9 @@ function importMatrixRight() { reader.onload = function (e) { const vals = JSON.parse(e.target.result); if (vals[0].length > 1) { - setMatrixRightFromRawVals(vals) + setMatrixFromRawVals(matrix_right, vals) } else { - setMatrixRightFromVals(vals); + setMatrixFromVals(matrix_right, vals); } updateMatrix(matrix_right, 'right') sendToDisplay(true); diff --git a/index.html b/index.html index a131f2e..af033d7 100644 --- a/index.html +++ b/index.html @@ -71,7 +71,7 @@

Device Information

- Persist + Keep Awake

From 2d9de79310634ac0ae8d75e3a7f410aab012e404 Mon Sep 17 00:00:00 2001 From: NixOS User Date: Mon, 26 Jan 2026 12:45:00 +0100 Subject: [PATCH 11/11] Simplify export functions and clarify value conversion - Replace triple negation with explicit ternary operator - Remove unused raw parameter and dead code path - Simplify export button handlers --- app.js | 40 ++++++++++------------------------------ 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/app.js b/app.js index 85b8309..b951006 100644 --- a/app.js +++ b/app.js @@ -157,16 +157,10 @@ function initOptions() { 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) + exportMatrixLeft(); }); $('#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) + exportMatrixRight(); }); $('#wakeBtn').click(function() { wake(portLeft, true); @@ -376,21 +370,14 @@ function setMatrixFromRawVals(matrix, vals) { for (let row = 0; row < height; row++) { for (let col = 0; col < width; col++) { - matrix[row][col] = !!!vals[row][col] + matrix[row][col] = vals[row][col] ? 0 : 1; } } } -function exportMatrixLeft(raw) { - let vals - if (raw) { - //set vals as a 34 by 9 byte array - vals = getRawVals(matrix_left) - } else { - //encode vals into a 39-byte array - vals = prepareValsForDrawingLeft(); - } - //save json file +function exportMatrixLeft() { + // Export as 34x9 2D array + const vals = getRawVals(matrix_left); const blob = new Blob([JSON.stringify(vals)], { type: "application/json" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); @@ -402,23 +389,16 @@ function exportMatrixLeft(raw) { URL.revokeObjectURL(url); } -function exportMatrixRight(raw) { - let vals - if (raw) { - //set vals as a 34 by 9 byte array - vals = getRawVals(matrix_right) - } else { - //encode vals into a 39-byte array - vals = prepareValsForDrawingRight(); - } +function exportMatrixRight() { + // Export as 34x9 2D array + const vals = getRawVals(matrix_right); 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'}`; + a.download = "matrix_right.json"; document.body.appendChild(a); a.click(); document.body.removeChild(a);