diff --git a/html/images/0.jpg b/html/images/0.jpg index c35a698..a921d59 100644 Binary files a/html/images/0.jpg and b/html/images/0.jpg differ diff --git a/html/index.html b/html/index.html index 7571e07..856b5f8 100644 --- a/html/index.html +++ b/html/index.html @@ -18,6 +18,7 @@ #canvas-box { margin-top: 10px; } button { padding: 0.375rem 0.75rem; border: 1px solid #0d6efd; border-radius: 0.375rem; } + button:disabled { opacity: 0.65; } button.primary { color: #fff; background-color: #0d6efd; } button.primary:hover { color: #fff; border-color: #0b5ed7; background-color: #0b5ed7; } button.secondary { color: #fff; background-color: #6c757d; } @@ -38,8 +39,8 @@ 蓝牙
- - + +
@@ -50,7 +51,7 @@ - +
@@ -59,13 +60,13 @@ 传图
- - + +
- + - +
状态:
@@ -93,7 +94,7 @@ - +
diff --git a/html/js/main.js b/html/js/main.js index 45147ee..bb5c3ab 100644 --- a/html/js/main.js +++ b/html/js/main.js @@ -6,290 +6,319 @@ let reconnectTrys = 0; let canvas; let startTime; -let chunkSize = 38; + +const MAX_PACKET_SIZE = 20; +const EpdCmd = { + SET_PINS: 0x00, + INIT: 0x01, + CLEAR: 0x02, + SEND_CMD: 0x03, + SEND_DATA: 0x04, + DISPLAY: 0x05, + SLEEP: 0x06, + + SET_CONFIG: 0x90, + SYS_RESET: 0x91, + SYS_SLEEP: 0x92, + CFG_ERASE: 0x99, +}; function resetVariables() { - gattServer = null; - epdService = null; - epdCharacteristic = null; - document.getElementById("log").value = ''; + gattServer = null; + epdService = null; + epdCharacteristic = null; + document.getElementById("log").value = ''; } async function handleError(error) { - console.log(error); - resetVariables(); - if (bleDevice == null) - return; - if (reconnectTrys <= 5) { - reconnectTrys++; - await connect(); - } - else { - addLog("连接失败!"); - reconnectTrys = 0; - } + console.log(error); + resetVariables(); + if (bleDevice == null) + return; + if (reconnectTrys <= 5) { + reconnectTrys++; + await connect(); + } + else { + addLog("连接失败!"); + reconnectTrys = 0; + } } -async function sendCommand(cmd) { - if (epdCharacteristic) { - await epdCharacteristic.writeValue(cmd); - } else { - addLog("服务不可用,请检查蓝牙连接"); - } +async function write(cmd, data) { + if (!epdCharacteristic) { + addLog("服务不可用,请检查蓝牙连接"); + return; + } + let payload = [cmd]; + if (data) { + if (typeof data == 'string') data = hex2bytes(data); + if (data instanceof Uint8Array) data = Array.from(data); + payload.push(...data) + }; + if (payload.length > MAX_PACKET_SIZE) { + throw new Error("BLE packet too large!"); + } + addLog(` ${bytes2hex(payload)}`); + await epdCharacteristic.writeValue(Uint8Array.from(payload)); } -async function sendcmd(cmdTXT) { - addLog(` ${cmdTXT}`); - await sendCommand(hexToBytes(cmdTXT)); +async function epdWrite(cmd, data) { + const chunkSize = MAX_PACKET_SIZE - 1; + const count = Math.round(data.length / chunkSize); + let chunkIdx = 0; + + if (typeof data == 'string') data = hex2bytes(data); + + await write(EpdCmd.SEND_CMD, [cmd]); + for (let i = 0; i < data.length; i += chunkSize) { + let currentTime = (new Date().getTime() - startTime) / 1000.0; + setStatus(`命令:0x${cmd.toString(16)}, 数据块: ${chunkIdx+1}/${count+1}, 总用时: ${currentTime}s`); + await write(EpdCmd.SEND_DATA, data.slice(i, i + chunkSize)); + chunkIdx++; + } } async function setDriver() { - const driver = document.getElementById("epddriver").value; - const pins = document.getElementById("epdpins").value; - await sendcmd(`00${pins}`); - await sendcmd(`01${driver}`); + await write(EpdCmd.SET_PINS, document.getElementById("epdpins").value); + await write(EpdCmd.INIT, document.getElementById("epddriver").value); } async function clearscreen() { - if(confirm('确认清除屏幕内容?')) { - await sendcmd("02"); - } + if(confirm('确认清除屏幕内容?')) { + await write(EpdCmd.CLEAR); + } } -async function sendCmWithData(cmd, data){ - const count = Math.round(data.length / chunkSize); - let chunkIdx = 0; - - await sendcmd(`03${cmd}`); - for (let i = 0; i < data.length; i += chunkSize) { - let currentTime = (new Date().getTime() - startTime) / 1000.0; - let chunk = data.substring(i, i + chunkSize); - setStatus(`命令:0x${cmd}, 数据块: ${chunkIdx+1}/${count+1}, 总用时: ${currentTime}s`); - await sendcmd(`04${chunk}`); - chunkIdx++; - } +async function sendcmd() { + const cmdTXT = document.getElementById('cmdTXT').value; + if (cmdTXT == '') return; + const bytes = hex2bytes(cmdTXT); + await write(bytes[0], bytes.length > 1 ? bytes.slice(1) : null); } async function send4GrayLut() { - await sendCmWithData("20", "000A0000000160141400000100140000000100130A010001000000000000000000000000000000000000"); // vcom - await sendCmWithData("21", "400A0000000190141400000110140A000001A01301000001000000000000000000000000000000000000"); // red not use - await sendCmWithData("22", "400A0000000190141400000100140A000001990C01030401000000000000000000000000000000000000"); // bw r - await sendCmWithData("23", "400A0000000190141400000100140A000001990B04040101000000000000000000000000000000000000"); // wb w - await sendCmWithData("24", "800A0000000190141400000120140A000001501301000001000000000000000000000000000000000000"); // bb b - await sendCmWithData("25", "400A0000000190141400000110140A000001A01301000001000000000000000000000000000000000000"); // vcom + await epdWrite(0x20, "000A0000000160141400000100140000000100130A010001000000000000000000000000000000000000"); // vcom + await epdWrite(0x21, "400A0000000190141400000110140A000001A01301000001000000000000000000000000000000000000"); // red not use + await epdWrite(0x22, "400A0000000190141400000100140A000001990C01030401000000000000000000000000000000000000"); // bw r + await epdWrite(0x23, "400A0000000190141400000100140A000001990B04040101000000000000000000000000000000000000"); // wb w + await epdWrite(0x24, "800A0000000190141400000120140A000001501301000001000000000000000000000000000000000000"); // bb b + await epdWrite(0x25, "400A0000000190141400000110140A000001A01301000001000000000000000000000000000000000000"); // vcom } function getImageData(canvas, driver, mode) { - if (mode === '4gray') { - return bytesToHex(canvas2gray(canvas)); - } else { - let data = bytesToHex(canvas2bytes(canvas, 'bw')); - if (driver === '03') { - if (mode.startsWith('bwr')) { - data += bytesToHex(canvas2bytes(canvas, 'red')); - } else { - const count = data.length; - data += 'F'.repeat(count); - } - } - return data; - } + if (mode === '4gray') { + return canvas2gray(canvas); + } else { + let data = canvas2bytes(canvas, 'bw'); + if (driver === '03') { + if (mode.startsWith('bwr')) { + data.push(...canvas2bytes(canvas, 'red')); + } else { + const data1= 'F'.repeat(data.length); + data.push(...data1); + } + } + return data; + } } async function sendimg() { - startTime = new Date().getTime(); - const canvas = document.getElementById("canvas"); - const driver = document.getElementById("epddriver").value; - const mode = document.getElementById('dithering').value; - const imgArray = getImageData(canvas, driver, mode); - const bwArrLen = (canvas.width/8) * canvas.height * 2; + startTime = new Date().getTime(); + const canvas = document.getElementById("canvas"); + const driver = document.getElementById("epddriver").value; + const mode = document.getElementById('dithering').value; + const imgArray = getImageData(canvas, driver, mode); + const bwArrLen = canvas.width * canvas.height / 8; - if (imgArray.length == bwArrLen * 2) { - await sendCmWithData("10", imgArray.slice(0, bwArrLen - 1)); - await sendCmWithData("13", imgArray.slice(bwArrLen)); - } else { - await sendCmWithData(driver === "03" ? "10" : "13", imgArray); - } + if (imgArray.length == bwArrLen * 2) { + await epdWrite(0x10, imgArray.slice(0, bwArrLen)); + await epdWrite(0x13, imgArray.slice(bwArrLen)); + } else { + await epdWrite(driver === "03" ? 0x10 : 0x13, imgArray); + } - if (mode === "4gray") { - await sendcmd("0300"); - await sendcmd("043F"); // Load LUT from register - await send4GrayLut(); - await sendcmd("05"); - await sendcmd("0300"); - await sendcmd("041F"); // Load LUT from OTP - } else { - await sendcmd("05"); - } + if (mode === "4gray") { + await epdWrite(0x00, [0x3F]); // Load LUT from register + await send4GrayLut(); + await write(EpdCmd.DISPLAY); + await epdWrite(0x00, [0x1F]); // Load LUT from OTP + } else { + await write(EpdCmd.DISPLAY); + } - const sendTime = (new Date().getTime() - startTime) / 1000.0; - addLog(`发送完成!耗时: ${sendTime}s`); - setStatus(`发送完成!耗时: ${sendTime}s`); + const sendTime = (new Date().getTime() - startTime) / 1000.0; + addLog(`发送完成!耗时: ${sendTime}s`); + setStatus(`发送完成!耗时: ${sendTime}s`); } function updateButtonStatus() { - const connected = gattServer != null && gattServer.connected; - const status = connected ? null : 'disabled'; - document.getElementById("reconnectbutton").disabled = (gattServer == null || gattServer.connected) ? 'disabled' : null; - document.getElementById("sendcmdbutton").disabled = status; - document.getElementById("clearscreenbutton").disabled = status; - document.getElementById("sendimgbutton").disabled = status; - document.getElementById("setDriverbutton").disabled = status; + const connected = gattServer != null && gattServer.connected; + const status = connected ? null : 'disabled'; + document.getElementById("reconnectbutton").disabled = (gattServer == null || gattServer.connected) ? 'disabled' : null; + document.getElementById("sendcmdbutton").disabled = status; + document.getElementById("clearscreenbutton").disabled = status; + document.getElementById("sendimgbutton").disabled = status; + document.getElementById("setDriverbutton").disabled = status; } function disconnect() { - updateButtonStatus(); - resetVariables(); - addLog('已断开连接.'); - document.getElementById("connectbutton").innerHTML = '连接'; + updateButtonStatus(); + resetVariables(); + addLog('已断开连接.'); + document.getElementById("connectbutton").innerHTML = '连接'; } async function preConnect() { - if (gattServer != null && gattServer.connected) { - if (bleDevice != null && bleDevice.gatt.connected) { - await sendcmd("06"); - bleDevice.gatt.disconnect(); - } - } - else { - connectTrys = 0; - try { - bleDevice = await navigator.bluetooth.requestDevice({ - optionalServices: ['62750001-d828-918d-fb46-b6c11c675aec'], - acceptAllDevices: true - }); - } catch (e) { - if (e.message) addLog(e.message); - return; - } + if (gattServer != null && gattServer.connected) { + if (bleDevice != null && bleDevice.gatt.connected) { + await write(EpdCmd.SLEEP); + bleDevice.gatt.disconnect(); + } + } + else { + connectTrys = 0; + try { + bleDevice = await navigator.bluetooth.requestDevice({ + optionalServices: ['62750001-d828-918d-fb46-b6c11c675aec'], + acceptAllDevices: true + }); + } catch (e) { + if (e.message) addLog(e.message); + return; + } - await bleDevice.addEventListener('gattserverdisconnected', disconnect); - try { - await connect(); - } catch (e) { - await handleError(e); - } - } + await bleDevice.addEventListener('gattserverdisconnected', disconnect); + try { + await connect(); + } catch (e) { + await handleError(e); + } + } } async function reConnect() { - connectTrys = 0; - if (bleDevice != null && bleDevice.gatt.connected) - bleDevice.gatt.disconnect(); - resetVariables(); - addLog("正在重连"); - setTimeout(async function () { await connect(); }, 300); + connectTrys = 0; + if (bleDevice != null && bleDevice.gatt.connected) + bleDevice.gatt.disconnect(); + resetVariables(); + addLog("正在重连"); + setTimeout(async function () { await connect(); }, 300); } async function connect() { - if (epdCharacteristic == null && bleDevice != null) { - addLog("正在连接: " + bleDevice.name); + if (epdCharacteristic == null && bleDevice != null) { + addLog("正在连接: " + bleDevice.name); - gattServer = await bleDevice.gatt.connect(); - addLog(' 找到 GATT Server'); + gattServer = await bleDevice.gatt.connect(); + addLog(' 找到 GATT Server'); - epdService = await gattServer.getPrimaryService('62750001-d828-918d-fb46-b6c11c675aec'); - addLog(' 找到 EPD Service'); + epdService = await gattServer.getPrimaryService('62750001-d828-918d-fb46-b6c11c675aec'); + addLog(' 找到 EPD Service'); - epdCharacteristic = await epdService.getCharacteristic('62750002-d828-918d-fb46-b6c11c675aec'); - addLog(' 找到 Characteristic'); + epdCharacteristic = await epdService.getCharacteristic('62750002-d828-918d-fb46-b6c11c675aec'); + addLog(' 找到 Characteristic'); - await epdCharacteristic.startNotifications(); - epdCharacteristic.addEventListener('characteristicvaluechanged', (event) => { - addLog(` ${bytesToHex(event.target.value.buffer)}`); - document.getElementById("epdpins").value = bytesToHex(event.target.value.buffer.slice(0, 7)); - document.getElementById("epddriver").value = bytesToHex(event.target.value.buffer.slice(7, 8)); - }); + await epdCharacteristic.startNotifications(); + epdCharacteristic.addEventListener('characteristicvaluechanged', (event) => { + addLog(` ${bytes2hex(event.target.value.buffer)}`); + document.getElementById("epdpins").value = bytes2hex(event.target.value.buffer.slice(0, 7)); + document.getElementById("epddriver").value = bytes2hex(event.target.value.buffer.slice(7, 8)); + }); - await sendcmd("01"); + await write(EpdCmd.INIT); - document.getElementById("connectbutton").innerHTML = '断开'; - updateButtonStatus(); - } + document.getElementById("connectbutton").innerHTML = '断开'; + updateButtonStatus(); + } } function setStatus(statusText) { - document.getElementById("status").innerHTML = statusText; + document.getElementById("status").innerHTML = statusText; } function addLog(logTXT) { - const log = document.getElementById("log"); - const now = new Date(); - const time = String(now.getHours()).padStart(2, '0') + ":" + - String(now.getMinutes()).padStart(2, '0') + ":" + - String(now.getSeconds()).padStart(2, '0') + " "; - log.innerHTML += '' + time + '' + logTXT + '
'; - log.scrollTop = log.scrollHeight; - while ((log.innerHTML.match(/
/g) || []).length > 20) { - var logs_br_position = log.innerHTML.search("
"); - log.innerHTML = log.innerHTML.substring(logs_br_position + 4); - log.scrollTop = log.scrollHeight; - } + const log = document.getElementById("log"); + const now = new Date(); + const time = String(now.getHours()).padStart(2, '0') + ":" + + String(now.getMinutes()).padStart(2, '0') + ":" + + String(now.getSeconds()).padStart(2, '0') + " "; + log.innerHTML += '' + time + '' + logTXT + '
'; + log.scrollTop = log.scrollHeight; + while ((log.innerHTML.match(/
/g) || []).length > 20) { + var logs_br_position = log.innerHTML.search("
"); + log.innerHTML = log.innerHTML.substring(logs_br_position + 4); + log.scrollTop = log.scrollHeight; + } } -function hexToBytes(hex) { - for (var bytes = [], c = 0; c < hex.length; c += 2) - bytes.push(parseInt(hex.substr(c, 2), 16)); - return new Uint8Array(bytes); +function clearLog() { + document.getElementById("log").innerHTML = ''; } -function bytesToHex(data) { - return new Uint8Array(data).reduce( - function (memo, i) { - return memo + ("0" + i.toString(16)).slice(-2); - }, ""); +function hex2bytes(hex) { + for (var bytes = [], c = 0; c < hex.length; c += 2) + bytes.push(parseInt(hex.substr(c, 2), 16)); + return new Uint8Array(bytes); +} + +function bytes2hex(data) { + return new Uint8Array(data).reduce( + function (memo, i) { + return memo + ("0" + i.toString(16)).slice(-2); + }, ""); } function intToHex(intIn) { - let stringOut = ("0000" + intIn.toString(16)).substr(-4) - return stringOut.substring(2, 4) + stringOut.substring(0, 2); + let stringOut = ("0000" + intIn.toString(16)).substr(-4) + return stringOut.substring(2, 4) + stringOut.substring(0, 2); } async function update_image () { - const canvas = document.getElementById("canvas"); - const ctx = canvas.getContext("2d"); + const canvas = document.getElementById("canvas"); + const ctx = canvas.getContext("2d"); - let image = new Image();; - const image_file = document.getElementById('image_file'); - if (image_file.files.length > 0) { - const file = image_file.files[0]; - image.src = URL.createObjectURL(file); - } else { - image.src = document.getElementById('demo-img').src; - } + let image = new Image();; + const image_file = document.getElementById('image_file'); + if (image_file.files.length > 0) { + const file = image_file.files[0]; + image.src = URL.createObjectURL(file); + } else { + image.src = document.getElementById('demo-img').src; + } - image.onload = function(event) { - URL.revokeObjectURL(this.src); - ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height); - convert_dithering() - } + image.onload = function(event) { + URL.revokeObjectURL(this.src); + ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height); + convert_dithering() + } } function clear_canvas() { - if(confirm('确认清除画布内容?')) { - const ctx = canvas.getContext("2d"); - ctx.fillStyle = 'white'; - ctx.fillRect(0, 0, canvas.width, canvas.height); - } + if(confirm('确认清除画布内容?')) { + const ctx = canvas.getContext("2d"); + ctx.fillStyle = 'white'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + } } function convert_dithering() { - const ctx = canvas.getContext("2d"); - const mode = document.getElementById('dithering').value; - if (mode.startsWith('bwr')) { - ditheringCanvasByPalette(canvas, bwrPalette, mode); - } else if (mode === '4gray') { - dithering(ctx, canvas.width, canvas.height, 4, "gray"); - } else { - dithering(ctx, canvas.width, canvas.height, parseInt(document.getElementById('threshold').value), mode); - } + const ctx = canvas.getContext("2d"); + const mode = document.getElementById('dithering').value; + if (mode.startsWith('bwr')) { + ditheringCanvasByPalette(canvas, bwrPalette, mode); + } else if (mode === '4gray') { + dithering(ctx, canvas.width, canvas.height, 4, "gray"); + } else { + dithering(ctx, canvas.width, canvas.height, parseInt(document.getElementById('threshold').value), mode); + } } document.body.onload = () => { - canvas = document.getElementById('canvas'); + canvas = document.getElementById('canvas'); - updateButtonStatus(); - update_image(); + updateButtonStatus(); + update_image(); - document.getElementById('dithering').value = 'none'; + document.getElementById('dithering').value = 'none'; } \ No newline at end of file