diff --git a/docs/images/0.jpg b/docs/images/0.jpg
index 1f13239..f949de8 100644
Binary files a/docs/images/0.jpg and b/docs/images/0.jpg differ
diff --git a/docs/index.html b/docs/index.html
index 24846e3..2e37cf5 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -1,269 +1,111 @@
-
+
4.2 寸电子墨水屏蓝牙控制器(nRF51)
+
-
-
-
+
-
-
-
-
-
-
-
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/js/dithering.js b/docs/js/dithering.js
new file mode 100644
index 0000000..06b5b12
--- /dev/null
+++ b/docs/js/dithering.js
@@ -0,0 +1,249 @@
+const bwrPalette = [
+ [0, 0, 0, 255],
+ [255, 255, 255, 255],
+ [255, 0, 0, 255]
+]
+
+const bwPalette = [
+ [0, 0, 0, 255],
+ [255, 255, 255, 255],
+]
+
+function dithering(ctx, width, height, threshold, type) {
+ const bayerThresholdMap = [
+ [ 15, 135, 45, 165 ],
+ [ 195, 75, 225, 105 ],
+ [ 60, 180, 30, 150 ],
+ [ 240, 120, 210, 90 ]
+ ];
+
+ const lumR = [];
+ const lumG = [];
+ const lumB = [];
+ for (let i=0; i<256; i++) {
+ lumR[i] = i*0.299;
+ lumG[i] = i*0.587;
+ lumB[i] = i*0.114;
+ }
+ const imageData = ctx.getImageData(0, 0, width, height);
+
+ const imageDataLength = imageData.data.length;
+
+ // Greyscale luminance (sets r pixels to luminance of rgb)
+ for (let i = 0; i <= imageDataLength; i += 4) {
+ imageData.data[i] = Math.floor(lumR[imageData.data[i]] + lumG[imageData.data[i+1]] + lumB[imageData.data[i+2]]);
+ }
+
+ const w = imageData.width;
+ let newPixel, err;
+
+ for (let currentPixel = 0; currentPixel <= imageDataLength; currentPixel+=4) {
+
+ if (type ==="none") {
+ // No dithering
+ imageData.data[currentPixel] = imageData.data[currentPixel] < threshold ? 0 : 255;
+ } else if (type ==="bayer") {
+ // 4x4 Bayer ordered dithering algorithm
+ var x = currentPixel/4 % w;
+ var y = Math.floor(currentPixel/4 / w);
+ var map = Math.floor( (imageData.data[currentPixel] + bayerThresholdMap[x%4][y%4]) / 2 );
+ imageData.data[currentPixel] = (map < threshold) ? 0 : 255;
+ } else if (type ==="floydsteinberg") {
+ // Floyda€"Steinberg dithering algorithm
+ newPixel = imageData.data[currentPixel] < 129 ? 0 : 255;
+ err = Math.floor((imageData.data[currentPixel] - newPixel) / 16);
+ imageData.data[currentPixel] = newPixel;
+
+ imageData.data[currentPixel + 4 ] += err*7;
+ imageData.data[currentPixel + 4*w - 4 ] += err*3;
+ imageData.data[currentPixel + 4*w ] += err*5;
+ imageData.data[currentPixel + 4*w + 4 ] += err*1;
+ } else {
+ // Bill Atkinson's dithering algorithm
+ newPixel = imageData.data[currentPixel] < threshold ? 0 : 255;
+ err = Math.floor((imageData.data[currentPixel] - newPixel) / 8);
+ imageData.data[currentPixel] = newPixel;
+
+ imageData.data[currentPixel + 4 ] += err;
+ imageData.data[currentPixel + 8 ] += err;
+ imageData.data[currentPixel + 4*w - 4 ] += err;
+ imageData.data[currentPixel + 4*w ] += err;
+ imageData.data[currentPixel + 4*w + 4 ] += err;
+ imageData.data[currentPixel + 8*w ] += err;
+ }
+
+ // Set g and b pixels equal to r
+ imageData.data[currentPixel + 1] = imageData.data[currentPixel + 2] = imageData.data[currentPixel];
+ }
+
+ ctx.putImageData(imageData, 0, 0);
+}
+
+function canvas2bytes(canvas, type='bw') {
+ const ctx = canvas.getContext("2d");
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
+
+ const arr = [];
+ let buffer = [];
+
+ for (let y = 0; y < canvas.height; y++) {
+ for (let x = 0; x < canvas.width; x++) {
+ const index = (canvas.width * y + x) * 4;
+ if (type !== 'bwr') {
+ buffer.push(imageData.data[index] > 0 && imageData.data[index+1] > 0 && imageData.data[index+2] > 0 ? 1 : 0);
+ } else {
+ buffer.push(imageData.data[index] > 0 && imageData.data[index+1] === 0 && imageData.data[index+2] === 0 ? 1 : 0);
+ }
+
+ if (buffer.length === 8) {
+ arr.push(parseInt(buffer.join(''), 2));
+ buffer = [];
+ }
+ }
+ }
+ return arr;
+}
+
+function bytes2canvas(bytes, canvas) {
+ const ctx = canvas.getContext("2d");
+ const imageData = ctx.createImageData(canvas.width, canvas.height);
+
+ let buffer = [];
+ for (let byte of bytes) {
+ const binaryString = byte.toString(2).padStart(8, '0');
+ buffer.push(...binaryString.split('').map(bit => parseInt(bit, 10)));
+ }
+ let len = buffer.length;
+
+ for (let y = 0; y < canvas.height; y++) {
+ for (let x = 0; x < canvas.width; x++) {
+ const index = (canvas.width * y + x) * 4;
+ const bit = buffer.shift();
+ const value = bit ? 255 : 0;
+ imageData.data[index] = value; // R
+ imageData.data[index + 1] = value; // G
+ imageData.data[index + 2] = value; // B
+ imageData.data[index + 3] = 255; // A
+ }
+ }
+
+ if (buffer.length * 2 == len) {
+ for (let y = 0; y < canvas.height; y++) {
+ for (let x = 0; x < canvas.width; x++) {
+ const index = (canvas.width * y + x) * 4;
+ const bit = buffer.shift();
+ if (bit) {
+ imageData.data[index] = 255; // R
+ imageData.data[index + 1] = 0; // G
+ imageData.data[index + 2] = 0; // B
+ imageData.data[index + 3] = 255; // A
+ }
+ }
+ }
+ }
+
+ ctx.putImageData(imageData, 0, 0);
+}
+
+function getColorDistance(rgba1, rgba2) {
+ const [r1, b1, g1] = rgba1;
+ const [r2, b2, g2] = rgba2;
+
+ const rm = (r1 + r2 ) / 2;
+
+ const r = r1 - r2;
+ const g = g1 - g2;
+ const b = b1 - b2;
+
+ return Math.sqrt((2 + rm / 256) * r * r + 4 * g * g + (2 + (255 - rm) / 256) * b * b);
+}
+
+function getNearColor(pixel, palette) {
+ let minDistance = 255 * 255 * 3 + 1;
+ let paletteIndex = 0;
+
+ for (let i = 0; i < palette.length; i++) {
+ const targetColor = palette[i];
+ const distance = getColorDistance(pixel, targetColor);
+ if (distance < minDistance) {
+ minDistance = distance;
+ paletteIndex = i;
+ }
+ }
+
+ return palette[paletteIndex];
+}
+
+
+function getNearColorV2(color, palette) {
+ let minDistanceSquared = 255*255 + 255*255 + 255*255 + 1;
+
+ let bestIndex = 0;
+ for (let i = 0; i < palette.length; i++) {
+ let rdiff = (color[0] & 0xff) - (palette[i][0] & 0xff);
+ let gdiff = (color[1] & 0xff) - (palette[i][1] & 0xff);
+ let bdiff = (color[2] & 0xff) - (palette[i][2] & 0xff);
+ let distanceSquared = rdiff*rdiff + gdiff*gdiff + bdiff*bdiff;
+ if (distanceSquared < minDistanceSquared) {
+ minDistanceSquared = distanceSquared;
+ bestIndex = i;
+ }
+ }
+ return palette[bestIndex];
+
+}
+
+
+function updatePixel(imageData, index, color) {
+ imageData[index] = color[0];
+ imageData[index+1] = color[1];
+ imageData[index+2] = color[2];
+ imageData[index+3] = color[3];
+}
+
+function getColorErr(color1, color2, rate) {
+ const res = [];
+ for (let i = 0; i < 3; i++) {
+ res.push(Math.floor((color1[i] - color2[i]) / rate));
+ }
+ return res;
+}
+
+function updatePixelErr(imageData, index, err, rate) {
+ imageData[index] += err[0] * rate;
+ imageData[index+1] += err[1] * rate;
+ imageData[index+2] += err[2] * rate;
+}
+
+function ditheringCanvasByPalette(canvas, palette, type) {
+ palette = palette || bwrPalette;
+
+ const ctx = canvas.getContext('2d');
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
+ const w = imageData.width;
+
+ for (let currentPixel = 0; currentPixel <= imageData.data.length; currentPixel+=4) {
+ const newColor = getNearColorV2(imageData.data.slice(currentPixel, currentPixel+4), palette);
+
+ if (type === "bwr_floydsteinberg") {
+ const err = getColorErr(imageData.data.slice(currentPixel, currentPixel+4), newColor, 16);
+
+ updatePixel(imageData.data, currentPixel, newColor);
+ updatePixelErr(imageData.data, currentPixel +4, err, 7);
+ updatePixelErr(imageData.data, currentPixel + 4*w - 4, err, 3);
+ updatePixelErr(imageData.data, currentPixel + 4*w, err, 5);
+ updatePixelErr(imageData.data, currentPixel + 4*w + 4, err, 1);
+ } else {
+ const err = getColorErr(imageData.data.slice(currentPixel, currentPixel+4), newColor, 8);
+
+ updatePixel(imageData.data, currentPixel, newColor);
+ updatePixelErr(imageData.data, currentPixel +4, err, 1);
+ updatePixelErr(imageData.data, currentPixel +8, err, 1);
+ updatePixelErr(imageData.data, currentPixel +4 * w - 4, err, 1);
+ updatePixelErr(imageData.data, currentPixel +4 * w, err, 1);
+ updatePixelErr(imageData.data, currentPixel +4 * w + 4, err, 1);
+ updatePixelErr(imageData.data, currentPixel +8 * w, err, 1);
+ }
+ }
+ ctx.putImageData(imageData, 0, 0);
+}
\ No newline at end of file
diff --git a/docs/js/main.js b/docs/js/main.js
new file mode 100644
index 0000000..c31f020
--- /dev/null
+++ b/docs/js/main.js
@@ -0,0 +1,252 @@
+let bleDevice;
+let gattServer;
+let Theservice;
+let writeCharacteristic;
+let reconnectTrys = 0;
+
+let imgArray = "";
+let imgArrayLen = 0;
+let chunkSize = 38;
+let uploadPart = 0;
+let totalPart = 0;
+
+function resetVariables() {
+ gattServer = null;
+ Theservice = null;
+ writeCharacteristic = null;
+ document.getElementById("log").value = '';
+ imgArray = "";
+ imgArrayLen = 0;
+ uploadPart = 0;
+}
+
+function handleError(error) {
+ console.log(error);
+ resetVariables();
+ if (bleDevice == null)
+ return;
+ if (reconnectTrys <= 5) {
+ reconnectTrys++;
+ connect();
+ }
+ else {
+ addLog("Was not able to connect, aborting");
+ reconnectTrys = 0;
+ }
+}
+
+async function sendCommand(cmd) {
+ if (writeCharacteristic) {
+ await writeCharacteristic.writeValue(cmd);
+ }
+}
+
+async function sendcmd(cmdTXT) {
+ let cmd = hexToBytes(cmdTXT);
+ addLog('Send CMD: ' + cmdTXT);
+ await sendCommand(cmd);
+}
+
+function setDriver() {
+ let driver = document.getElementById("epddriver").value;
+ let pins = document.getElementById("epdpins").value;
+ sendcmd("00" + pins).then(() => {
+ sendcmd("01" + driver);
+ });
+}
+
+function clearscreen() {
+ if(confirm('确认清除屏幕内容?')) {
+ sendcmd("01").then(() => {
+ sendcmd("02").then(() => {
+ sendcmd("06");
+ })
+ }).catch(handleError);
+ }
+}
+
+function sendimg(cmdIMG) {
+ startTime = new Date().getTime();
+ imgArray = cmdIMG.replace(/(?:\r\n|\r|\n|,|0x| )/g, '');
+ imgArrayLen = imgArray.length;
+ uploadPart = 0;
+ totalPart = Math.round(imgArrayLen / chunkSize);
+ console.log('Sending image ' + imgArrayLen);
+ sendcmd("01").then(() => {
+ sendCommand(hexToBytes("0313")).then(() => {
+ sendIMGpart();
+ });
+ }).catch(handleError);
+}
+
+function sendIMGpart() {
+ if (imgArray.length > 0) {
+ let currentPart = imgArray.substring(0, chunkSize);
+ let currentTime = (new Date().getTime() - startTime) / 1000.0;
+ imgArray = imgArray.substring(chunkSize);
+ setStatus('正在发送块: ' + (uploadPart++) + "/" + totalPart + ", 用时: " + currentTime + "s");
+ addLog('Sending Part: ' + currentPart);
+ sendCommand(hexToBytes("04" + currentPart)).then(() => {
+ sendIMGpart();
+ })
+ } else {
+ sendCommand(hexToBytes("05")).then(() => {
+ let sendTime = (new Date().getTime() - startTime) / 1000.0;
+ addLog("Done! Time used: " + sendTime + "s");
+ setStatus("发送完成!耗时: " + sendTime + "s");
+ sendcmd("06");
+ })
+ }
+}
+
+function updateButtonStatus() {
+ let connected = gattServer != null && gattServer.connected;
+ let status = connected ? null : 'disabled';
+ document.getElementById("sendcmdbutton").disabled = status;
+ document.getElementById("clearscreenbutton").disabled = status;
+ document.getElementById("sendimgbutton").disabled = status;
+ document.getElementById("setDriverbutton").disabled = status;
+}
+
+function disconnect() {
+ resetVariables();
+ addLog('Disconnected.');
+ document.getElementById("connectbutton").innerHTML = '连接';
+ updateButtonStatus();
+}
+
+function preConnect() {
+ if (gattServer != null && gattServer.connected) {
+ if (bleDevice != null && bleDevice.gatt.connected)
+ bleDevice.gatt.disconnect();
+ }
+ else {
+ connectTrys = 0;
+ navigator.bluetooth.requestDevice({ optionalServices: ['62750001-d828-918d-fb46-b6c11c675aec'], acceptAllDevices: true }).then(device => {
+ device.addEventListener('gattserverdisconnected', disconnect);
+ bleDevice = device;
+ connect();
+ }).catch(handleError);
+ }
+}
+
+function reConnect() {
+ connectTrys = 0;
+ if (bleDevice != null && bleDevice.gatt.connected)
+ bleDevice.gatt.disconnect();
+ resetVariables();
+ addLog("Reconnect");
+ setTimeout(function () { connect(); }, 300);
+}
+
+function connect() {
+ if (writeCharacteristic == null) {
+ addLog("Connecting to: " + bleDevice.name);
+ bleDevice.gatt.connect().then(server => {
+ addLog('> Found GATT Server');
+ gattServer = server;
+ return gattServer.getPrimaryService('62750001-d828-918d-fb46-b6c11c675aec');
+ }).then(service => {
+ addLog('> Found Service');
+ Theservice = service;
+ return Theservice.getCharacteristic('62750002-d828-918d-fb46-b6c11c675aec');
+ }).then(characteristic => {
+ addLog('> Found Characteristic');
+ document.getElementById("connectbutton").innerHTML = '断开';
+ updateButtonStatus();
+ writeCharacteristic = characteristic;
+ return;
+ }).catch(handleError);
+ }
+}
+
+function setStatus(statusText) {
+ document.getElementById("status").innerHTML = statusText;
+}
+
+function addLog(logTXT) {
+ var today = new Date();
+ var time = ("0" + today.getHours()).slice(-2) + ":" + ("0" + today.getMinutes()).slice(-2) + ":" + ("0" + today.getSeconds()).slice(-2) + " : ";
+ document.getElementById("log").innerHTML += time + logTXT + '