update html

This commit is contained in:
Shuanglei Tao
2024-12-25 14:42:48 +08:00
parent 3d7db302aa
commit e20c8a013e
3 changed files with 244 additions and 214 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 402 KiB

After

Width:  |  Height:  |  Size: 278 KiB

View File

@@ -18,6 +18,7 @@
#canvas-box { margin-top: 10px; } #canvas-box { margin-top: 10px; }
button { padding: 0.375rem 0.75rem; border: 1px solid #0d6efd; border-radius: 0.375rem; } 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 { color: #fff; background-color: #0d6efd; }
button.primary:hover { color: #fff; border-color: #0b5ed7; background-color: #0b5ed7; } button.primary:hover { color: #fff; border-color: #0b5ed7; background-color: #0b5ed7; }
button.secondary { color: #fff; background-color: #6c757d; } button.secondary { color: #fff; background-color: #6c757d; }
@@ -38,8 +39,8 @@
<legend>蓝牙</legend> <legend>蓝牙</legend>
<div style="display: flex; justify-content: space-between;"> <div style="display: flex; justify-content: space-between;">
<div> <div>
<button id="connectbutton" type="button" class="primary" onclick="preConnect();">连接</button> <button id="connectbutton" type="button" class="primary" onclick="preConnect()">连接</button>
<button id="reconnectbutton" type="button" class="secondary" onclick="reConnect();">重连</button> <button id="reconnectbutton" type="button" class="secondary" onclick="reConnect()">重连</button>
</div> </div>
<div> <div>
<label for="epddriver">驱动</label> <label for="epddriver">驱动</label>
@@ -50,7 +51,7 @@
</select> </select>
<label for="epdpins">引脚</label> <label for="epdpins">引脚</label>
<input id="epdpins" type="text" value=""> <input id="epdpins" type="text" value="">
<button id="setDriverbutton" type="button" class="primary" onclick="setDriver();">确认</button> <button id="setDriverbutton" type="button" class="primary" onclick="setDriver()">确认</button>
</div> </div>
</div> </div>
</fieldset> </fieldset>
@@ -59,13 +60,13 @@
<legend>传图</legend> <legend>传图</legend>
<div style="margin-bottom: 10px; display: flex; justify-content: space-between;"> <div style="margin-bottom: 10px; display: flex; justify-content: space-between;">
<div> <div>
<button id="clearcanvasbutton" type="button" class="secondary" onclick="clear_canvas();">清除画布</button> <button id="clearcanvasbutton" type="button" class="secondary" onclick="clear_canvas()">清除画布</button>
<button id="sendimgbutton" type="button" class="primary" class="primary" onclick="sendimg();">发送图片</button> <button id="sendimgbutton" type="button" class="primary" onclick="sendimg()">发送图片</button>
</div> </div>
<div> <div>
<button id="clearscreenbutton" type="button" class="secondary" onclick="clearscreen();">清除屏幕</button> <button id="clearscreenbutton" type="button" class="secondary" onclick="clearscreen()">清除屏幕</button>
<input type="text" id="cmdTXT" value=""> <input type="text" id="cmdTXT" value="">
<button id="sendcmdbutton" type="button" class="primary" onclick="sendcmd(document.getElementById(&quot;cmdTXT&quot;).value);">发送命令</button> <button id="sendcmdbutton" type="button" class="primary" onclick="sendcmd()">发送命令</button>
</div> </div>
</div> </div>
<div style="font-size: 85%; color: #666; margin-bottom: 10px; padding-bottom: 10px; border-bottom: 1px dotted #AAA;"><b>状态:</b><span id="status"></span></div> <div style="font-size: 85%; color: #666; margin-bottom: 10px; padding-bottom: 10px; border-bottom: 1px dotted #AAA;"><b>状态:</b><span id="status"></span></div>
@@ -93,7 +94,7 @@
<label for="threshold">阈值</label> <label for="threshold">阈值</label>
<input type="number" max="255" min="0" value="125" id="threshold" onchange="update_image()"> <input type="number" max="255" min="0" value="125" id="threshold" onchange="update_image()">
</div> </div>
<button type="button" class="secondary" onclick="document.getElementById('log').innerHTML = '';">清空日志</button> <button type="button" class="secondary" onclick="clearLog()">清空日志</button>
</div> </div>
<div style="display: flex; justify-content: space-between;"> <div style="display: flex; justify-content: space-between;">
<canvas id="canvas" width="400" height="300" style="border: black solid 1px;"></canvas> <canvas id="canvas" width="400" height="300" style="border: black solid 1px;"></canvas>

View File

@@ -6,290 +6,319 @@ let reconnectTrys = 0;
let canvas; let canvas;
let startTime; 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() { function resetVariables() {
gattServer = null; gattServer = null;
epdService = null; epdService = null;
epdCharacteristic = null; epdCharacteristic = null;
document.getElementById("log").value = ''; document.getElementById("log").value = '';
} }
async function handleError(error) { async function handleError(error) {
console.log(error); console.log(error);
resetVariables(); resetVariables();
if (bleDevice == null) if (bleDevice == null)
return; return;
if (reconnectTrys <= 5) { if (reconnectTrys <= 5) {
reconnectTrys++; reconnectTrys++;
await connect(); await connect();
} }
else { else {
addLog("连接失败!"); addLog("连接失败!");
reconnectTrys = 0; reconnectTrys = 0;
} }
} }
async function sendCommand(cmd) { async function write(cmd, data) {
if (epdCharacteristic) { if (!epdCharacteristic) {
await epdCharacteristic.writeValue(cmd); addLog("服务不可用,请检查蓝牙连接");
} else { return;
addLog("服务不可用,请检查蓝牙连接"); }
} 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(`<span class="action">⇑</span> ${bytes2hex(payload)}`);
await epdCharacteristic.writeValue(Uint8Array.from(payload));
} }
async function sendcmd(cmdTXT) { async function epdWrite(cmd, data) {
addLog(`<span class="action">⇑</span> ${cmdTXT}`); const chunkSize = MAX_PACKET_SIZE - 1;
await sendCommand(hexToBytes(cmdTXT)); 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() { async function setDriver() {
const driver = document.getElementById("epddriver").value; await write(EpdCmd.SET_PINS, document.getElementById("epdpins").value);
const pins = document.getElementById("epdpins").value; await write(EpdCmd.INIT, document.getElementById("epddriver").value);
await sendcmd(`00${pins}`);
await sendcmd(`01${driver}`);
} }
async function clearscreen() { async function clearscreen() {
if(confirm('确认清除屏幕内容?')) { if(confirm('确认清除屏幕内容?')) {
await sendcmd("02"); await write(EpdCmd.CLEAR);
} }
} }
async function sendCmWithData(cmd, data){ async function sendcmd() {
const count = Math.round(data.length / chunkSize); const cmdTXT = document.getElementById('cmdTXT').value;
let chunkIdx = 0; if (cmdTXT == '') return;
const bytes = hex2bytes(cmdTXT);
await sendcmd(`03${cmd}`); await write(bytes[0], bytes.length > 1 ? bytes.slice(1) : null);
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 send4GrayLut() { async function send4GrayLut() {
await sendCmWithData("20", "000A0000000160141400000100140000000100130A010001000000000000000000000000000000000000"); // vcom await epdWrite(0x20, "000A0000000160141400000100140000000100130A010001000000000000000000000000000000000000"); // vcom
await sendCmWithData("21", "400A0000000190141400000110140A000001A01301000001000000000000000000000000000000000000"); // red not use await epdWrite(0x21, "400A0000000190141400000110140A000001A01301000001000000000000000000000000000000000000"); // red not use
await sendCmWithData("22", "400A0000000190141400000100140A000001990C01030401000000000000000000000000000000000000"); // bw r await epdWrite(0x22, "400A0000000190141400000100140A000001990C01030401000000000000000000000000000000000000"); // bw r
await sendCmWithData("23", "400A0000000190141400000100140A000001990B04040101000000000000000000000000000000000000"); // wb w await epdWrite(0x23, "400A0000000190141400000100140A000001990B04040101000000000000000000000000000000000000"); // wb w
await sendCmWithData("24", "800A0000000190141400000120140A000001501301000001000000000000000000000000000000000000"); // bb b await epdWrite(0x24, "800A0000000190141400000120140A000001501301000001000000000000000000000000000000000000"); // bb b
await sendCmWithData("25", "400A0000000190141400000110140A000001A01301000001000000000000000000000000000000000000"); // vcom await epdWrite(0x25, "400A0000000190141400000110140A000001A01301000001000000000000000000000000000000000000"); // vcom
} }
function getImageData(canvas, driver, mode) { function getImageData(canvas, driver, mode) {
if (mode === '4gray') { if (mode === '4gray') {
return bytesToHex(canvas2gray(canvas)); return canvas2gray(canvas);
} else { } else {
let data = bytesToHex(canvas2bytes(canvas, 'bw')); let data = canvas2bytes(canvas, 'bw');
if (driver === '03') { if (driver === '03') {
if (mode.startsWith('bwr')) { if (mode.startsWith('bwr')) {
data += bytesToHex(canvas2bytes(canvas, 'red')); data.push(...canvas2bytes(canvas, 'red'));
} else { } else {
const count = data.length; const data1= 'F'.repeat(data.length);
data += 'F'.repeat(count); data.push(...data1);
} }
} }
return data; return data;
} }
} }
async function sendimg() { async function sendimg() {
startTime = new Date().getTime(); startTime = new Date().getTime();
const canvas = document.getElementById("canvas"); const canvas = document.getElementById("canvas");
const driver = document.getElementById("epddriver").value; const driver = document.getElementById("epddriver").value;
const mode = document.getElementById('dithering').value; const mode = document.getElementById('dithering').value;
const imgArray = getImageData(canvas, driver, mode); const imgArray = getImageData(canvas, driver, mode);
const bwArrLen = (canvas.width/8) * canvas.height * 2; const bwArrLen = canvas.width * canvas.height / 8;
if (imgArray.length == bwArrLen * 2) { if (imgArray.length == bwArrLen * 2) {
await sendCmWithData("10", imgArray.slice(0, bwArrLen - 1)); await epdWrite(0x10, imgArray.slice(0, bwArrLen));
await sendCmWithData("13", imgArray.slice(bwArrLen)); await epdWrite(0x13, imgArray.slice(bwArrLen));
} else { } else {
await sendCmWithData(driver === "03" ? "10" : "13", imgArray); await epdWrite(driver === "03" ? 0x10 : 0x13, imgArray);
} }
if (mode === "4gray") { if (mode === "4gray") {
await sendcmd("0300"); await epdWrite(0x00, [0x3F]); // Load LUT from register
await sendcmd("043F"); // Load LUT from register await send4GrayLut();
await send4GrayLut(); await write(EpdCmd.DISPLAY);
await sendcmd("05"); await epdWrite(0x00, [0x1F]); // Load LUT from OTP
await sendcmd("0300"); } else {
await sendcmd("041F"); // Load LUT from OTP await write(EpdCmd.DISPLAY);
} else { }
await sendcmd("05");
}
const sendTime = (new Date().getTime() - startTime) / 1000.0; const sendTime = (new Date().getTime() - startTime) / 1000.0;
addLog(`发送完成!耗时: ${sendTime}s`); addLog(`发送完成!耗时: ${sendTime}s`);
setStatus(`发送完成!耗时: ${sendTime}s`); setStatus(`发送完成!耗时: ${sendTime}s`);
} }
function updateButtonStatus() { function updateButtonStatus() {
const connected = gattServer != null && gattServer.connected; const connected = gattServer != null && gattServer.connected;
const status = connected ? null : 'disabled'; const status = connected ? null : 'disabled';
document.getElementById("reconnectbutton").disabled = (gattServer == null || gattServer.connected) ? 'disabled' : null; document.getElementById("reconnectbutton").disabled = (gattServer == null || gattServer.connected) ? 'disabled' : null;
document.getElementById("sendcmdbutton").disabled = status; document.getElementById("sendcmdbutton").disabled = status;
document.getElementById("clearscreenbutton").disabled = status; document.getElementById("clearscreenbutton").disabled = status;
document.getElementById("sendimgbutton").disabled = status; document.getElementById("sendimgbutton").disabled = status;
document.getElementById("setDriverbutton").disabled = status; document.getElementById("setDriverbutton").disabled = status;
} }
function disconnect() { function disconnect() {
updateButtonStatus(); updateButtonStatus();
resetVariables(); resetVariables();
addLog('已断开连接.'); addLog('已断开连接.');
document.getElementById("connectbutton").innerHTML = '连接'; document.getElementById("connectbutton").innerHTML = '连接';
} }
async function preConnect() { async function preConnect() {
if (gattServer != null && gattServer.connected) { if (gattServer != null && gattServer.connected) {
if (bleDevice != null && bleDevice.gatt.connected) { if (bleDevice != null && bleDevice.gatt.connected) {
await sendcmd("06"); await write(EpdCmd.SLEEP);
bleDevice.gatt.disconnect(); bleDevice.gatt.disconnect();
} }
} }
else { else {
connectTrys = 0; connectTrys = 0;
try { try {
bleDevice = await navigator.bluetooth.requestDevice({ bleDevice = await navigator.bluetooth.requestDevice({
optionalServices: ['62750001-d828-918d-fb46-b6c11c675aec'], optionalServices: ['62750001-d828-918d-fb46-b6c11c675aec'],
acceptAllDevices: true acceptAllDevices: true
}); });
} catch (e) { } catch (e) {
if (e.message) addLog(e.message); if (e.message) addLog(e.message);
return; return;
} }
await bleDevice.addEventListener('gattserverdisconnected', disconnect); await bleDevice.addEventListener('gattserverdisconnected', disconnect);
try { try {
await connect(); await connect();
} catch (e) { } catch (e) {
await handleError(e); await handleError(e);
} }
} }
} }
async function reConnect() { async function reConnect() {
connectTrys = 0; connectTrys = 0;
if (bleDevice != null && bleDevice.gatt.connected) if (bleDevice != null && bleDevice.gatt.connected)
bleDevice.gatt.disconnect(); bleDevice.gatt.disconnect();
resetVariables(); resetVariables();
addLog("正在重连"); addLog("正在重连");
setTimeout(async function () { await connect(); }, 300); setTimeout(async function () { await connect(); }, 300);
} }
async function connect() { async function connect() {
if (epdCharacteristic == null && bleDevice != null) { if (epdCharacteristic == null && bleDevice != null) {
addLog("正在连接: " + bleDevice.name); addLog("正在连接: " + bleDevice.name);
gattServer = await bleDevice.gatt.connect(); gattServer = await bleDevice.gatt.connect();
addLog(' 找到 GATT Server'); addLog(' 找到 GATT Server');
epdService = await gattServer.getPrimaryService('62750001-d828-918d-fb46-b6c11c675aec'); epdService = await gattServer.getPrimaryService('62750001-d828-918d-fb46-b6c11c675aec');
addLog(' 找到 EPD Service'); addLog(' 找到 EPD Service');
epdCharacteristic = await epdService.getCharacteristic('62750002-d828-918d-fb46-b6c11c675aec'); epdCharacteristic = await epdService.getCharacteristic('62750002-d828-918d-fb46-b6c11c675aec');
addLog(' 找到 Characteristic'); addLog(' 找到 Characteristic');
await epdCharacteristic.startNotifications(); await epdCharacteristic.startNotifications();
epdCharacteristic.addEventListener('characteristicvaluechanged', (event) => { epdCharacteristic.addEventListener('characteristicvaluechanged', (event) => {
addLog(`<span class="action">⇓</span> ${bytesToHex(event.target.value.buffer)}`); addLog(`<span class="action">⇓</span> ${bytes2hex(event.target.value.buffer)}`);
document.getElementById("epdpins").value = bytesToHex(event.target.value.buffer.slice(0, 7)); document.getElementById("epdpins").value = bytes2hex(event.target.value.buffer.slice(0, 7));
document.getElementById("epddriver").value = bytesToHex(event.target.value.buffer.slice(7, 8)); document.getElementById("epddriver").value = bytes2hex(event.target.value.buffer.slice(7, 8));
}); });
await sendcmd("01"); await write(EpdCmd.INIT);
document.getElementById("connectbutton").innerHTML = '断开'; document.getElementById("connectbutton").innerHTML = '断开';
updateButtonStatus(); updateButtonStatus();
} }
} }
function setStatus(statusText) { function setStatus(statusText) {
document.getElementById("status").innerHTML = statusText; document.getElementById("status").innerHTML = statusText;
} }
function addLog(logTXT) { function addLog(logTXT) {
const log = document.getElementById("log"); const log = document.getElementById("log");
const now = new Date(); const now = new Date();
const time = String(now.getHours()).padStart(2, '0') + ":" + const time = String(now.getHours()).padStart(2, '0') + ":" +
String(now.getMinutes()).padStart(2, '0') + ":" + String(now.getMinutes()).padStart(2, '0') + ":" +
String(now.getSeconds()).padStart(2, '0') + " "; String(now.getSeconds()).padStart(2, '0') + " ";
log.innerHTML += '<span class="time">' + time + '</span>' + logTXT + '<br>'; log.innerHTML += '<span class="time">' + time + '</span>' + logTXT + '<br>';
log.scrollTop = log.scrollHeight; log.scrollTop = log.scrollHeight;
while ((log.innerHTML.match(/<br>/g) || []).length > 20) { while ((log.innerHTML.match(/<br>/g) || []).length > 20) {
var logs_br_position = log.innerHTML.search("<br>"); var logs_br_position = log.innerHTML.search("<br>");
log.innerHTML = log.innerHTML.substring(logs_br_position + 4); log.innerHTML = log.innerHTML.substring(logs_br_position + 4);
log.scrollTop = log.scrollHeight; log.scrollTop = log.scrollHeight;
} }
} }
function hexToBytes(hex) { function clearLog() {
for (var bytes = [], c = 0; c < hex.length; c += 2) document.getElementById("log").innerHTML = '';
bytes.push(parseInt(hex.substr(c, 2), 16));
return new Uint8Array(bytes);
} }
function bytesToHex(data) { function hex2bytes(hex) {
return new Uint8Array(data).reduce( for (var bytes = [], c = 0; c < hex.length; c += 2)
function (memo, i) { bytes.push(parseInt(hex.substr(c, 2), 16));
return memo + ("0" + i.toString(16)).slice(-2); 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) { function intToHex(intIn) {
let stringOut = ("0000" + intIn.toString(16)).substr(-4) let stringOut = ("0000" + intIn.toString(16)).substr(-4)
return stringOut.substring(2, 4) + stringOut.substring(0, 2); return stringOut.substring(2, 4) + stringOut.substring(0, 2);
} }
async function update_image () { async function update_image () {
const canvas = document.getElementById("canvas"); const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d");
let image = new Image();; let image = new Image();;
const image_file = document.getElementById('image_file'); const image_file = document.getElementById('image_file');
if (image_file.files.length > 0) { if (image_file.files.length > 0) {
const file = image_file.files[0]; const file = image_file.files[0];
image.src = URL.createObjectURL(file); image.src = URL.createObjectURL(file);
} else { } else {
image.src = document.getElementById('demo-img').src; image.src = document.getElementById('demo-img').src;
} }
image.onload = function(event) { image.onload = function(event) {
URL.revokeObjectURL(this.src); URL.revokeObjectURL(this.src);
ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height); ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height);
convert_dithering() convert_dithering()
} }
} }
function clear_canvas() { function clear_canvas() {
if(confirm('确认清除画布内容?')) { if(confirm('确认清除画布内容?')) {
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d");
ctx.fillStyle = 'white'; ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillRect(0, 0, canvas.width, canvas.height);
} }
} }
function convert_dithering() { function convert_dithering() {
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d");
const mode = document.getElementById('dithering').value; const mode = document.getElementById('dithering').value;
if (mode.startsWith('bwr')) { if (mode.startsWith('bwr')) {
ditheringCanvasByPalette(canvas, bwrPalette, mode); ditheringCanvasByPalette(canvas, bwrPalette, mode);
} else if (mode === '4gray') { } else if (mode === '4gray') {
dithering(ctx, canvas.width, canvas.height, 4, "gray"); dithering(ctx, canvas.width, canvas.height, 4, "gray");
} else { } else {
dithering(ctx, canvas.width, canvas.height, parseInt(document.getElementById('threshold').value), mode); dithering(ctx, canvas.width, canvas.height, parseInt(document.getElementById('threshold').value), mode);
} }
} }
document.body.onload = () => { document.body.onload = () => {
canvas = document.getElementById('canvas'); canvas = document.getElementById('canvas');
updateButtonStatus(); updateButtonStatus();
update_image(); update_image();
document.getElementById('dithering').value = 'none'; document.getElementById('dithering').value = 'none';
} }