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/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 diff --git a/app.js b/app.js index 94c437e..6a05a7c 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,15 +43,18 @@ 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) { + for (const pattern of PATTERNS) { $("#select-left").append(``); $("#select-left").on("change", async function() { if (pattern == 'Custom') return; @@ -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++) { @@ -144,6 +150,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); @@ -152,6 +176,9 @@ function initOptions() { $('#sleepBtn').click(function() { wake(portLeft, false); wake(portRight, false); + }); + $('#persistCb').click(function() { + persist = !persist; }); $('#bootloaderBtn').click(function() { bootloader(portLeft); @@ -228,6 +255,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; @@ -246,6 +274,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; @@ -264,6 +293,190 @@ 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; + + 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] + } + } +} + +//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(); + } + 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); @@ -386,6 +599,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]); } diff --git a/index.html b/index.html index 4c3bfaa..416f631 100644 --- a/index.html +++ b/index.html @@ -70,7 +70,9 @@