蓝牙墨水屏上位机学习(3)
main.js中sendimg()函数学习,对应发送图片按钮
函数代码如下:
async function sendimg() {const canvasSize = document.getElementById('canvasSize').value;const ditherMode = document.getElementById('ditherMode').value;const epdDriverSelect = document.getElementById('epddriver');const selectedOption = epdDriverSelect.options[epdDriverSelect.selectedIndex];if (selectedOption.getAttribute('data-size') !== canvasSize) {addLog(`画布尺寸和驱动不匹配,请重新选择。`);return;}if (selectedOption.getAttribute('data-color') !== ditherMode) {addLog(`颜色模式和驱动不匹配,请重新选择。`);return;}startTime = new Date().getTime();const status = document.getElementById("status");status.parentElement.style.display = "block";const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);const processedData = processImageData(imageData);updateButtonStatus(true);if (ditherMode === 'fourColor') {await writeImage(processedData, 'color');} else if (ditherMode === 'threeColor') {const halfLength = Math.floor(processedData.length / 2);await writeImage(processedData.slice(0, halfLength), 'bw');await writeImage(processedData.slice(halfLength), 'red');} else if (ditherMode === 'blackWhiteColor') {await writeImage(processedData, 'bw');} else {addLog("当前固件不支持此颜色模式。");updateButtonStatus();return;}await write(EpdCmd.REFRESH);updateButtonStatus();const sendTime = (new Date().getTime() - startTime) / 1000.0;addLog(`发送完成!耗时: ${sendTime}s`);setStatus(`发送完成!耗时: ${sendTime}s`);addLog("屏幕刷新完成前请不要操作。");setTimeout(() => {status.parentElement.style.display = "none";}, 5000);
}
电子纸图像发送函数 sendimg()
详解
这个 sendimg()
函数实现了通过蓝牙向电子纸显示设备发送图像数据的核心功能。它处理图像预处理、数据分块传输和屏幕刷新命令,是电子纸显示系统的关键部分。下面我将详细解析其功能和实现逻辑。
函数整体功能
sendimg()
函数的主要工作流程:
- 验证用户选择的画布尺寸、颜色模式与电子纸驱动是否匹配
- 从 Canvas 获取图像数据并进行预处理(如抖动处理)
- 根据不同的颜色模式(黑白 / 三色 / 四色)组织和发送数据
- 发送屏幕刷新命令触发电子纸更新显示
- 记录发送时间并显示操作结果
代码逻辑详解
1. 参数验证与初始化
const canvasSize = document.getElementById('canvasSize').value;
const ditherMode = document.getElementById('ditherMode').value;
const epdDriverSelect = document.getElementById('epddriver');
const selectedOption = epdDriverSelect.options[epdDriverSelect.selectedIndex];// 验证画布尺寸与驱动是否匹配
if (selectedOption.getAttribute('data-size') !== canvasSize) {addLog(`画布尺寸和驱动不匹配,请重新选择。`);return;
}
// 验证颜色模式与驱动是否匹配
if (selectedOption.getAttribute('data-color') !== ditherMode) {addLog(`颜色模式和驱动不匹配,请重新选择。`);return;
}// 记录开始时间并显示状态
startTime = new Date().getTime();
const status = document.getElementById("status");
status.parentElement.style.display = "block";
补充javascript知识:
HTML DOM Document 对象
HTML DOM 节点
在 HTML DOM (Document Object Model) 中 , 每一个元素都是 节点:
文档是一个文档。
所有的HTML元素都是元素节点。
所有 HTML 属性都是属性节点。
文本插入到 HTML 元素是文本节点。are text nodes。
注释是注释节点。
Document 对象
当浏览器载入 HTML 文档, 它就会成为 document 对象。
document 对象是HTML文档的根节点与所有其他节点(元素节点,文本节点,属性节点, 注释节点)。
Document 对象使我们可以从脚本中对 HTML 页面中的所有元素进行访问。
提示:Document 对象是 Window 对象的一部分,可通过 window.document 属性对其进行访问。
Document 对象属性和方法
HTML文档中可以使用以上属性和方法:
属性 / 方法 描述
document.activeElement 返回当前获取焦点元素
document.addEventListener() 向文档添加句柄
document.adoptNode(node) 从另外一个文档返回 adapded 节点到当前文档。
document.anchors 返回对文档中所有 Anchor 对象的引用。
document.applets 返回对文档中所有 Applet 对象的引用。
document.baseURI 返回文档的绝对基础 URI
document.body 返回文档的body元素
document.close() 关闭用 document.open() 方法打开的输出流,并显示选定的数据。
document.cookie 设置或返回与当前文档有关的所有 cookie。
document.createAttribute() 创建一个属性节点
document.createComment() createComment() 方法可创建注释节点。
document.createDocumentFragment() 创建空的 DocumentFragment 对象,并返回此对象。
document.createElement() 创建元素节点。
document.createTextNode() 创建文本节点。
document.doctype 返回与文档相关的文档类型声明 (DTD)。
document.documentElement 返回文档的根节点
document.documentMode 返回用于通过浏览器渲染文档的模式
document.documentURI 设置或返回文档的位置
document.domain 返回当前文档的域名。
document.domConfig 返回normalizeDocument()被调用时所使用的配置
document.embeds 返回文档中所有嵌入的内容(embed)集合
document.forms 返回对文档中所有 Form 对象引用。
document. getElementsByClassName() 返回文档中所有指定类名的元素集合,作为 NodeList 对象。
document.getElementById() 返回对拥有指定 id 的第一个对象的引用。
document.getElementsByName() 返回带有指定名称的对象集合。
document.getElementsByTagName() 返回带有指定标签名的对象集合。
document.images 返回对文档中所有 Image 对象引用。
document.implementation 返回处理该文档的 DOMImplementation 对象。
document.importNode() 把一个节点从另一个文档复制到该文档以便应用。
document.inputEncoding 返回用于文档的编码方式(在解析时)。
document.lastModified 返回文档被最后修改的日期和时间。
document.links 返回对文档中所有 Area 和 Link 对象引用。
document.normalize() 删除空文本节点,并连接相邻节点
document.normalizeDocument() 删除空文本节点,并连接相邻节点的
document.open() 打开一个流,以收集来自任何 document.write() 或 document.writeln() 方法的输出。
document.querySelector() 返回文档中匹配指定的CSS选择器的第一元素
document.querySelectorAll() document.querySelectorAll() 是 HTML5中引入的新方法,返回文档中匹配的CSS选择器的所有元素节点列表
document.readyState 返回文档状态 (载入中……)
document.referrer 返回载入当前文档的文档的 URL。
document.removeEventListener() 移除文档中的事件句柄(由 addEventListener() 方法添加)
document.renameNode() 重命名元素或者属性节点。
document.scripts 返回页面中所有脚本的集合。
document.strictErrorChecking 设置或返回是否强制进行错误检查。
document.title 返回当前文档的标题。
document.URL 返回文档完整的URL
document.write() 向文档写 HTML 表达式 或 JavaScript 代码。
document.writeln() 等同于 write() 方法,不同的是在每个表达式之后写一个换行符。
其中:document.getElementById() 返回对拥有指定 id 的第一个对象的引用。
sendimg()函数的第一行代码:
const canvasSize = document.getElementById('canvasSize').value;
1. document : document 是浏览器提供的一个全局对象,代表当前加载的 HTML 文档。通过这个对象,可以访问和操作文档中的所有元素。
2. getElementById('canvasSize') :这是 document 对象的一个方法,用于通过元素的 id 属性来查找文档中的元素。在这里,它会查找 id 为 canvasSize 的元素。如果找到了该元素,则返回对应的 DOM 元素对象;如果未找到,则返回 null 。
3. .value :这是访问 DOM 元素的 value 属性,通常用于获取表单元素(如 <input> 、 <select> 、 <textarea> 等)的当前值。
4. const canvasSize :使用 const 关键字声明一个常量 canvasSize ,并将 id 为 canvasSize 的元素的 value 属性值赋给它。 const 声明的常量一旦赋值就不能再重新赋值。
### 示例场景
假设 HTML 文档中有如下代码:
```
<input type="number" id="canvasSize" value="800">
```
当执行 const canvasSize = document.getElementById('canvasSize').value; 时, canvasSize 的值将是字符串 "800" 。
在index.html中找到如下 代码:
<select id="canvasSize" onchange="updateCanvasSize()"><option value="1.54_152_152">1.54 (152x152)</option><option value="1.54_200_200">1.54 (200x200)</option><option value="2.13_212_104">2.13 (212x104)</option><option value="2.13_250_122">2.13 (250x122)</option><option value="2.66_296_152">2.66 (296x152)</option><option value="2.9_296_128">2.9 (296x128)</option><option value="2.9_384_168">2.9 (384x168)</option><option value="3.5_384_184">3.5 (384x184)</option><option value="3.7_416_240">3.7 (416x240)</option><option value="3.97_800_480">3.97 (800x480)</option><option value="4.2_400_300" selected>4.2 (400x300)</option><option value="5.79_792_272">5.79 (792x272)</option><option value="7.5_800_480">7.5 (800x480)</option><option value="10.2_960_640">10.2 (960x640)</option><option value="10.85_1360_480">10.85 (1360x480)</option><option value="11.6_960_640">11.6 (960x640)</option><option value="4E_600_400">4E (600x400)</option><option value="7.3E6">7.3E6 (480x800)</option></select>
html语法中<select>标签用法如下 :
如:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body><select><option value="volvo">Volvo</option><option value="saab">Saab</option><option value="opel">Opel</option><option value="audi">Audi</option>
</select></body>
</html>
运行结果是
这是一个下拉选择框。
sendimg()函数的第二行代码:
const ditherMode = document.getElementById('ditherMode').value//抖动模式
1. document : document 是浏览器提供的全局对象,代表当前加载的 HTML 文档。借助该对象,你能访问和操作文档里的所有元素。
2. getElementById('ditherMode') :这是 document 对象的一个方法,作用是通过元素的 id 属性在文档里查找对应的元素。若找到了 id 为 ditherMode 的元素,就会返回对应的 DOM 元素对象;若未找到,则返回 null 。
3. .value :这是在访问 DOM 元素的 value 属性,通常用于获取表单元素(如 <input> 、 <select> 、 <textarea> 等)的当前值。
4. const ditherMode :使用 const 关键字声明一个常量 ditherMode ,并把 id 为 ditherMode 的元素的 value 属性值赋给它。 const 声明的常量一旦赋值,就不能再重新赋值。
### 示例场景
假设 HTML 文档中有如下代码:
```
<select id="ditherMode">
<option value="none">无抖动</option>
<option value="floyd-steinberg" selected>弗洛伊德 - 斯坦伯格抖动</option>
<option value="ordered">有序抖动</option>
</select>
```
当执行 const ditherMode = document.getElementById('ditherMode').value; 时, ditherMode 的值会是字符串 "floyd-steinberg" ,因为该选项当前处于选中状态。
在index.html中找到如下 代码:
<select id="ditherMode" onchange="updateImage(false)"><option value="blackWhiteColor">双色(黑白)</option><option value="threeColor">三色(黑白红)</option><option value="fourColor">四色(黑白红黄)</option><option value="sixColor">六色(黑白红黄蓝绿)</option></select>
sendimg()函数的第三行代码:
const epdDriverSelect = document.getElementById('epddriver');
1. const :这是 ES6 引入的关键字,用于声明一个常量。常量一旦被赋值,在其作用域内就不能再被重新赋值。
2. epdDriverSelect :这是声明的常量名称,推测该常量可能用于表示与电子纸驱动(EPD Driver)相关的选择器。
3. document : document 是浏览器提供的全局对象,代表当前加载的 HTML 文档。借助这个对象,你可以访问和操作文档中的所有元素。
4. getElementById('epddriver') :这是 document 对象的一个方法,作用是通过元素的 id 属性在文档中查找对应的元素。如果找到了 id 为 epddriver 的元素,就会返回对应的 DOM 元素对象;如果没找到,则返回 null 。
### 示例场景
假设 HTML 文档中有如下代码:
```
<select id="epddriver">
<option value="driver1">驱动 1</option>
<option value="driver2">驱动 2</option>
</select>
```
当执行 const epdDriverSelect = document.getElementById('epddriver'); 时, epdDriverSelect 就会指向这个 <select> 元素,后续可以通过这个常量对该元素进行操作,比如获取或修改其值、添加事件监听器等。
1. 什么是 HTML DOM?
HTML DOM 是浏览器将 HTML 文档解析为一个树形结构的编程接口。它将文档中的每个部分(如元素、属性、文本等)表示为节点(Node),这些节点共同组成了 DOM 树。通过 DOM,开发者可以使用 JavaScript 动态地操作网页内容。
DOM 树的结构
DOM 树由多个节点组成,常见的节点类型包括:
文档节点(Document):整个文档的根节点。
元素节点(Element):HTML 标签(如 <div>、<p> 等)。
属性节点(Attribute):HTML 元素的属性(如 class、id 等)。
文本节点(Text):元素中的文本内容。
注释节点(Comment):HTML 文档中的注释。
在Index.html中找到如下 代码:
<select id="epddriver" onchange="updateDitcherOptions()"><option value="01" data-color="blackWhiteColor" data-size="4.2_400_300">UC8176/UC8276(黑白)</option><option value="03" data-color="threeColor" data-size="4.2_400_300">UC8176/UC8276(三色)</option><option value="04" data-color="blackWhiteColor" data-size="4.2_400_300">SSD1619/SSD1683(黑白)</option><option value="02" data-color="threeColor" data-size="4.2_400_300">SSD1619/SSD1683(三色)</option><option value="05" data-color="fourColor" data-size="4.2_400_300">JD79668(四色)</option></select>
代码第4行
const selectedOption = epdDriverSelect.options[epdDriverSelect.selectedIndex];
代码解析:获取选中的电子纸驱动选项
这行代码从 HTML 的下拉选择框(<select>
)中获取用户当前选中的电子纸驱动选项,是前端开发中处理表单选择的典型操作。下面我将详细解析其功能和实现逻辑。
代码功能详解
1. 获取 select 元素引用
const epdDriverSelect = document.getElementById('epddriver');
- 作用:获取 ID 为
epddriver
的 HTML 元素引用 - 典型 HTML 结构:
<select id="epddriver"><option value="epd213b" data-size="128x296" data-color="blackWhiteColor">2.13" 黑白</option><option value="epd213c" data-size="128x296" data-color="threeColor">2.13" 三色</option><option value="epd42" data-size="400x300" data-color="blackWhiteColor">4.2" 黑白</option> </select>
2. 获取选中的选项索引
epdDriverSelect.selectedIndex
selectedIndex
属性:返回当前选中选项的索引值(从 0 开始)- 示例:如果用户选择了 "2.13" 三色 ",则
selectedIndex
为 1
3. 获取选中的选项元素
const selectedOption = epdDriverSelect.options[epdDriverSelect.selectedIndex];
options
属性:是一个类数组对象,包含所有<option>
子元素- 最终结果:
selectedOption
指向用户当前选中的<option>
元素
关键属性与应用场景
1. 常用的 <option>
属性
value
:选项的值(如"epd213b"
)text
:显示给用户的文本(如"2.13" 黑白"
)- 自定义数据属性:
data-size
:兼容的画布尺寸(如"128x296"
)data-color
:支持的颜色模式(如"blackWhiteColor"
)
2. 在电子纸系统中的应用
获取选中的驱动选项后,通常会读取其自定义数据属性进行验证:
// 验证画布尺寸与驱动是否匹配
if (selectedOption.getAttribute('data-size') !== canvasSize) {addLog(`画布尺寸和驱动不匹配,请重新选择。`);return;
}// 验证颜色模式与驱动是否匹配
if (selectedOption.getAttribute('data-color') !== ditherMode) {addLog(`颜色模式和驱动不匹配,请重新选择。`);return;
}
代码示例与交互逻辑
下面是一个完整的示例,展示如何监听驱动选择变化并动态更新 UI:
javascript
// 获取select元素
const driverSelect = document.getElementById('epddriver');// 监听选择变化事件
driverSelect.addEventListener('change', function() {const selectedOption = this.options[this.selectedIndex];// 获取驱动信息const driverValue = selectedOption.value;const driverName = selectedOption.text;const supportedSize = selectedOption.getAttribute('data-size');const supportedColor = selectedOption.getAttribute('data-color');// 更新UI显示document.getElementById('driverInfo').textContent = `已选择: ${driverName}支持尺寸: ${supportedSize}支持颜色: ${supportedColor}`;// 自动调整画布尺寸(可选)resizeCanvas(supportedSize);
});// 初始化显示当前选择
function initDriverInfo() {const event = new Event('change');driverSelect.dispatchEvent(event);
}// 页面加载后初始化
window.addEventListener('DOMContentLoaded', initDriverInfo);
常见问题与最佳实践
1. 常见错误
- 未选择任何选项:
selectedIndex
为-1
,需添加检查 - 属性拼写错误:如
data-size
写成dataSize
- 兼容性问题:旧版浏览器可能不支持某些属性
2. 最佳实践
- 添加默认选项:确保 select 有一个默认选中项
html
预览
<option value="" selected disabled>请选择驱动</option>
- 使用 dataset 属性:更简洁地访问自定义数据属性
javascript
const supportedSize = selectedOption.dataset.size; // 代替 getAttribute
- 实时验证:在用户选择变化时立即验证,而不是等到提交时
通过这行代码,系统能够准确获取用户选择的电子纸驱动信息,并据此进行后续的数据处理和验证,确保图像数据能正确适配目标硬件。
- 关键验证点:确保用户选择的画布尺寸(如 200×200)和颜色模式(如黑白、三色)与电子纸驱动支持的参数一致,避免因不匹配导致显示异常。
2. 图像数据处理
javascript
// 从Canvas获取原始图像数据
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// 处理图像数据(如转换为适合电子纸的格式、应用抖动算法)
const processedData = processImageData(imageData);// 禁用按钮防止重复操作
updateButtonStatus(true);
processImageData()
函数:- 将 RGB/A 图像数据转换为电子纸支持的颜色空间
- 应用抖动算法(如 Floyd-Steinberg)减少颜色位数
- 为不同的电子纸驱动格式优化数据排列
3. 根据颜色模式发送数据
javascript
if (ditherMode === 'fourColor') {await writeImage(processedData, 'color');
} else if (ditherMode === 'threeColor') {// 三色模式需要分两次发送:黑白数据和红色数据const halfLength = Math.floor(processedData.length / 2);await writeImage(processedData.slice(0, halfLength), 'bw');await writeImage(processedData.slice(halfLength), 'red');
} else if (ditherMode === 'blackWhiteColor') {await writeImage(processedData, 'bw');
} else {addLog("当前固件不支持此颜色模式。");updateButtonStatus();return;
}
- 多色模式处理:
- 三色电子纸通常需要分别发送黑白层和红色层数据
- 四色模式则一次性发送所有颜色数据
- 不同模式调用
writeImage()
函数时会传递不同的参数(如 'bw' 表示黑白数据)
4. 发送刷新命令与状态更新
javascript
// 发送刷新命令,触发电子纸物理刷新
await write(EpdCmd.REFRESH);
updateButtonStatus();// 计算发送耗时并显示结果
const sendTime = (new Date().getTime() - startTime) / 1000.0;
addLog(`发送完成!耗时: ${sendTime}s`);
setStatus(`发送完成!耗时: ${sendTime}s`);
addLog("屏幕刷新完成前请不要操作。");// 5秒后隐藏状态显示
setTimeout(() => {status.parentElement.style.display = "none";
}, 5000);
- 刷新命令:
EpdCmd.REFRESH
是一个特殊指令,通知电子纸控制器根据接收到的图像数据更新物理显示。 - 时间记录:精确计算数据发送耗时,帮助用户评估系统性能。
关键函数解析
1. writeImage(data, type)
这个函数负责将图像数据分块发送到电子纸设备:
javascript
async function writeImage(data, type) {const chunkSize = 20; // 每包20字节(受蓝牙MTU限制)const cmd = type === 'bw' ? EpdCmd.WRITE_BW : type === 'red' ? EpdCmd.WRITE_RED : EpdCmd.WRITE_COLOR;// 发送开始命令await write(cmd);// 分块发送数据for (let i = 0; i < data.length; i += chunkSize) {const chunk = data.slice(i, i + chunkSize);await write(chunk);// 更新进度显示const percent = ((i / data.length) * 100).toFixed(1);setStatus(`发送中: ${percent}%`);}// 发送结束命令await write(EpdCmd.END_DATA);
}
- 数据分块:将大图像数据拆分为小数据包(通常≤20 字节),适应蓝牙低功耗的传输限制
- 命令类型:根据数据类型(黑白 / 红色 / 彩色)发送不同的命令前缀
- 进度反馈:实时更新 UI 显示发送进度,提升用户体验
2. write(data)
这个底层函数负责通过蓝牙发送数据:
javascript
async function write(data) {if (!epdCharacteristic) {throw new Error('未连接到电子纸特征');}// 将数据转换为ArrayBufferconst buffer = data instanceof ArrayBuffer ? data : typeof data === 'number' ? new Uint8Array([data]).buffer : new TextEncoder().encode(data.toString()).buffer;// 写入数据到蓝牙特征return await epdCharacteristic.writeValueWithResponse(buffer);
}
- 数据格式转换:支持多种数据类型(数字、字符串、ArrayBuffer),统一转换为蓝牙可传输的格式
- 带响应的写入:使用
writeValueWithResponse()
确保数据可靠传输
电子纸显示原理补充
电子纸(如 E Ink)与传统显示屏的区别:
- 双稳态特性:刷新后无需持续供电即可保持显示内容
- 低刷新频率:刷新过程较慢(通常需要 1-2 秒),但功耗极低
- 特殊驱动要求:
- 需要特定的波形驱动信号才能正确显示
- 不同颜色模式(黑白、三色、四色)需要不同的数据格式和处理方式
优化建议
-
增加数据校验机制
- 在发送大块数据后,添加 CRC 校验确保数据完整性
- 实现错误重传机制,提高可靠性
-
优化刷新流程
- 实现部分刷新功能,仅更新屏幕变化区域
- 根据显示内容智能选择刷新模式(如快速刷新或高质量刷新)
-
增强用户反馈
- 添加视觉指示器显示电子纸刷新状态
- 提供刷新完成的声音提示或震动反馈
-
性能优化
- 实现数据压缩算法,减少传输时间
- 优化蓝牙 MTU(最大传输单元),提高单次传输数据量
这个函数实现了电子纸图像传输的核心逻辑,通过合理组织数据和控制传输流程,确保图像能正确、高效地显示在电子纸设备上。
编辑
分享