From cb7eee312f0718e4db5f1add797aaa7e678fe139 Mon Sep 17 00:00:00 2001 From: agent Date: Fri, 24 Jul 2026 09:23:28 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=B7=A5=E5=85=B7=E9=93=BE=E5=B0=B1?= =?UTF-8?q?=E7=BB=AA(bleak/pillow/pytest/capstone/androguard)=20+=20Web?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE=E6=8F=90=E5=8F=96=E6=8A=A5=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- analysis/web/ble.js | 4409 ++++++++++++++++++++++++++++++++++ analysis/web/hex.js | 15 + analysis/web/index.html | 1310 ++++++++++ analysis/web/moban.js | 943 ++++++++ analysis/web/protocol-raw.md | 181 ++ analysis/web/switch2.js | 55 + analysis/web/time.js | 21 + analysis/web/timedjs.js | 715 ++++++ analysis/web/timedjs2.js | 323 +++ requirements.txt | 5 + tools/check_env.py | 29 + 11 files changed, 8006 insertions(+) create mode 100644 analysis/web/ble.js create mode 100644 analysis/web/hex.js create mode 100644 analysis/web/index.html create mode 100644 analysis/web/moban.js create mode 100644 analysis/web/protocol-raw.md create mode 100644 analysis/web/switch2.js create mode 100644 analysis/web/time.js create mode 100644 analysis/web/timedjs.js create mode 100644 analysis/web/timedjs2.js create mode 100644 requirements.txt create mode 100644 tools/check_env.py diff --git a/analysis/web/ble.js b/analysis/web/ble.js new file mode 100644 index 0000000..e5191e4 --- /dev/null +++ b/analysis/web/ble.js @@ -0,0 +1,4409 @@ + + let bleDevice; + let gattServer; + let epdService; + let rxtxService; + let epdCharacteristic; + let rxtxCharacteristic; + let reconnectTrys = 0; + const my_step = 480; + function delay(delayInMs) { + return new Promise(resolve => { + setTimeout(() => { + resolve(2); + }, delayInMs); + }); + } + function resetVariables() { + gattServer = null; + epdService = null; + epdCharacteristic = null; + rxtxCharacteristic = null; + rxtxService = 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("Was not able to connect, aborting"); + reconnectTrys = 0; + } + } + async function sendCommand(cmd) { + if (epdCharacteristic) { + await epdCharacteristic.writeValueWithResponse(cmd) + } else { + addLog('服务不可用。蓝牙链接上了吗?') + } + } + async function clearScreen(cmd) { + addLog('刷屏') + await triggerEpdCmd(`00${cmd}`); + await triggerEpdCmd('01') + } + async function rxTxSendCommand(cmd) { + if (rxtxCharacteristic) { + await rxtxCharacteristic.writeValueWithResponse(cmd); + //addLog(`✅ [RXTX发送] 已发送 ${cmd.length} 字节`); + + // 可选:等待100ms后读取响应 + setTimeout(async () => { + try { + const value = await rxtxCharacteristic.readValue(); + if (value && value.byteLength > 0) { + const formatted = formatReceivedData(value); + addLog(`📥 [RXTX响应] ${formatted}`); + + const rxtxRxDiv = document.getElementById('rxtxRxData'); + if (rxtxRxDiv) { + rxtxRxDiv.innerHTML = `📎 读取响应:\n${formatted}`; + } + } + } catch(e) { + // 忽略读取失败(可能设备没有返回数据) + } + }, 100); + } else { + addLog('服务不可用。蓝牙链接上了吗?'); + } +} + async function SendCommand(cmd) { + let code; + for (let i; i < cmd.length; i += 2) { + code = '' + cmd[i] + cmd[i + 1]; + triggerRxTxCmd(code); + await delay(1000); + } + } + async function rxTxSendCommand2(cmd) { + if (rxtxCharacteristic) { + await rxtxCharacteristic.writeValueWith(cmd); + } else { + addLog('服务不可用。蓝牙链接上了吗?') + } + } + + async function sendBufferData(value, type) { + addLog(`开始发送图片模式:${type}, 大小 ${value.length}byte`); + let code = 'ff'; + if (type === 'bwr') { + code = '00'; + } + let cod = '03'; + if (value.length == 7000 || value.length == 18000) { cod = '04'; } + const step = my_step; + let partIndex = 0; + for (let i = 0; i < value.length; i += step) { + addLog(`正在发送第${partIndex + 1}块. 块大小: ${step / 2 + 4}byte. 起始位置: ${i / 2}`); + await sendCommand(hexToBytes(cod + code + intToHex(i / 2, 2) + value.substring(i, i + step))); + partIndex += 1; + } + } + function updateCanvasSize() { + const sizeSelect = document.getElementById('screen-size'); + const canvas = document.getElementById('canvas'); + const ctx = canvas.getContext("2d"); + let width, height; + if (sizeSelect.value == "4.2传图") { + width = 400; + height = 300; + } else if (sizeSelect.value === "2.9传图") { + width = 296; + height =128; + } else if (sizeSelect.value === "日历小图") { + width = 280; + height = 100; + } else if (sizeSelect.value === "7.5传图") { + width = 800; + height = 480; + } else if (sizeSelect.value === "7.5日历小图") { + width = 400; + height = 180; + } else if (sizeSelect.value === "2.13传图") { + width = 212; + height =128; + }else if (sizeSelect.value === "2.13高分传图") { + width = 250; + height =128; + } + canvas.width = width; + canvas.height = height; + ctx.fillStyle = 'white'; + ctx.fillRect(0, 0, width, height); + update_image(); + addLog(`画布尺寸已更新为: ${width}x${height}`); + } + + + async function upload_image() { + const canvas = document.getElementById('canvas'); + const startTime = new Date().getTime(); + if(canvas.width==296){ + + await sendBufferData(bytesToHex(canvas2bytes_bw(canvas)), 'bw') + await sendBufferData(bytesToHex(canvas2bytes_bw(canvas, 'bwr')), 'bwr') + }else if(canvas.width==250 ||canvas.width==212){ + await sendBufferData(bytesToHex(canvas2bytes_bw(canvas)), 'bw') + }else{ + await sendBufferData(bytesToHex(canvas2bytes(canvas)), 'bw') + await sendBufferData(bytesToHex(canvas2bytes(canvas, 'bwr')), 'bwr') + } + + await delay(300); + if (canvas.width == 280 || canvas.height == 180) { await sendCommand(hexToBytes("AA")) } + else { await sendCommand(hexToBytes("01")) } + addLog(`上传完成,耗时${(new Date().getTime() - startTime) / 1000}s`); + } + + async function update_imagemb(imageSource) { + + if (imageSource){ + const canvas = document.getElementById("unifiedCanvas"); + const ctx = canvas.getContext("2d"); + const image = new Image(); + image.src = imageSource; + image.onload = function (event) { + URL.revokeObjectURL(this.src); + currentImage = image; + drawImageWithRotation(image); + applyImageAdjustments(canvas); + convert_dithering(canvas); + + } + return; + } + + } + function resetAll_mb() { + brightnessSlider.value = '-30'; + contrastSlider.value = '100'; + saturationSlider.value = '100'; + diffusionSlider.value = '0'; + // 更新显示数值 + brightnessValue.textContent = -30; + contrastValue.textContent = 100; + saturationValue.textContent = 100; + diffusionValue.textContent = 0; + const canvas = document.getElementById('unifiedCanvas'); + applyImageAdjustments(canvas); + } + function resetAll_mb2() { + brightnessSlider.value = '0'; + contrastSlider.value = '100'; + saturationSlider.value = '100'; + diffusionSlider.value = '0'; + // 更新显示数值 + brightnessValue.textContent = 0; + contrastValue.textContent = 100; + saturationValue.textContent = 100; + diffusionValue.textContent = 0; + const canvas = document.getElementById('unifiedCanvas'); + applyImageAdjustments(canvas); + } +async function upload_image_moban(bat) { + const canvas = document.getElementById('unifiedCanvas'); + resetAll_mb(); + + cmd='EA0'+bat; + await rxTxSendCommand(hexToBytes(cmd)); + const startTime = new Date().getTime(); + + if(canvas.width==296){ + + await sendBufferData(bytesToHex(canvas2bytes_bw(canvas)), 'bw') + await sendBufferData(bytesToHex(canvas2bytes_bw(canvas, 'bwr')), 'bwr')} + else { await sendBufferData(bytesToHex(canvas2bytes(canvas)), 'bw') + await sendBufferData(bytesToHex(canvas2bytes(canvas, 'bwr')), 'bwr')} + + + + + resetAll_mb2(); + resetAll(); + await delay(300); + await sendCommand(hexToBytes("01")); + addLog(`上传完成,耗时${(new Date().getTime() - startTime) / 1000}s`); + } + + + + async function upload_image_lb(cmd) { + cmd='EA0'+cmd; + await rxTxSendCommand(hexToBytes(cmd)); + upload_image(); + } + async function upload_image_lb_num(cmd) { + if(cmd>4)cmd=4; + if(cmd<1)cmd=1; + addLog(`轮播数量已更新为: ${cmd}个!`); + cmd=cmd-1; + cmd='EA000'+cmd; + await rxTxSendCommand(hexToBytes(cmd)); + addLog(`轮播数量更新成功!`); + } + + async function setTime() { + const { unixNow, localeTimeString, year, month, day, week } = getUnixTime(); + addLog("时间设置为: " + localeTimeString + " : dd" + intToHex(unixNow, 4)); + await rxTxSendCommand(hexToBytes('dd' + + [intToHex(unixNow, 4), intToHex(year, 2), intToHex(month, 1), intToHex(day, 1), intToHex(week, 1)].join(''))); + await rxTxSendCommand(hexToBytes('e2')) + } + + const chinaCities = { +"北京": "101010100", +"海淀": "101010200", +"朝阳": "101010300", +"顺义": "101010400", +"怀柔": "101010500", +"通州": "101010600", +"昌平": "101010700", +"延庆": "101010800", +"丰台": "101010900", +"石景山": "101011000", +"大兴": "101011100", +"房山": "101011200", +"密云": "101011300", +"门头沟": "101011400", +"平谷": "101011500", +"东城": "101011600", +"西城": "101011700", +"上海": "101020100", +"闵行": "101020200", +"宝山": "101020300", +"黄浦": "101020400", +"嘉定": "101020500", +"浦东新区": "101020600", +"金山": "101020700", +"青浦": "101020800", +"松江": "101020900", +"奉贤": "101021000", +"崇明": "101021100", +"徐汇": "101021200", +"长宁": "101021300", +"静安": "101021400", +"普陀": "101021500", +"虹口": "101021600", +"杨浦": "101021700", +"天津": "101030100", +"武清": "101030200", +"宝坻": "101030300", +"东丽": "101030400", +"西青": "101030500", +"北辰": "101030600", +"宁河": "101030700", +"和平": "101030800", +"静海": "101030900", +"津南": "101031000", +"滨海新区": "101031100", +"河东": "101031200", +"河西": "101031300", +"蓟州": "101031400", +"南开": "101031500", +"河北": "101031600", +"红桥": "101031700", +"重庆": "101040100", +"永川": "101040200", +"合川": "101040300", +"南川": "101040400", +"江津": "101040500", +"北碚": "101040800", +"巴南": "101040900", +"长寿": "101041000", +"黔江": "101041100", +"渝中": "101041200", +"万州": "101041300", +"涪陵": "101041400", +"城口": "101041600", +"云阳": "101041700", +"巫溪": "101041800", +"奉节": "101041900", +"巫山": "101042000", +"潼南": "101042100", +"垫江": "101042200", +"梁平": "101042300", +"忠县": "101042400", +"石柱": "101042500", +"大足": "101042600", +"荣昌": "101042700", +"铜梁": "101042800", +"璧山": "101042900", +"丰都": "101043000", +"武隆": "101043100", +"彭水": "101043200", +"綦江": "101043300", +"酉阳": "101043400", +"大渡口": "101043500", +"秀山": "101043600", +"沙坪坝": "101043800", +"九龙坡": "101043900", +"南岸": "101044000", +"开州": "101044100", +"两江新区": "101044200", +"哈尔滨": "101050101", +"双城": "101050102", +"呼兰": "101050103", +"阿城": "101050104", +"宾县": "101050105", +"依兰": "101050106", +"巴彦": "101050107", +"通河": "101050108", +"方正": "101050109", +"延寿": "101050110", +"尚志": "101050111", +"五常": "101050112", +"木兰": "101050113", +"道里": "101050114", +"南岗": "101050115", +"道外": "101050116", +"平房": "101050117", +"松北": "101050118", +"香坊": "101050119", +"齐齐哈尔": "101050201", +"讷河": "101050202", +"龙江": "101050203", +"甘南": "101050204", +"富裕": "101050205", +"依安": "101050206", +"拜泉": "101050207", +"克山": "101050208", +"克东": "101050209", +"泰来": "101050210", +"龙沙": "101050211", +"建华": "101050212", +"铁锋": "101050213", +"昂昂溪": "101050214", +"富拉尔基": "101050215", +"碾子山": "101050216", +"梅里斯": "101050217", +"牡丹江": "101050301", +"海林": "101050302", +"穆棱": "101050303", +"林口": "101050304", +"绥芬河": "101050305", +"宁安": "101050306", +"东宁": "101050307", +"东安": "101050308", +"阳明": "101050309", +"爱民": "101050310", +"西安": "101050311", +"佳木斯": "101050401", +"汤原": "101050402", +"抚远": "101050403", +"桦川": "101050404", +"桦南": "101050405", +"同江": "101050406", +"富锦": "101050407", +"向阳": "101050408", +"前进": "101050409", +"东风": "101050410", +"郊区": "101050411", +"绥化": "101050501", +"肇东": "101050502", +"安达": "101050503", +"海伦": "101050504", +"明水": "101050505", +"望奎": "101050506", +"兰西": "101050507", +"青冈": "101050508", +"庆安": "101050509", +"绥棱": "101050510", +"北林": "101050511", +"黑河": "101050601", +"嫩江": "101050602", +"孙吴": "101050603", +"逊克": "101050604", +"五大连池": "101050605", +"北安": "101050606", +"爱辉": "101050607", +"大兴安岭": "101050701", +"塔河": "101050702", +"漠河": "101050703", +"呼玛": "101050704", +"加格达奇区": "101050708", +"伊春": "101050801", +"铁力": "101050804", +"嘉荫": "101050805", +"南岔": "101050806", +"友好": "101050807", +"金林": "101050808", +"伊美": "101050811", +"乌翠": "101050813", +"汤旺县": "101050814", +"大箐山县": "101050815", +"丰林": "101050816", +"大庆": "101050901", +"林甸": "101050902", +"肇州": "101050903", +"肇源": "101050904", +"杜尔伯特": "101050905", +"萨尔图": "101050906", +"龙凤": "101050907", +"让胡路": "101050908", +"红岗": "101050909", +"大同": "101050910", +"新兴": "101051001", +"七台河": "101051002", +"勃利": "101051003", +"桃山": "101051004", +"茄子河": "101051005", +"鸡西": "101051101", +"虎林": "101051102", +"密山": "101051103", +"鸡东": "101051104", +"鸡冠": "101051105", +"恒山": "101051106", +"滴道": "101051107", +"梨树": "101051108", +"城子河": "101051109", +"麻山": "101051110", +"鹤岗": "101051201", +"绥滨": "101051202", +"萝北": "101051203", +"向阳": "101051204", +"工农": "101051205", +"南山": "101051206", +"兴安": "101051207", +"东山": "101051208", +"兴山": "101051209", +"双鸭山": "101051301", +"集贤": "101051302", +"宝清": "101051303", +"饶河": "101051304", +"友谊": "101051305", +"尖山": "101051306", +"岭东": "101051307", +"四方台": "101051308", +"宝山": "101051309", +"长春": "101060101", +"农安": "101060102", +"德惠": "101060103", +"九台": "101060104", +"榆树": "101060105", +"双阳": "101060106", +"二道": "101060107", +"南关": "101060108", +"宽城": "101060109", +"朝阳": "101060110", +"绿园": "101060111", +"吉林": "101060201", +"舒兰": "101060202", +"永吉": "101060203", +"蛟河": "101060204", +"磐石": "101060205", +"桦甸": "101060206", +"昌邑": "101060207", +"龙潭": "101060208", +"船营": "101060209", +"丰满": "101060210", +"延吉": "101060301", +"敦化": "101060302", +"安图": "101060303", +"汪清": "101060304", +"和龙": "101060305", +"延边": "101060306", +"龙井": "101060307", +"珲春": "101060308", +"图们": "101060309", +"四平": "101060401", +"双辽": "101060402", +"梨树": "101060403", +"公主岭": "101060404", +"伊通": "101060405", +"铁西": "101060406", +"铁东": "101060407", +"通化": "101060501", +"梅河口": "101060502", +"柳河": "101060503", +"辉南": "101060504", +"集安": "101060505", +"通化县": "101060506", +"东昌": "101060507", +"二道江": "101060508", +"白城": "101060601", +"洮南": "101060602", +"大安": "101060603", +"镇赉": "101060604", +"通榆": "101060605", +"洮北": "101060606", +"辽源": "101060701", +"东丰": "101060702", +"东辽": "101060703", +"龙山": "101060704", +"西安": "101060705", +"松原": "101060801", +"乾安": "101060802", +"前郭": "101060803", +"长岭": "101060804", +"扶余": "101060805", +"宁江": "101060806", +"白山": "101060901", +"靖宇": "101060902", +"临江": "101060903", +"长白": "101060905", +"抚松": "101060906", +"江源": "101060907", +"浑江": "101060908", +"沈阳": "101070101", +"浑南": "101070102", +"辽中": "101070103", +"康平": "101070104", +"法库": "101070105", +"新民": "101070106", +"和平": "101070107", +"沈河": "101070108", +"大东": "101070109", +"皇姑": "101070110", +"铁西": "101070111", +"苏家屯": "101070112", +"沈北新区": "101070113", +"于洪": "101070114", +"大连": "101070201", +"瓦房店": "101070202", +"金州": "101070203", +"普兰店": "101070204", +"旅顺": "101070205", +"长海": "101070206", +"庄河": "101070207", +"中山": "101070208", +"西岗": "101070209", +"沙河口": "101070210", +"甘井子": "101070211", +"鞍山": "101070301", +"台安": "101070302", +"岫岩": "101070303", +"海城": "101070304", +"铁东": "101070305", +"铁西": "101070306", +"立山": "101070307", +"千山": "101070308", +"抚顺": "101070401", +"新宾": "101070402", +"清原": "101070403", +"新抚": "101070405", +"东洲": "101070406", +"望花": "101070407", +"顺城": "101070408", +"抚顺县": "101070409", +"本溪": "101070501", +"本溪县": "101070502", +"平山": "101070503", +"桓仁": "101070504", +"溪湖": "101070505", +"明山": "101070506", +"南芬": "101070507", +"丹东": "101070601", +"凤城": "101070602", +"宽甸": "101070603", +"东港": "101070604", +"元宝": "101070605", +"振兴": "101070606", +"振安": "101070607", +"锦州": "101070701", +"凌海": "101070702", +"古塔": "101070703", +"义县": "101070704", +"黑山": "101070705", +"北镇": "101070706", +"凌河": "101070707", +"太和": "101070708", +"营口": "101070801", +"大石桥": "101070802", +"盖州": "101070803", +"站前": "101070804", +"西市": "101070805", +"鲅鱼圈": "101070806", +"老边": "101070807", +"阜新": "101070901", +"彰武": "101070902", +"海州": "101070903", +"新邱": "101070904", +"太平": "101070905", +"清河门": "101070906", +"细河": "101070907", +"阜新县": "101070908", +"辽阳": "101071001", +"辽阳县": "101071002", +"灯塔": "101071003", +"弓长岭": "101071004", +"白塔": "101071005", +"文圣": "101071006", +"宏伟": "101071007", +"太子河": "101071008", +"铁岭": "101071101", +"开原": "101071102", +"昌图": "101071103", +"西丰": "101071104", +"调兵山": "101071105", +"银州": "101071106", +"清河": "101071107", +"铁岭县": "101071108", +"朝阳": "101071201", +"双塔": "101071202", +"凌源": "101071203", +"喀左": "101071204", +"北票": "101071205", +"龙城": "101071206", +"建平县": "101071207", +"朝阳县": "101071208", +"盘锦": "101071301", +"大洼": "101071302", +"盘山": "101071303", +"双台子": "101071304", +"兴隆台": "101071305", +"葫芦岛": "101071401", +"建昌": "101071402", +"绥中": "101071403", +"兴城": "101071404", +"连山": "101071405", +"龙港": "101071406", +"南票": "101071407", +"呼和浩特": "101080101", +"土左旗": "101080102", +"托克托": "101080103", +"和林": "101080104", +"清水河": "101080105", +"赛罕": "101080106", +"武川": "101080107", +"新城": "101080108", +"回民": "101080109", +"玉泉": "101080110", +"包头": "101080201", +"白云鄂博": "101080202", +"土右旗": "101080204", +"固阳": "101080205", +"达茂旗": "101080206", +"东河": "101080208", +"昆都仑": "101080209", +"青山": "101080210", +"石拐": "101080211", +"九原": "101080212", +"乌海": "101080301", +"海勃湾": "101080302", +"海南": "101080303", +"乌达": "101080304", +"集宁": "101080401", +"卓资": "101080402", +"化德": "101080403", +"商都": "101080404", +"乌兰察布": "101080405", +"兴和": "101080406", +"凉城": "101080407", +"察右前旗": "101080408", +"察右中旗": "101080409", +"察右后旗": "101080410", +"四子王旗": "101080411", +"丰镇": "101080412", +"通辽": "101080501", +"科左中旗": "101080503", +"科左后旗": "101080504", +"开鲁": "101080506", +"库伦": "101080507", +"奈曼": "101080508", +"扎鲁特": "101080509", +"科尔沁": "101080510", +"霍林郭勒": "101080512", +"赤峰": "101080601", +"红山": "101080602", +"阿鲁旗": "101080603", +"巴林左旗": "101080605", +"巴林右旗": "101080606", +"林西": "101080607", +"克什克腾": "101080608", +"翁牛特": "101080609", +"喀喇沁": "101080611", +"宁城": "101080613", +"敖汉": "101080614", +"元宝山": "101080616", +"松山": "101080617", +"鄂尔多斯": "101080701", +"达拉特": "101080703", +"准格尔": "101080704", +"鄂前旗": "101080705", +"鄂托克": "101080708", +"杭锦旗": "101080709", +"乌审旗": "101080710", +"伊金霍洛": "101080711", +"东胜": "101080713", +"康巴什": "101080714", +"临河": "101080801", +"五原": "101080802", +"磴口": "101080803", +"乌前旗": "101080804", +"乌中旗": "101080806", +"乌后旗": "101080807", +"杭锦后旗": "101080810", +"巴彦淖尔": "101080811", +"锡林浩特": "101080901", +"锡林郭勒": "101080902", +"二连浩特": "101080903", +"阿巴嘎": "101080904", +"苏左旗": "101080906", +"苏右旗": "101080907", +"东乌旗": "101080909", +"西乌旗": "101080910", +"太仆寺": "101080911", +"镶黄旗": "101080912", +"正镶白旗": "101080913", +"正蓝旗": "101080914", +"多伦": "101080915", +"海拉尔": "101081001", +"阿荣旗": "101081003", +"莫力达瓦": "101081004", +"鄂伦春旗": "101081005", +"鄂温克旗": "101081006", +"陈旗": "101081007", +"新左旗": "101081008", +"新右旗": "101081009", +"满洲里": "101081010", +"牙克石": "101081011", +"扎兰屯": "101081012", +"呼伦贝尔": "101081013", +"额尔古纳": "101081014", +"根河": "101081015", +"扎赉诺尔": "101081017", +"乌兰浩特": "101081101", +"阿尔山": "101081102", +"科右中旗": "101081103", +"扎赉特": "101081105", +"突泉": "101081107", +"兴安盟": "101081108", +"科右前旗": "101081109", +"阿左旗": "101081201", +"阿右旗": "101081202", +"额济纳": "101081203", +"阿拉善盟": "101081213", +"石家庄": "101090101", +"井陉": "101090102", +"正定": "101090103", +"栾城": "101090104", +"行唐": "101090105", +"灵寿": "101090106", +"高邑": "101090107", +"深泽": "101090108", +"赞皇": "101090109", +"无极": "101090110", +"平山": "101090111", +"元氏": "101090112", +"赵县": "101090113", +"辛集": "101090114", +"藁城": "101090115", +"晋州": "101090116", +"新乐": "101090117", +"鹿泉": "101090118", +"长安": "101090119", +"桥西": "101090120", +"新华": "101090121", +"井陉矿区": "101090122", +"裕华": "101090123", +"保定": "101090201", +"满城": "101090202", +"阜平": "101090203", +"徐水": "101090204", +"唐县": "101090205", +"高阳": "101090206", +"竞秀": "101090208", +"涞源": "101090209", +"望都": "101090210", +"易县": "101090212", +"莲池": "101090213", +"曲阳": "101090214", +"蠡县": "101090215", +"顺平": "101090216", +"涿州": "101090218", +"定州": "101090219", +"安国": "101090220", +"高碑店": "101090221", +"涞水": "101090222", +"定兴": "101090223", +"清苑": "101090224", +"博野": "101090225", +"张家口": "101090301", +"宣化": "101090302", +"张北": "101090303", +"康保": "101090304", +"沽源": "101090305", +"尚义": "101090306", +"蔚县": "101090307", +"阳原": "101090308", +"怀安": "101090309", +"万全": "101090310", +"怀来": "101090311", +"涿鹿": "101090312", +"赤城": "101090313", +"崇礼": "101090314", +"桥东": "101090315", +"桥西": "101090316", +"下花园": "101090317", +"双桥": "101090401", +"承德": "101090402", +"承德县": "101090403", +"兴隆": "101090404", +"平泉": "101090405", +"滦平": "101090406", +"隆化": "101090407", +"丰宁": "101090408", +"宽城": "101090409", +"围场": "101090410", +"双滦": "101090411", +"鹰手营子矿": "101090412", +"唐山": "101090501", +"丰南": "101090502", +"丰润": "101090503", +"滦州": "101090504", +"滦南": "101090505", +"乐亭": "101090506", +"迁西": "101090507", +"玉田": "101090508", +"曹妃甸": "101090509", +"遵化": "101090510", +"迁安": "101090511", +"路南": "101090513", +"路北": "101090514", +"古冶": "101090515", +"开平": "101090516", +"廊坊": "101090601", +"固安": "101090602", +"永清": "101090603", +"香河": "101090604", +"大城": "101090605", +"文安": "101090606", +"大厂": "101090607", +"霸州": "101090608", +"三河": "101090609", +"安次": "101090610", +"广阳": "101090611", +"沧州": "101090701", +"青县": "101090702", +"东光": "101090703", +"海兴": "101090704", +"盐山": "101090705", +"肃宁": "101090706", +"南皮": "101090707", +"吴桥": "101090708", +"献县": "101090709", +"孟村": "101090710", +"泊头": "101090711", +"任丘": "101090712", +"黄骅": "101090713", +"河间": "101090714", +"新华": "101090715", +"沧县": "101090716", +"运河": "101090717", +"衡水": "101090801", +"枣强": "101090802", +"武邑": "101090803", +"武强": "101090804", +"饶阳": "101090805", +"安平": "101090806", +"故城": "101090807", +"景县": "101090808", +"阜城": "101090809", +"冀州": "101090810", +"深州": "101090811", +"桃城": "101090812", +"邢台": "101090901", +"临城": "101090902", +"襄都": "101090903", +"内丘": "101090904", +"柏乡": "101090905", +"隆尧": "101090906", +"南和": "101090907", +"宁晋": "101090908", +"巨鹿": "101090909", +"新河": "101090910", +"广宗": "101090911", +"平乡": "101090912", +"威县": "101090913", +"清河": "101090914", +"临西": "101090915", +"南宫": "101090916", +"沙河": "101090917", +"任泽": "101090918", +"信都": "101090919", +"邯郸": "101091001", +"峰峰": "101091002", +"临漳": "101091003", +"成安": "101091004", +"大名": "101091005", +"涉县": "101091006", +"磁县": "101091007", +"肥乡": "101091008", +"永年": "101091009", +"邱县": "101091010", +"鸡泽": "101091011", +"广平": "101091012", +"馆陶": "101091013", +"魏县": "101091014", +"曲周": "101091015", +"武安": "101091016", +"邯山": "101091017", +"丛台": "101091018", +"复兴": "101091019", +"秦皇岛": "101091101", +"青龙": "101091102", +"昌黎": "101091103", +"抚宁": "101091104", +"卢龙": "101091105", +"北戴河": "101091106", +"海港": "101091107", +"山海关": "101091108", +"雄安新区": "101091201", +"容城": "101091202", +"安新": "101091203", +"雄县": "101091204", +"太原": "101100101", +"清徐": "101100102", +"阳曲": "101100103", +"娄烦": "101100104", +"古交": "101100105", +"尖草坪区": "101100106", +"小店区": "101100107", +"迎泽": "101100108", +"杏花岭": "101100109", +"万柏林": "101100110", +"晋源": "101100111", +"大同": "101100201", +"阳高": "101100202", +"云州": "101100203", +"天镇": "101100204", +"广灵": "101100205", +"灵丘": "101100206", +"浑源": "101100207", +"左云": "101100208", +"云冈": "101100209", +"新荣": "101100211", +"平城": "101100214", +"阳泉": "101100301", +"盂县": "101100302", +"平定": "101100303", +"矿区": "101100304", +"郊区": "101100305", +"城区": "101100306", +"晋中": "101100401", +"榆次": "101100402", +"榆社": "101100403", +"左权": "101100404", +"和顺": "101100405", +"昔阳": "101100406", +"寿阳": "101100407", +"太谷": "101100408", +"祁县": "101100409", +"平遥": "101100410", +"灵石": "101100411", +"介休": "101100412", +"长治": "101100501", +"黎城": "101100502", +"屯留": "101100503", +"潞城": "101100504", +"襄垣": "101100505", +"平顺": "101100506", +"武乡": "101100507", +"沁县": "101100508", +"长子": "101100509", +"沁源": "101100510", +"壶关": "101100511", +"潞州": "101100512", +"上党": "101100513", +"晋城": "101100601", +"沁水": "101100602", +"阳城": "101100603", +"陵川": "101100604", +"高平": "101100605", +"泽州": "101100606", +"城区": "101100607", +"临汾": "101100701", +"曲沃": "101100702", +"永和": "101100703", +"隰县": "101100704", +"大宁": "101100705", +"吉县": "101100706", +"襄汾": "101100707", +"蒲县": "101100708", +"汾西": "101100709", +"洪洞": "101100710", +"霍州": "101100711", +"乡宁": "101100712", +"翼城": "101100713", +"侯马": "101100714", +"浮山": "101100715", +"安泽": "101100716", +"古县": "101100717", +"尧都": "101100718", +"运城": "101100801", +"临猗": "101100802", +"稷山": "101100803", +"万荣": "101100804", +"河津": "101100805", +"新绛": "101100806", +"绛县": "101100807", +"闻喜": "101100808", +"垣曲": "101100809", +"永济": "101100810", +"芮城": "101100811", +"夏县": "101100812", +"平陆": "101100813", +"盐湖": "101100814", +"朔州": "101100901", +"平鲁": "101100902", +"山阴": "101100903", +"右玉": "101100904", +"应县": "101100905", +"怀仁": "101100906", +"朔城": "101100907", +"忻州": "101101001", +"定襄": "101101002", +"五台县": "101101003", +"河曲": "101101004", +"偏关": "101101005", +"神池": "101101006", +"宁武": "101101007", +"代县": "101101008", +"繁峙": "101101009", +"保德": "101101011", +"静乐": "101101012", +"岢岚": "101101013", +"五寨": "101101014", +"原平": "101101015", +"忻府": "101101016", +"吕梁": "101101100", +"离石": "101101101", +"临县": "101101102", +"兴县": "101101103", +"岚县": "101101104", +"柳林": "101101105", +"石楼": "101101106", +"方山": "101101107", +"交口": "101101108", +"中阳": "101101109", +"孝义": "101101110", +"汾阳": "101101111", +"文水": "101101112", +"交城": "101101113", +"西安": "101110101", +"长安": "101110102", +"临潼": "101110103", +"蓝田": "101110104", +"周至": "101110105", +"鄠邑": "101110106", +"高陵": "101110107", +"新城": "101110108", +"碑林": "101110109", +"莲湖": "101110110", +"灞桥": "101110111", +"未央": "101110112", +"雁塔": "101110113", +"阎良": "101110114", +"咸阳": "101110200", +"三原": "101110201", +"礼泉": "101110202", +"永寿": "101110203", +"淳化": "101110204", +"泾阳": "101110205", +"武功": "101110206", +"乾县": "101110207", +"彬州": "101110208", +"长武": "101110209", +"旬邑": "101110210", +"兴平": "101110211", +"秦都": "101110212", +"渭城": "101110213", +"延安": "101110300", +"延长": "101110301", +"延川": "101110302", +"子长": "101110303", +"宜川": "101110304", +"富县": "101110305", +"志丹": "101110306", +"安塞": "101110307", +"甘泉": "101110308", +"洛川": "101110309", +"黄陵": "101110310", +"黄龙": "101110311", +"吴起": "101110312", +"宝塔": "101110313", +"榆林": "101110401", +"府谷": "101110402", +"神木": "101110403", +"佳县": "101110404", +"定边": "101110405", +"靖边": "101110406", +"横山": "101110407", +"米脂": "101110408", +"子洲": "101110409", +"绥德": "101110410", +"吴堡": "101110411", +"清涧": "101110412", +"榆阳": "101110413", +"渭南": "101110501", +"潼关": "101110503", +"大荔": "101110504", +"白水": "101110505", +"富平": "101110506", +"蒲城": "101110507", +"澄城": "101110508", +"合阳": "101110509", +"韩城": "101110510", +"华阴": "101110511", +"临渭": "101110512", +"华州": "101110513", +"商洛": "101110601", +"洛南": "101110602", +"柞水": "101110603", +"商州": "101110604", +"镇安": "101110605", +"丹凤": "101110606", +"商南": "101110607", +"山阳": "101110608", +"安康": "101110701", +"紫阳": "101110702", +"石泉": "101110703", +"汉阴": "101110704", +"旬阳": "101110705", +"岚皋": "101110706", +"平利": "101110707", +"白河": "101110708", +"镇坪": "101110709", +"宁陕": "101110710", +"汉滨": "101110711", +"汉中": "101110801", +"略阳": "101110802", +"勉县": "101110803", +"留坝": "101110804", +"洋县": "101110805", +"城固": "101110806", +"西乡": "101110807", +"佛坪": "101110808", +"宁强": "101110809", +"南郑": "101110810", +"镇巴": "101110811", +"汉台": "101110812", +"宝鸡": "101110901", +"渭滨": "101110902", +"千阳": "101110903", +"麟游": "101110904", +"岐山": "101110905", +"凤翔": "101110906", +"扶风": "101110907", +"眉县": "101110908", +"太白": "101110909", +"凤县": "101110910", +"陇县": "101110911", +"陈仓": "101110912", +"金台": "101110913", +"铜川": "101111001", +"宜君": "101111003", +"耀州": "101111004", +"王益": "101111005", +"印台": "101111006", +"杨陵": "101111102", +"济南": "101120101", +"长清": "101120102", +"商河": "101120103", +"章丘": "101120104", +"平阴": "101120105", +"济阳": "101120106", +"历下": "101120107", +"市中": "101120108", +"槐荫": "101120109", +"天桥": "101120110", +"历城": "101120111", +"青岛": "101120201", +"崂山": "101120202", +"市南": "101120203", +"即墨": "101120204", +"胶州": "101120205", +"黄岛": "101120206", +"莱西": "101120207", +"平度": "101120208", +"市北": "101120209", +"李沧": "101120210", +"城阳": "101120211", +"淄博": "101120301", +"淄川": "101120302", +"博山": "101120303", +"高青": "101120304", +"周村": "101120305", +"沂源": "101120306", +"桓台": "101120307", +"临淄": "101120308", +"张店": "101120309", +"德州": "101120401", +"武城": "101120402", +"临邑": "101120403", +"齐河": "101120405", +"乐陵": "101120406", +"庆云": "101120407", +"平原": "101120408", +"宁津": "101120409", +"夏津": "101120410", +"禹城": "101120411", +"德城": "101120412", +"陵城": "101120413", +"烟台": "101120501", +"莱州": "101120502", +"蓬莱": "101120504", +"龙口": "101120505", +"招远": "101120506", +"栖霞": "101120507", +"福山": "101120508", +"牟平": "101120509", +"莱阳": "101120510", +"海阳": "101120511", +"芝罘": "101120512", +"莱山": "101120513", +"潍坊": "101120601", +"青州": "101120602", +"寿光": "101120603", +"临朐": "101120604", +"昌乐": "101120605", +"昌邑": "101120606", +"安丘": "101120607", +"高密": "101120608", +"诸城": "101120609", +"潍城": "101120610", +"寒亭": "101120611", +"坊子": "101120612", +"奎文": "101120613", +"济宁": "101120701", +"嘉祥": "101120702", +"微山": "101120703", +"鱼台": "101120704", +"兖州": "101120705", +"金乡": "101120706", +"汶上": "101120707", +"泗水": "101120708", +"梁山": "101120709", +"曲阜": "101120710", +"邹城": "101120711", +"任城": "101120712", +"泰安": "101120801", +"新泰": "101120802", +"泰山": "101120803", +"肥城": "101120804", +"东平": "101120805", +"宁阳": "101120806", +"岱岳": "101120807", +"临沂": "101120901", +"莒南": "101120902", +"沂南": "101120903", +"兰陵": "101120904", +"临沭": "101120905", +"郯城": "101120906", +"蒙阴": "101120907", +"平邑": "101120908", +"费县": "101120909", +"沂水": "101120910", +"兰山": "101120911", +"罗庄": "101120912", +"河东": "101120913", +"菏泽": "101121001", +"鄄城": "101121002", +"郓城": "101121003", +"东明": "101121004", +"定陶": "101121005", +"巨野": "101121006", +"曹县": "101121007", +"成武": "101121008", +"单县": "101121009", +"牡丹": "101121010", +"滨州": "101121101", +"博兴": "101121102", +"无棣": "101121103", +"阳信": "101121104", +"惠民": "101121105", +"沾化": "101121106", +"邹平": "101121107", +"滨城": "101121108", +"东营": "101121201", +"河口": "101121202", +"垦利": "101121203", +"利津": "101121204", +"广饶": "101121205", +"东营区": "101121206", +"威海": "101121301", +"文登": "101121302", +"荣成": "101121303", +"乳山": "101121304", +"环翠": "101121307", +"枣庄": "101121401", +"薛城": "101121402", +"峄城": "101121403", +"台儿庄": "101121404", +"滕州": "101121405", +"市中": "101121406", +"山亭": "101121407", +"日照": "101121501", +"五莲": "101121502", +"莒县": "101121503", +"东港": "101121504", +"岚山": "101121505", +"莱芜": "101121601", +"钢城": "101121603", +"聊城": "101121701", +"冠县": "101121702", +"阳谷": "101121703", +"高唐": "101121704", +"茌平": "101121705", +"东阿": "101121706", +"临清": "101121707", +"东昌府": "101121708", +"莘县": "101121709", +"乌鲁木齐": "101130101", +"天山": "101130102", +"沙依巴克": "101130104", +"达坂城": "101130105", +"新市": "101130106", +"水磨沟": "101130107", +"头屯河": "101130111", +"米东": "101130112", +"乌鲁木齐县": "101130113", +"克拉玛依": "101130201", +"乌尔禾": "101130202", +"白碱滩": "101130203", +"独山子": "101130204", +"克拉玛依区": "101130205", +"石河子": "101130301", +"昌吉": "101130401", +"呼图壁": "101130402", +"阜康": "101130404", +"吉木萨尔": "101130405", +"奇台": "101130406", +"玛纳斯": "101130407", +"木垒": "101130408", +"昌吉市": "101130410", +"吐鲁番": "101130501", +"托克逊": "101130502", +"高昌": "101130503", +"鄯善": "101130504", +"库尔勒": "101130601", +"轮台": "101130602", +"尉犁": "101130603", +"若羌": "101130604", +"且末": "101130605", +"和静": "101130606", +"焉耆": "101130607", +"和硕": "101130608", +"巴音郭楞": "101130609", +"博湖": "101130612", +"阿拉尔": "101130701", +"阿克苏": "101130801", +"乌什": "101130802", +"温宿": "101130803", +"拜城": "101130804", +"新和": "101130805", +"沙雅": "101130806", +"库车": "101130807", +"柯坪": "101130808", +"阿瓦提": "101130809", +"阿克苏市": "101130810", +"喀什地区": "101130901", +"英吉沙": "101130902", +"塔什库尔干": "101130903", +"麦盖提": "101130904", +"莎车": "101130905", +"叶城": "101130906", +"泽普": "101130907", +"巴楚": "101130908", +"岳普湖": "101130909", +"伽师": "101130910", +"疏附": "101130911", +"疏勒": "101130912", +"喀什市": "101130913", +"伊宁": "101131001", +"察布查尔": "101131002", +"尼勒克": "101131003", +"伊宁县": "101131004", +"巩留": "101131005", +"新源": "101131006", +"昭苏": "101131007", +"特克斯": "101131008", +"霍城": "101131009", +"霍尔果斯": "101131010", +"奎屯": "101131011", +"伊犁": "101131012", +"塔城地区": "101131101", +"裕民": "101131102", +"额敏": "101131103", +"和布克赛尔": "101131104", +"托里": "101131105", +"乌苏": "101131106", +"沙湾": "101131107", +"塔城市": "101131108", +"哈密": "101131201", +"伊州": "101131202", +"巴里坤": "101131203", +"伊吾": "101131204", +"和田地区": "101131301", +"皮山": "101131302", +"策勒": "101131303", +"墨玉": "101131304", +"洛浦": "101131305", +"民丰": "101131306", +"于田": "101131307", +"和田市": "101131308", +"和田县": "101131309", +"阿勒泰地区": "101131401", +"哈巴河": "101131402", +"吉木乃": "101131405", +"布尔津": "101131406", +"福海": "101131407", +"富蕴": "101131408", +"青河": "101131409", +"阿勒泰市": "101131410", +"阿图什": "101131501", +"乌恰": "101131502", +"阿克陶": "101131503", +"阿合奇": "101131504", +"克州": "101131505", +"博乐": "101131601", +"温泉": "101131602", +"精河": "101131603", +"博尔塔拉": "101131604", +"阿拉山口": "101131606", +"图木舒克": "101131701", +"五家渠": "101131801", +"铁门关": "101131901", +"昆玉": "101131920", +"北屯": "101132101", +"双河": "101132201", +"可克达拉": "101132301", +"胡杨河": "101132401", +"新星市": "101132501", +"白杨市": "101132601", +"拉萨": "101140101", +"当雄": "101140102", +"尼木": "101140103", +"林周": "101140104", +"堆龙德庆": "101140105", +"曲水": "101140106", +"达孜": "101140107", +"墨竹工卡": "101140108", +"城关": "101140109", +"日喀则": "101140201", +"拉孜": "101140202", +"南木林": "101140203", +"聂拉木": "101140204", +"定日": "101140205", +"江孜": "101140206", +"仲巴": "101140208", +"萨嘎": "101140209", +"吉隆": "101140210", +"昂仁": "101140211", +"定结": "101140212", +"萨迦": "101140213", +"谢通门": "101140214", +"桑珠孜": "101140215", +"岗巴": "101140216", +"白朗": "101140217", +"亚东": "101140218", +"康马": "101140219", +"仁布": "101140220", +"山南": "101140301", +"贡嘎": "101140302", +"扎囊": "101140303", +"加查": "101140304", +"浪卡子": "101140305", +"错那": "101140306", +"隆子": "101140307", +"乃东": "101140309", +"桑日": "101140310", +"洛扎": "101140311", +"措美": "101140312", +"琼结": "101140313", +"曲松": "101140314", +"林芝": "101140401", +"波密": "101140402", +"米林": "101140403", +"察隅": "101140404", +"工布江达": "101140405", +"朗县": "101140406", +"墨脱": "101140407", +"巴宜": "101140408", +"昌都": "101140501", +"丁青": "101140502", +"边坝": "101140503", +"洛隆": "101140504", +"左贡": "101140505", +"芒康": "101140506", +"类乌齐": "101140507", +"八宿": "101140508", +"江达": "101140509", +"察雅": "101140510", +"贡觉": "101140511", +"卡若": "101140512", +"那曲": "101140601", +"尼玛": "101140602", +"嘉黎": "101140603", +"班戈": "101140604", +"安多": "101140605", +"索县": "101140606", +"聂荣": "101140607", +"巴青": "101140608", +"比如": "101140609", +"双湖": "101140610", +"申扎": "101140611", +"色尼": "101140612", +"阿里地区": "101140701", +"改则": "101140702", +"普兰": "101140705", +"札达": "101140706", +"噶尔": "101140707", +"日土": "101140708", +"革吉": "101140709", +"措勤": "101140710", +"西宁": "101150101", +"大通": "101150102", +"湟源": "101150103", +"湟中": "101150104", +"城东": "101150105", +"城中": "101150106", +"城西": "101150107", +"城北": "101150108", +"平安": "101150201", +"乐都": "101150202", +"民和": "101150203", +"互助": "101150204", +"化隆": "101150205", +"循化": "101150206", +"海东": "101150207", +"同仁": "101150301", +"尖扎": "101150302", +"泽库": "101150303", +"河南": "101150304", +"黄南": "101150305", +"共和": "101150401", +"海南": "101150402", +"贵德": "101150404", +"兴海": "101150406", +"贵南": "101150407", +"同德": "101150408", +"玛沁": "101150501", +"班玛": "101150502", +"甘德": "101150503", +"达日": "101150504", +"久治": "101150505", +"玛多": "101150506", +"果洛": "101150507", +"玉树州": "101150601", +"称多": "101150602", +"治多": "101150603", +"杂多": "101150604", +"囊谦": "101150605", +"曲麻莱": "101150606", +"玉树市": "101150607", +"德令哈": "101150701", +"海西": "101150702", +"天峻": "101150708", +"乌兰": "101150709", +"茫崖": "101150712", +"大柴旦": "101150713", +"格尔木": "101150714", +"都兰": "101150715", +"海晏": "101150801", +"门源": "101150802", +"祁连": "101150803", +"海北": "101150804", +"刚察": "101150806", +"兰州": "101160101", +"皋兰": "101160102", +"永登": "101160103", +"榆中": "101160104", +"城关": "101160105", +"七里河": "101160106", +"西固": "101160107", +"安宁": "101160108", +"红古": "101160109", +"定西": "101160201", +"通渭": "101160202", +"陇西": "101160203", +"渭源": "101160204", +"临洮": "101160205", +"漳县": "101160206", +"岷县": "101160207", +"安定": "101160208", +"平凉": "101160301", +"泾川": "101160302", +"灵台": "101160303", +"崇信": "101160304", +"华亭": "101160305", +"庄浪": "101160306", +"静宁": "101160307", +"崆峒": "101160308", +"庆阳": "101160401", +"西峰": "101160402", +"环县": "101160403", +"华池": "101160404", +"合水": "101160405", +"正宁": "101160406", +"宁县": "101160407", +"镇原": "101160408", +"庆城": "101160409", +"武威": "101160501", +"民勤": "101160502", +"古浪": "101160503", +"凉州": "101160504", +"天祝": "101160505", +"金昌": "101160601", +"永昌": "101160602", +"金川": "101160603", +"张掖": "101160701", +"肃南": "101160702", +"民乐": "101160703", +"临泽": "101160704", +"高台": "101160705", +"山丹": "101160706", +"甘州": "101160707", +"酒泉": "101160801", +"肃州": "101160802", +"金塔": "101160803", +"阿克塞": "101160804", +"瓜州": "101160805", +"肃北": "101160806", +"玉门": "101160807", +"敦煌": "101160808", +"天水": "101160901", +"秦州": "101160902", +"清水": "101160903", +"秦安": "101160904", +"甘谷": "101160905", +"武山": "101160906", +"张家川": "101160907", +"麦积": "101160908", +"武都": "101161001", +"成县": "101161002", +"文县": "101161003", +"宕昌": "101161004", +"康县": "101161005", +"西和": "101161006", +"礼县": "101161007", +"徽县": "101161008", +"两当": "101161009", +"陇南": "101161010", +"临夏": "101161101", +"康乐": "101161102", +"永靖": "101161103", +"广河": "101161104", +"和政": "101161105", +"东乡": "101161106", +"积石山": "101161107", +"临夏市": "101161108", +"临夏县": "101161109", +"合作": "101161201", +"临潭": "101161202", +"卓尼": "101161203", +"舟曲": "101161204", +"迭部": "101161205", +"玛曲": "101161206", +"碌曲": "101161207", +"夏河": "101161208", +"甘南": "101161209", +"白银": "101161301", +"靖远": "101161302", +"会宁": "101161303", +"平川": "101161304", +"景泰": "101161305", +"白银区": "101161306", +"嘉峪关": "101161401", +"银川": "101170101", +"永宁": "101170102", +"灵武": "101170103", +"贺兰": "101170104", +"兴庆": "101170105", +"西夏": "101170106", +"金凤": "101170107", +"石嘴山": "101170201", +"惠农": "101170202", +"平罗": "101170203", +"大武口": "101170205", +"吴忠": "101170301", +"同心": "101170302", +"盐池": "101170303", +"利通": "101170304", +"红寺堡": "101170305", +"青铜峡": "101170306", +"固原": "101170401", +"西吉": "101170402", +"隆德": "101170403", +"泾源": "101170404", +"原州": "101170405", +"彭阳": "101170406", +"中卫": "101170501", +"中宁": "101170502", +"沙坡头": "101170503", +"海原": "101170504", +"郑州": "101180101", +"巩义": "101180102", +"荥阳": "101180103", +"登封": "101180104", +"新密": "101180105", +"新郑": "101180106", +"中牟": "101180107", +"上街": "101180108", +"中原": "101180109", +"二七": "101180110", +"管城": "101180111", +"金水": "101180112", +"惠济": "101180113", +"安阳": "101180201", +"汤阴": "101180202", +"滑县": "101180203", +"内黄": "101180204", +"林州": "101180205", +"文峰": "101180206", +"北关": "101180207", +"殷都": "101180208", +"龙安": "101180209", +"安阳县": "101180210", +"新乡": "101180301", +"获嘉": "101180302", +"原阳": "101180303", +"辉县": "101180304", +"卫辉": "101180305", +"延津": "101180306", +"封丘": "101180307", +"长垣": "101180308", +"红旗": "101180309", +"卫滨": "101180310", +"凤泉": "101180311", +"牧野": "101180312", +"新乡县": "101180313", +"许昌": "101180401", +"鄢陵": "101180402", +"襄城": "101180403", +"长葛": "101180404", +"禹州": "101180405", +"魏都": "101180406", +"建安": "101180407", +"平顶山": "101180501", +"郏县": "101180502", +"宝丰": "101180503", +"汝州": "101180504", +"叶县": "101180505", +"舞钢": "101180506", +"鲁山": "101180507", +"石龙": "101180508", +"新华": "101180509", +"卫东": "101180510", +"湛河": "101180511", +"信阳": "101180601", +"息县": "101180602", +"罗山": "101180603", +"光山": "101180604", +"新县": "101180605", +"淮滨": "101180606", +"潢川": "101180607", +"固始": "101180608", +"商城": "101180609", +"浉河": "101180610", +"平桥": "101180611", +"南阳": "101180701", +"南召": "101180702", +"方城": "101180703", +"社旗": "101180704", +"西峡": "101180705", +"内乡": "101180706", +"镇平": "101180707", +"淅川": "101180708", +"新野": "101180709", +"唐河": "101180710", +"邓州": "101180711", +"桐柏": "101180712", +"宛城": "101180713", +"卧龙": "101180714", +"开封": "101180801", +"杞县": "101180802", +"尉氏": "101180803", +"通许": "101180804", +"兰考": "101180805", +"龙亭": "101180806", +"顺河": "101180807", +"鼓楼": "101180808", +"禹王台": "101180809", +"祥符": "101180810", +"洛阳": "101180901", +"新安": "101180902", +"宜阳": "101180904", +"洛宁": "101180905", +"伊川": "101180906", +"嵩县": "101180907", +"偃师": "101180908", +"栾川": "101180909", +"汝阳": "101180910", +"孟津": "101180911", +"老城": "101180912", +"西工": "101180913", +"瀍河": "101180914", +"涧西": "101180915", +"洛龙": "101180916", +"商丘": "101181001", +"梁园": "101181002", +"睢县": "101181003", +"民权": "101181004", +"虞城": "101181005", +"柘城": "101181006", +"宁陵": "101181007", +"夏邑": "101181008", +"永城": "101181009", +"睢阳": "101181010", +"焦作": "101181101", +"修武": "101181102", +"武陟": "101181103", +"沁阳": "101181104", +"解放": "101181105", +"博爱": "101181106", +"温县": "101181107", +"孟州": "101181108", +"中站": "101181109", +"马村": "101181110", +"山阳": "101181111", +"鹤壁": "101181201", +"浚县": "101181202", +"淇县": "101181203", +"鹤山": "101181204", +"山城": "101181205", +"淇滨": "101181206", +"濮阳": "101181301", +"台前": "101181302", +"南乐": "101181303", +"清丰": "101181304", +"范县": "101181305", +"华龙": "101181306", +"濮阳县": "101181307", +"周口": "101181401", +"扶沟": "101181402", +"太康": "101181403", +"淮阳": "101181404", +"西华": "101181405", +"商水": "101181406", +"项城": "101181407", +"郸城": "101181408", +"鹿邑": "101181409", +"沈丘": "101181410", +"川汇": "101181411", +"漯河": "101181501", +"临颍": "101181502", +"舞阳": "101181503", +"源汇": "101181504", +"郾城": "101181505", +"召陵": "101181506", +"驻马店": "101181601", +"西平": "101181602", +"遂平": "101181603", +"上蔡": "101181604", +"汝南": "101181605", +"泌阳": "101181606", +"平舆": "101181607", +"新蔡": "101181608", +"确山": "101181609", +"正阳": "101181610", +"驿城": "101181611", +"三门峡": "101181701", +"灵宝": "101181702", +"渑池": "101181703", +"卢氏": "101181704", +"义马": "101181705", +"湖滨": "101181707", +"陕州": "101181708", +"济源": "101181801", +"南京": "101190101", +"溧水": "101190102", +"高淳": "101190103", +"江宁": "101190104", +"六合": "101190105", +"浦口": "101190107", +"玄武": "101190108", +"秦淮": "101190109", +"建邺": "101190110", +"鼓楼": "101190111", +"栖霞": "101190112", +"雨花台": "101190113", +"无锡": "101190201", +"江阴": "101190202", +"宜兴": "101190203", +"锡山": "101190204", +"惠山": "101190205", +"滨湖": "101190206", +"梁溪": "101190207", +"新吴": "101190208", +"镇江": "101190301", +"丹阳": "101190302", +"扬中": "101190303", +"句容": "101190304", +"丹徒": "101190305", +"京口": "101190306", +"润州": "101190307", +"苏州": "101190401", +"常熟": "101190402", +"张家港": "101190403", +"昆山": "101190404", +"吴中": "101190405", +"虎丘": "101190406", +"吴江": "101190407", +"太仓": "101190408", +"相城": "101190409", +"姑苏": "101190410", +"南通": "101190501", +"海安": "101190502", +"如皋": "101190503", +"如东": "101190504", +"崇川": "101190505", +"启东": "101190507", +"海门": "101190508", +"通州": "101190509", +"扬州": "101190601", +"宝应": "101190602", +"仪征": "101190603", +"高邮": "101190604", +"江都": "101190605", +"邗江": "101190606", +"广陵": "101190607", +"盐城": "101190701", +"响水": "101190702", +"滨海": "101190703", +"阜宁": "101190704", +"射阳": "101190705", +"建湖": "101190706", +"东台": "101190707", +"大丰": "101190708", +"盐都": "101190709", +"亭湖": "101190710", +"徐州": "101190801", +"铜山": "101190802", +"丰县": "101190803", +"沛县": "101190804", +"邳州": "101190805", +"睢宁": "101190806", +"新沂": "101190807", +"鼓楼": "101190808", +"云龙": "101190809", +"贾汪": "101190810", +"泉山": "101190811", +"淮安": "101190901", +"金湖": "101190902", +"盱眙": "101190903", +"洪泽": "101190904", +"涟水": "101190905", +"淮阴区": "101190906", +"清江浦": "101190907", +"淮安区": "101190908", +"连云港": "101191001", +"东海": "101191002", +"赣榆": "101191003", +"灌云": "101191004", +"灌南": "101191005", +"海州": "101191006", +"连云": "101191007", +"常州": "101191101", +"溧阳": "101191102", +"金坛": "101191103", +"武进": "101191104", +"天宁": "101191105", +"钟楼": "101191106", +"新北": "101191107", +"泰州": "101191201", +"兴化": "101191202", +"泰兴": "101191203", +"姜堰": "101191204", +"靖江": "101191205", +"海陵": "101191206", +"高港": "101191207", +"宿迁": "101191301", +"沭阳": "101191302", +"泗阳": "101191303", +"泗洪": "101191304", +"宿豫": "101191305", +"宿城": "101191306", +"武汉": "101200101", +"蔡甸": "101200102", +"黄陂": "101200103", +"新洲": "101200104", +"江夏": "101200105", +"东西湖": "101200106", +"江岸": "101200107", +"江汉": "101200108", +"硚口": "101200109", +"汉阳": "101200110", +"武昌": "101200111", +"青山": "101200112", +"洪山": "101200113", +"汉南": "101200114", +"襄阳": "101200201", +"襄州": "101200202", +"保康": "101200203", +"南漳": "101200204", +"宜城": "101200205", +"老河口": "101200206", +"谷城": "101200207", +"枣阳": "101200208", +"襄城": "101200209", +"樊城": "101200210", +"鄂州": "101200301", +"梁子湖": "101200302", +"华容": "101200303", +"鄂城": "101200304", +"孝感": "101200401", +"安陆": "101200402", +"云梦": "101200403", +"大悟": "101200404", +"应城": "101200405", +"汉川": "101200406", +"孝昌": "101200407", +"孝南": "101200408", +"黄冈": "101200501", +"红安": "101200502", +"麻城": "101200503", +"罗田": "101200504", +"英山": "101200505", +"浠水": "101200506", +"蕲春": "101200507", +"黄梅": "101200508", +"武穴": "101200509", +"团风": "101200510", +"黄州": "101200511", +"黄石": "101200601", +"大冶": "101200602", +"阳新": "101200603", +"铁山": "101200604", +"下陆": "101200605", +"西塞山": "101200606", +"黄石港": "101200607", +"咸宁": "101200701", +"赤壁": "101200702", +"嘉鱼": "101200703", +"崇阳": "101200704", +"通城": "101200705", +"通山": "101200706", +"咸安": "101200707", +"荆州": "101200801", +"江陵": "101200802", +"公安": "101200803", +"石首": "101200804", +"监利": "101200805", +"洪湖": "101200806", +"松滋": "101200807", +"沙市": "101200808", +"荆州区": "101200809", +"宜昌": "101200901", +"远安": "101200902", +"秭归": "101200903", +"兴山": "101200904", +"西陵": "101200905", +"五峰": "101200906", +"当阳": "101200907", +"长阳": "101200908", +"宜都": "101200909", +"枝江": "101200910", +"夷陵": "101200912", +"伍家岗": "101200913", +"点军": "101200914", +"猇亭": "101200915", +"恩施州": "101201001", +"利川": "101201002", +"建始": "101201003", +"咸丰": "101201004", +"宣恩": "101201005", +"鹤峰": "101201006", +"来凤": "101201007", +"巴东": "101201008", +"恩施市": "101201009", +"十堰": "101201101", +"竹溪": "101201102", +"郧西": "101201103", +"郧阳": "101201104", +"竹山": "101201105", +"房县": "101201106", +"丹江口": "101201107", +"茅箭": "101201108", +"张湾": "101201109", +"神农架": "101201201", +"随州": "101201301", +"广水": "101201302", +"曾都": "101201303", +"随县": "101201304", +"荆门": "101201401", +"钟祥": "101201402", +"京山": "101201403", +"掇刀": "101201404", +"沙洋": "101201405", +"东宝": "101201406", +"天门": "101201501", +"仙桃": "101201601", +"潜江": "101201701", +"杭州": "101210101", +"萧山": "101210102", +"桐庐": "101210103", +"淳安": "101210104", +"建德": "101210105", +"余杭": "101210106", +"临安": "101210107", +"富阳": "101210108", +"上城": "101210109", +"钱塘": "101210111", +"拱墅": "101210112", +"西湖": "101210113", +"滨江": "101210114", +"临平": "101210115", +"湖州": "101210201", +"长兴": "101210202", +"安吉": "101210203", +"德清": "101210204", +"吴兴": "101210205", +"南浔": "101210206", +"嘉兴": "101210301", +"嘉善": "101210302", +"海宁": "101210303", +"桐乡": "101210304", +"平湖": "101210305", +"海盐": "101210306", +"南湖": "101210307", +"秀洲": "101210308", +"宁波": "101210401", +"海曙": "101210402", +"慈溪": "101210403", +"余姚": "101210404", +"奉化": "101210405", +"象山": "101210406", +"宁海": "101210408", +"江北": "101210409", +"北仑": "101210410", +"鄞州": "101210411", +"镇海": "101210412", +"越城": "101210501", +"诸暨": "101210502", +"上虞": "101210503", +"新昌": "101210504", +"嵊州": "101210505", +"柯桥": "101210506", +"绍兴": "101210507", +"台州": "101210601", +"玉环": "101210603", +"三门": "101210604", +"天台": "101210605", +"仙居": "101210606", +"温岭": "101210607", +"临海": "101210610", +"椒江": "101210611", +"黄岩": "101210612", +"路桥": "101210613", +"温州": "101210701", +"泰顺": "101210702", +"文成": "101210703", +"平阳": "101210704", +"瑞安": "101210705", +"洞头": "101210706", +"乐清": "101210707", +"永嘉": "101210708", +"苍南": "101210709", +"鹿城": "101210710", +"龙湾": "101210711", +"瓯海": "101210712", +"龙港": "101210713", +"丽水": "101210801", +"遂昌": "101210802", +"龙泉": "101210803", +"缙云": "101210804", +"青田": "101210805", +"云和": "101210806", +"庆元": "101210807", +"松阳": "101210808", +"景宁": "101210809", +"莲都": "101210810", +"金华": "101210901", +"浦江": "101210902", +"兰溪": "101210903", +"义乌": "101210904", +"东阳": "101210905", +"武义": "101210906", +"永康": "101210907", +"磐安": "101210908", +"婺城": "101210909", +"金东": "101210910", +"衢州": "101211001", +"常山": "101211002", +"开化": "101211003", +"龙游": "101211004", +"江山": "101211005", +"衢江": "101211006", +"柯城": "101211007", +"舟山": "101211101", +"嵊泗": "101211102", +"岱山": "101211104", +"普陀": "101211105", +"定海": "101211106", +"合肥": "101220101", +"长丰": "101220102", +"肥东": "101220103", +"肥西": "101220104", +"巢湖": "101220105", +"庐江": "101220106", +"瑶海": "101220107", +"庐阳": "101220108", +"蜀山": "101220109", +"包河": "101220110", +"蚌埠": "101220201", +"怀远": "101220202", +"固镇": "101220203", +"五河": "101220204", +"龙子湖": "101220205", +"蚌山": "101220206", +"禹会": "101220207", +"淮上": "101220208", +"芜湖": "101220301", +"繁昌": "101220302", +"湾沚": "101220303", +"南陵": "101220304", +"无为": "101220305", +"镜湖": "101220306", +"弋江": "101220307", +"鸠江": "101220308", +"淮南": "101220401", +"凤台": "101220402", +"潘集": "101220403", +"大通": "101220404", +"田家庵": "101220405", +"谢家集": "101220406", +"八公山": "101220407", +"寿县": "101220408", +"马鞍山": "101220501", +"当涂": "101220502", +"含山": "101220503", +"和县": "101220504", +"花山": "101220505", +"雨山": "101220506", +"博望": "101220507", +"安庆": "101220601", +"太湖": "101220603", +"潜山": "101220604", +"怀宁": "101220605", +"宿松": "101220606", +"望江": "101220607", +"岳西": "101220608", +"桐城": "101220609", +"迎江": "101220610", +"大观": "101220611", +"宜秀": "101220612", +"宿州": "101220701", +"砀山": "101220702", +"灵璧": "101220703", +"泗县": "101220704", +"萧县": "101220705", +"埇桥": "101220706", +"阜阳": "101220801", +"阜南": "101220802", +"颍上": "101220803", +"临泉": "101220804", +"界首": "101220805", +"太和": "101220806", +"颍州": "101220807", +"颍东": "101220808", +"颍泉": "101220809", +"亳州": "101220901", +"涡阳": "101220902", +"利辛": "101220903", +"蒙城": "101220904", +"谯城": "101220905", +"黄山": "101221001", +"黄山区": "101221002", +"屯溪": "101221003", +"祁门": "101221004", +"黟县": "101221005", +"歙县": "101221006", +"休宁": "101221007", +"徽州": "101221009", +"滁州": "101221101", +"凤阳": "101221102", +"明光": "101221103", +"定远": "101221104", +"全椒": "101221105", +"来安": "101221106", +"天长": "101221107", +"琅琊": "101221108", +"南谯": "101221109", +"淮北": "101221201", +"濉溪": "101221202", +"杜集": "101221203", +"相山": "101221204", +"烈山": "101221205", +"铜陵": "101221301", +"铜官": "101221302", +"义安": "101221303", +"郊区": "101221304", +"枞阳": "101221305", +"宣城": "101221401", +"泾县": "101221402", +"旌德": "101221403", +"宁国": "101221404", +"绩溪": "101221405", +"广德": "101221406", +"郎溪": "101221407", +"宣州": "101221408", +"六安": "101221501", +"霍邱": "101221502", +"金安": "101221504", +"金寨": "101221505", +"霍山": "101221506", +"舒城": "101221507", +"裕安": "101221508", +"叶集": "101221509", +"池州": "101221701", +"东至": "101221702", +"青阳": "101221703", +"石台": "101221705", +"贵池": "101221706", +"福州": "101230101", +"闽清": "101230102", +"闽侯": "101230103", +"罗源": "101230104", +"连江": "101230105", +"鼓楼": "101230106", +"永泰": "101230107", +"平潭": "101230108", +"台江": "101230109", +"长乐": "101230110", +"福清": "101230111", +"仓山": "101230112", +"马尾": "101230113", +"晋安": "101230114", +"厦门": "101230201", +"同安": "101230202", +"思明": "101230203", +"海沧": "101230204", +"湖里": "101230205", +"集美": "101230206", +"翔安": "101230207", +"宁德": "101230301", +"古田": "101230302", +"霞浦": "101230303", +"寿宁": "101230304", +"周宁": "101230305", +"福安": "101230306", +"柘荣": "101230307", +"福鼎": "101230308", +"屏南": "101230309", +"蕉城": "101230310", +"莆田": "101230401", +"仙游": "101230402", +"涵江": "101230404", +"秀屿": "101230405", +"荔城": "101230406", +"城厢": "101230407", +"泉州": "101230501", +"安溪": "101230502", +"金门": "101230503", +"永春": "101230504", +"德化": "101230505", +"南安": "101230506", +"惠安": "101230508", +"晋江": "101230509", +"石狮": "101230510", +"鲤城": "101230511", +"丰泽": "101230512", +"洛江": "101230513", +"泉港": "101230514", +"漳州": "101230601", +"长泰": "101230602", +"南靖": "101230603", +"平和": "101230604", +"龙海": "101230605", +"漳浦": "101230606", +"诏安": "101230607", +"东山": "101230608", +"云霄": "101230609", +"华安": "101230610", +"芗城": "101230611", +"龙文": "101230612", +"龙岩": "101230701", +"长汀": "101230702", +"连城": "101230703", +"武平": "101230704", +"上杭": "101230705", +"永定": "101230706", +"漳平": "101230707", +"新罗": "101230708", +"三明": "101230801", +"宁化": "101230802", +"清流": "101230803", +"泰宁": "101230804", +"将乐": "101230805", +"建宁": "101230806", +"明溪": "101230807", +"沙县": "101230808", +"尤溪": "101230809", +"永安": "101230810", +"大田": "101230811", +"三元": "101230813", +"南平": "101230901", +"顺昌": "101230902", +"光泽": "101230903", +"邵武": "101230904", +"武夷山": "101230905", +"浦城": "101230906", +"建阳": "101230907", +"松溪": "101230908", +"政和": "101230909", +"建瓯": "101230910", +"延平": "101230911", +"南昌": "101240101", +"新建": "101240102", +"南昌县": "101240103", +"安义": "101240104", +"进贤": "101240105", +"东湖": "101240106", +"西湖": "101240107", +"青云谱": "101240108", +"红谷滩": "101240109", +"青山湖": "101240110", +"九江": "101240201", +"瑞昌": "101240202", +"庐山": "101240203", +"武宁": "101240204", +"德安": "101240205", +"永修": "101240206", +"湖口": "101240207", +"彭泽": "101240208", +"都昌": "101240210", +"浔阳": "101240211", +"修水": "101240212", +"共青城": "101240213", +"濂溪": "101240214", +"柴桑": "101240215", +"上饶": "101240301", +"鄱阳": "101240302", +"婺源": "101240303", +"信州": "101240304", +"余干": "101240305", +"万年": "101240306", +"德兴": "101240307", +"广信": "101240308", +"弋阳": "101240309", +"横峰": "101240310", +"铅山": "101240311", +"玉山": "101240312", +"广丰": "101240313", +"抚州": "101240401", +"广昌": "101240402", +"乐安": "101240403", +"崇仁": "101240404", +"金溪": "101240405", +"资溪": "101240406", +"宜黄": "101240407", +"南城": "101240408", +"南丰": "101240409", +"黎川": "101240410", +"东乡": "101240411", +"临川": "101240412", +"宜春": "101240501", +"铜鼓": "101240502", +"宜丰": "101240503", +"万载": "101240504", +"上高": "101240505", +"靖安": "101240506", +"奉新": "101240507", +"高安": "101240508", +"樟树": "101240509", +"丰城": "101240510", +"袁州": "101240511", +"吉安": "101240601", +"吉安县": "101240602", +"吉水": "101240603", +"新干": "101240604", +"峡江": "101240605", +"永丰": "101240606", +"永新": "101240607", +"井冈山": "101240608", +"万安": "101240609", +"遂川": "101240610", +"泰和": "101240611", +"安福": "101240612", +"吉州": "101240614", +"青原": "101240615", +"赣州": "101240701", +"崇义": "101240702", +"上犹": "101240703", +"南康": "101240704", +"大余": "101240705", +"信丰": "101240706", +"宁都": "101240707", +"石城": "101240708", +"瑞金": "101240709", +"于都": "101240710", +"会昌": "101240711", +"安远": "101240712", +"全南": "101240713", +"龙南": "101240714", +"定南": "101240715", +"寻乌": "101240716", +"兴国": "101240717", +"赣县": "101240718", +"章贡": "101240719", +"景德镇": "101240801", +"乐平": "101240802", +"浮梁": "101240803", +"昌江": "101240804", +"珠山": "101240805", +"萍乡": "101240901", +"莲花": "101240902", +"上栗": "101240903", +"安源": "101240904", +"芦溪": "101240905", +"湘东": "101240906", +"新余": "101241001", +"分宜": "101241002", +"渝水": "101241003", +"鹰潭": "101241101", +"余江": "101241102", +"贵溪": "101241103", +"月湖": "101241104", +"长沙": "101250101", +"宁乡": "101250102", +"浏阳": "101250103", +"望城": "101250105", +"长沙县": "101250106", +"芙蓉": "101250107", +"天心": "101250108", +"岳麓": "101250109", +"开福": "101250110", +"雨花": "101250111", +"湘潭": "101250201", +"韶山": "101250202", +"湘乡": "101250203", +"雨湖": "101250204", +"岳塘": "101250205", +"湘潭县": "101250206", +"株洲": "101250301", +"攸县": "101250302", +"醴陵": "101250303", +"荷塘": "101250304", +"茶陵": "101250305", +"炎陵": "101250306", +"芦淞": "101250307", +"石峰": "101250308", +"天元": "101250309", +"渌口": "101250310", +"衡阳": "101250401", +"衡山": "101250402", +"衡东": "101250403", +"祁东": "101250404", +"衡阳县": "101250405", +"常宁": "101250406", +"衡南": "101250407", +"耒阳": "101250408", +"南岳": "101250409", +"珠晖": "101250410", +"雁峰": "101250411", +"石鼓": "101250412", +"蒸湘": "101250413", +"郴州": "101250501", +"桂阳": "101250502", +"嘉禾": "101250503", +"宜章": "101250504", +"临武": "101250505", +"北湖": "101250506", +"资兴": "101250507", +"汝城": "101250508", +"安仁": "101250509", +"永兴": "101250510", +"桂东": "101250511", +"苏仙": "101250512", +"常德": "101250601", +"安乡": "101250602", +"桃源": "101250603", +"汉寿": "101250604", +"澧县": "101250605", +"临澧": "101250606", +"石门": "101250607", +"津市": "101250608", +"武陵": "101250609", +"鼎城": "101250610", +"益阳": "101250700", +"赫山区": "101250701", +"南县": "101250702", +"桃江": "101250703", +"安化": "101250704", +"沅江": "101250705", +"资阳": "101250706", +"娄底": "101250801", +"双峰": "101250802", +"冷水江": "101250803", +"娄星": "101250804", +"新化": "101250805", +"涟源": "101250806", +"邵阳": "101250901", +"隆回": "101250902", +"洞口": "101250903", +"新邵": "101250904", +"邵东": "101250905", +"绥宁": "101250906", +"新宁": "101250907", +"武冈": "101250908", +"城步": "101250909", +"邵阳县": "101250910", +"双清": "101250911", +"大祥": "101250912", +"北塔": "101250913", +"岳阳": "101251001", +"华容": "101251002", +"湘阴": "101251003", +"汨罗": "101251004", +"平江": "101251005", +"临湘": "101251006", +"岳阳楼区": "101251007", +"云溪": "101251008", +"君山": "101251009", +"岳阳县": "101251010", +"张家界": "101251101", +"桑植": "101251102", +"慈利": "101251103", +"武陵源": "101251104", +"永定": "101251105", +"怀化": "101251201", +"鹤城": "101251202", +"沅陵": "101251203", +"辰溪": "101251204", +"靖州": "101251205", +"会同": "101251206", +"通道": "101251207", +"麻阳": "101251208", +"新晃": "101251209", +"芷江": "101251210", +"溆浦": "101251211", +"中方": "101251212", +"洪江": "101251213", +"永州": "101251401", +"祁阳": "101251402", +"东安": "101251403", +"双牌": "101251404", +"道县": "101251405", +"宁远": "101251406", +"江永": "101251407", +"蓝山": "101251408", +"新田": "101251409", +"江华": "101251410", +"冷水滩": "101251411", +"零陵": "101251412", +"吉首": "101251501", +"保靖": "101251502", +"永顺": "101251503", +"古丈": "101251504", +"凤凰": "101251505", +"泸溪": "101251506", +"龙山": "101251507", +"花垣": "101251508", +"湘西": "101251509", +"贵阳": "101260101", +"白云": "101260102", +"花溪": "101260103", +"乌当": "101260104", +"息烽": "101260105", +"开阳": "101260106", +"修文": "101260107", +"清镇": "101260108", +"云岩": "101260110", +"南明": "101260111", +"观山湖": "101260112", +"遵义": "101260201", +"仁怀": "101260203", +"绥阳": "101260204", +"湄潭": "101260205", +"凤冈": "101260206", +"桐梓": "101260207", +"赤水": "101260208", +"习水": "101260209", +"道真": "101260210", +"正安": "101260211", +"务川": "101260212", +"余庆": "101260213", +"汇川": "101260214", +"红花岗": "101260215", +"播州": "101260216", +"安顺": "101260301", +"普定": "101260302", +"镇宁": "101260303", +"平坝": "101260304", +"紫云": "101260305", +"关岭": "101260306", +"西秀": "101260307", +"都匀": "101260401", +"贵定": "101260402", +"瓮安": "101260403", +"长顺": "101260404", +"福泉": "101260405", +"惠水": "101260406", +"龙里": "101260407", +"罗甸": "101260408", +"平塘": "101260409", +"独山": "101260410", +"三都": "101260411", +"荔波": "101260412", +"黔南": "101260413", +"凯里": "101260501", +"岑巩": "101260502", +"施秉": "101260503", +"镇远": "101260504", +"黄平": "101260505", +"黔东南": "101260506", +"麻江": "101260507", +"丹寨": "101260508", +"三穗": "101260509", +"台江": "101260510", +"剑河": "101260511", +"雷山": "101260512", +"黎平": "101260513", +"天柱": "101260514", +"锦屏": "101260515", +"榕江": "101260516", +"从江": "101260517", +"铜仁": "101260601", +"江口": "101260602", +"玉屏": "101260603", +"万山": "101260604", +"思南": "101260605", +"碧江": "101260606", +"印江": "101260607", +"石阡": "101260608", +"沿河": "101260609", +"德江": "101260610", +"松桃": "101260611", +"毕节": "101260701", +"赫章": "101260702", +"金沙": "101260703", +"威宁": "101260704", +"大方": "101260705", +"纳雍": "101260706", +"织金": "101260707", +"黔西": "101260708", +"七星关": "101260709", +"水城": "101260801", +"六枝": "101260802", +"六盘水": "101260803", +"盘州": "101260804", +"钟山": "101260805", +"兴义": "101260901", +"晴隆": "101260902", +"兴仁": "101260903", +"贞丰": "101260904", +"望谟": "101260905", +"黔西南": "101260906", +"安龙": "101260907", +"册亨": "101260908", +"普安": "101260909", +"成都": "101270101", +"龙泉驿": "101270102", +"新都": "101270103", +"温江": "101270104", +"金堂": "101270105", +"双流": "101270106", +"郫都": "101270107", +"大邑": "101270108", +"蒲江": "101270109", +"新津": "101270110", +"都江堰": "101270111", +"彭州": "101270112", +"邛崃": "101270113", +"崇州": "101270114", +"青白江": "101270115", +"锦江": "101270116", +"青羊": "101270117", +"金牛": "101270118", +"武侯": "101270119", +"成华": "101270120", +"简阳": "101270121", +"攀枝花": "101270201", +"仁和": "101270202", +"米易": "101270203", +"盐边": "101270204", +"东区": "101270205", +"西区": "101270206", +"自贡": "101270301", +"富顺": "101270302", +"荣县": "101270303", +"自流井": "101270304", +"贡井": "101270305", +"大安": "101270306", +"沿滩": "101270307", +"绵阳": "101270401", +"三台": "101270402", +"盐亭": "101270403", +"梓潼": "101270405", +"北川": "101270406", +"平武": "101270407", +"江油": "101270408", +"涪城": "101270409", +"游仙": "101270410", +"安州": "101270411", +"南充": "101270501", +"南部": "101270502", +"营山": "101270503", +"蓬安": "101270504", +"仪陇": "101270505", +"西充": "101270506", +"阆中": "101270507", +"顺庆": "101270508", +"高坪": "101270509", +"嘉陵": "101270510", +"达州": "101270601", +"宣汉": "101270602", +"开江": "101270603", +"大竹": "101270604", +"渠县": "101270605", +"万源": "101270606", +"通川": "101270607", +"达川": "101270608", +"遂宁": "101270701", +"蓬溪": "101270702", +"射洪": "101270703", +"船山": "101270704", +"安居": "101270705", +"大英": "101270706", +"广安": "101270801", +"岳池": "101270802", +"武胜": "101270803", +"邻水": "101270804", +"华蓥": "101270805", +"前锋": "101270806", +"广安区": "101270807", +"巴中": "101270901", +"通江": "101270902", +"南江": "101270903", +"平昌": "101270904", +"巴州": "101270905", +"恩阳": "101270906", +"泸州": "101271001", +"江阳": "101271002", +"泸县": "101271003", +"合江": "101271004", +"叙永": "101271005", +"古蔺": "101271006", +"纳溪": "101271007", +"龙马潭": "101271008", +"宜宾": "101271101", +"翠屏": "101271102", +"叙州区": "101271103", +"南溪": "101271104", +"江安": "101271105", +"长宁": "101271106", +"高县": "101271107", +"珙县": "101271108", +"筠连": "101271109", +"兴文": "101271110", +"屏山": "101271111", +"内江": "101271201", +"东兴": "101271202", +"威远": "101271203", +"资中": "101271204", +"隆昌": "101271205", +"市中": "101271206", +"资阳": "101271301", +"安岳": "101271302", +"乐至": "101271303", +"雁江": "101271305", +"乐山": "101271401", +"犍为": "101271402", +"井研": "101271403", +"夹江": "101271404", +"沐川": "101271405", +"峨边": "101271406", +"马边": "101271407", +"市中": "101271410", +"沙湾": "101271411", +"五通桥": "101271412", +"金口河": "101271413", +"峨眉山市": "101271414", +"眉山": "101271501", +"仁寿": "101271502", +"彭山": "101271503", +"洪雅": "101271504", +"丹棱": "101271505", +"青神": "101271506", +"东坡": "101271507", +"凉山": "101271601", +"木里": "101271603", +"盐源": "101271604", +"德昌": "101271605", +"会理": "101271606", +"会东": "101271607", +"宁南": "101271608", +"普格": "101271609", +"西昌": "101271610", +"金阳": "101271611", +"昭觉": "101271612", +"喜德": "101271613", +"冕宁": "101271614", +"越西": "101271615", +"甘洛": "101271616", +"雷波": "101271617", +"美姑": "101271618", +"布拖": "101271619", +"雅安": "101271701", +"名山": "101271702", +"荥经": "101271703", +"汉源": "101271704", +"石棉": "101271705", +"天全": "101271706", +"芦山": "101271707", +"宝兴": "101271708", +"雨城": "101271709", +"甘孜": "101271801", +"康定": "101271802", +"泸定": "101271803", +"丹巴": "101271804", +"九龙": "101271805", +"雅江": "101271806", +"道孚": "101271807", +"炉霍": "101271808", +"新龙": "101271809", +"德格": "101271810", +"白玉": "101271811", +"石渠": "101271812", +"色达": "101271813", +"理塘": "101271814", +"巴塘": "101271815", +"乡城": "101271816", +"稻城": "101271817", +"得荣": "101271818", +"甘孜县": "101271819", +"阿坝": "101271901", +"汶川": "101271902", +"理县": "101271903", +"茂县": "101271904", +"松潘": "101271905", +"九寨沟": "101271906", +"金川": "101271907", +"小金": "101271908", +"黑水": "101271909", +"马尔康": "101271910", +"壤塘": "101271911", +"若尔盖": "101271912", +"红原": "101271913", +"阿坝县": "101271914", +"德阳": "101272001", +"中江": "101272002", +"广汉": "101272003", +"什邡": "101272004", +"绵竹": "101272005", +"罗江": "101272006", +"旌阳": "101272007", +"广元": "101272101", +"旺苍": "101272102", +"青川": "101272103", +"剑阁": "101272104", +"苍溪": "101272105", +"利州": "101272106", +"昭化": "101272107", +"朝天": "101272108", +"广州": "101280101", +"番禺": "101280102", +"从化": "101280103", +"增城": "101280104", +"花都": "101280105", +"荔湾": "101280106", +"越秀": "101280107", +"海珠": "101280108", +"天河": "101280109", +"白云": "101280110", +"黄埔": "101280111", +"南沙": "101280112", +"韶关": "101280201", +"乳源": "101280202", +"始兴": "101280203", +"翁源": "101280204", +"乐昌": "101280205", +"仁化": "101280206", +"南雄": "101280207", +"新丰": "101280208", +"曲江": "101280209", +"浈江": "101280210", +"武江": "101280211", +"惠州": "101280301", +"博罗": "101280302", +"惠阳": "101280303", +"惠东": "101280304", +"龙门": "101280305", +"惠城": "101280306", +"梅州": "101280401", +"兴宁": "101280402", +"蕉岭": "101280403", +"大埔": "101280404", +"梅江": "101280405", +"丰顺": "101280406", +"平远": "101280407", +"五华": "101280408", +"梅县": "101280409", +"汕头": "101280501", +"潮阳": "101280502", +"澄海": "101280503", +"南澳": "101280504", +"龙湖": "101280505", +"金平": "101280506", +"濠江": "101280507", +"潮南": "101280508", +"深圳": "101280601", +"罗湖": "101280602", +"福田": "101280603", +"南山": "101280604", +"宝安": "101280605", +"龙岗": "101280606", +"盐田": "101280607", +"龙华": "101280608", +"坪山": "101280609", +"光明": "101280610", +"珠海": "101280701", +"斗门": "101280702", +"金湾": "101280703", +"香洲": "101280704", +"佛山": "101280800", +"顺德": "101280801", +"三水": "101280802", +"南海": "101280803", +"高明": "101280804", +"禅城": "101280805", +"肇庆": "101280901", +"广宁": "101280902", +"四会": "101280903", +"端州": "101280904", +"德庆": "101280905", +"怀集": "101280906", +"封开": "101280907", +"高要": "101280908", +"鼎湖": "101280909", +"湛江": "101281001", +"吴川": "101281002", +"雷州": "101281003", +"徐闻": "101281004", +"廉江": "101281005", +"赤坎": "101281006", +"遂溪": "101281007", +"坡头": "101281008", +"霞山": "101281009", +"麻章": "101281010", +"江门": "101281101", +"开平": "101281103", +"新会": "101281104", +"恩平": "101281105", +"台山": "101281106", +"蓬江": "101281107", +"鹤山": "101281108", +"江海": "101281109", +"河源": "101281201", +"紫金": "101281202", +"连平": "101281203", +"和平": "101281204", +"龙川": "101281205", +"东源": "101281206", +"源城": "101281207", +"清远": "101281301", +"连南": "101281302", +"连州": "101281303", +"连山": "101281304", +"阳山": "101281305", +"佛冈": "101281306", +"英德": "101281307", +"清新": "101281308", +"清城": "101281309", +"云浮": "101281401", +"罗定": "101281402", +"新兴": "101281403", +"郁南": "101281404", +"云城": "101281405", +"云安": "101281406", +"潮州": "101281501", +"饶平": "101281502", +"潮安": "101281503", +"湘桥": "101281504", +"东莞": "101281601", +"中山": "101281701", +"阳江": "101281801", +"阳春": "101281802", +"阳东": "101281803", +"阳西": "101281804", +"江城": "101281805", +"揭阳": "101281901", +"揭西": "101281902", +"普宁": "101281903", +"惠来": "101281904", +"揭东": "101281905", +"榕城": "101281906", +"茂名": "101282001", +"高州": "101282002", +"化州": "101282003", +"电白": "101282004", +"信宜": "101282005", +"茂南": "101282007", +"汕尾": "101282101", +"海丰": "101282102", +"陆丰": "101282103", +"陆河": "101282104", +"城区": "101282105", +"昆明": "101290101", +"五华": "101290102", +"东川": "101290103", +"寻甸": "101290104", +"晋宁": "101290105", +"宜良": "101290106", +"石林": "101290107", +"呈贡": "101290108", +"富民": "101290109", +"嵩明": "101290110", +"禄劝": "101290111", +"安宁": "101290112", +"盘龙": "101290114", +"官渡": "101290115", +"西山": "101290116", +"大理州": "101290201", +"云龙": "101290202", +"漾濞": "101290203", +"永平": "101290204", +"宾川": "101290205", +"弥渡": "101290206", +"祥云": "101290207", +"巍山": "101290208", +"剑川": "101290209", +"洱源": "101290210", +"鹤庆": "101290211", +"南涧": "101290212", +"大理市": "101290213", +"红河": "101290301", +"石屏": "101290302", +"建水": "101290303", +"弥勒": "101290304", +"元阳": "101290305", +"绿春": "101290306", +"开远": "101290307", +"个旧": "101290308", +"蒙自": "101290309", +"屏边": "101290310", +"泸西": "101290311", +"金平": "101290312", +"河口": "101290313", +"红河县": "101290314", +"曲靖": "101290401", +"沾益": "101290402", +"陆良": "101290403", +"富源": "101290404", +"马龙": "101290405", +"师宗": "101290406", +"罗平": "101290407", +"会泽": "101290408", +"宣威": "101290409", +"麒麟": "101290410", +"保山": "101290501", +"隆阳": "101290502", +"龙陵": "101290503", +"施甸": "101290504", +"昌宁": "101290505", +"腾冲": "101290506", +"文山州": "101290601", +"西畴": "101290602", +"马关": "101290603", +"麻栗坡": "101290604", +"砚山": "101290605", +"丘北": "101290606", +"广南": "101290607", +"富宁": "101290608", +"文山市": "101290609", +"玉溪": "101290701", +"澄江": "101290702", +"江川": "101290703", +"通海": "101290704", +"华宁": "101290705", +"新平": "101290706", +"易门": "101290707", +"峨山": "101290708", +"元江": "101290709", +"红塔": "101290710", +"楚雄州": "101290801", +"大姚": "101290802", +"元谋": "101290803", +"姚安": "101290804", +"牟定": "101290805", +"南华": "101290806", +"武定": "101290807", +"禄丰": "101290808", +"双柏": "101290809", +"永仁": "101290810", +"楚雄市": "101290811", +"普洱": "101290901", +"景谷": "101290902", +"景东": "101290903", +"澜沧": "101290904", +"思茅": "101290905", +"墨江": "101290906", +"江城": "101290907", +"孟连": "101290908", +"西盟": "101290909", +"镇沅": "101290911", +"宁洱": "101290912", +"昭通": "101291001", +"鲁甸": "101291002", +"彝良": "101291003", +"镇雄": "101291004", +"威信": "101291005", +"巧家": "101291006", +"绥江": "101291007", +"永善": "101291008", +"盐津": "101291009", +"大关": "101291010", +"水富": "101291011", +"昭阳": "101291012", +"临沧": "101291101", +"沧源": "101291102", +"耿马": "101291103", +"双江": "101291104", +"凤庆": "101291105", +"永德": "101291106", +"云县": "101291107", +"镇康": "101291108", +"临翔": "101291109", +"怒江": "101291201", +"福贡": "101291203", +"兰坪": "101291204", +"泸水": "101291205", +"贡山": "101291207", +"香格里拉": "101291301", +"德钦": "101291302", +"维西": "101291303", +"迪庆": "101291305", +"丽江": "101291401", +"永胜": "101291402", +"华坪": "101291403", +"宁蒗": "101291404", +"古城": "101291405", +"玉龙": "101291406", +"德宏": "101291501", +"陇川": "101291503", +"盈江": "101291504", +"瑞丽": "101291506", +"梁河": "101291507", +"芒市": "101291508", +"景洪": "101291601", +"西双版纳": "101291602", +"勐海": "101291603", +"勐腊": "101291605", +"南宁": "101300101", +"兴宁": "101300102", +"邕宁": "101300103", +"横州": "101300104", +"隆安": "101300105", +"马山": "101300106", +"上林": "101300107", +"武鸣": "101300108", +"宾阳": "101300109", +"青秀": "101300110", +"江南": "101300111", +"西乡塘": "101300112", +"良庆": "101300113", +"崇左": "101300201", +"天等": "101300202", +"龙州": "101300203", +"凭祥": "101300204", +"大新": "101300205", +"扶绥": "101300206", +"宁明": "101300207", +"江州": "101300208", +"柳州": "101300301", +"柳城": "101300302", +"城中": "101300303", +"鹿寨": "101300304", +"柳江": "101300305", +"融安": "101300306", +"融水": "101300307", +"三江": "101300308", +"鱼峰": "101300309", +"柳南": "101300310", +"柳北": "101300311", +"来宾": "101300401", +"忻城": "101300402", +"金秀": "101300403", +"象州": "101300404", +"武宣": "101300405", +"合山": "101300406", +"兴宾": "101300407", +"桂林": "101300501", +"秀峰": "101300502", +"龙胜": "101300503", +"永福": "101300504", +"临桂": "101300505", +"兴安": "101300506", +"灵川": "101300507", +"全州": "101300508", +"灌阳": "101300509", +"阳朔": "101300510", +"恭城": "101300511", +"平乐": "101300512", +"荔浦": "101300513", +"资源": "101300514", +"叠彩": "101300515", +"象山": "101300516", +"七星": "101300517", +"雁山": "101300518", +"梧州": "101300601", +"藤县": "101300602", +"万秀": "101300603", +"苍梧": "101300604", +"蒙山": "101300605", +"岑溪": "101300606", +"长洲": "101300607", +"龙圩": "101300608", +"贺州": "101300701", +"昭平": "101300702", +"富川": "101300703", +"钟山": "101300704", +"八步": "101300705", +"平桂": "101300706", +"贵港": "101300801", +"桂平": "101300802", +"平南": "101300803", +"港北": "101300804", +"港南": "101300805", +"覃塘": "101300806", +"玉林": "101300901", +"博白": "101300902", +"北流": "101300903", +"容县": "101300904", +"陆川": "101300905", +"兴业": "101300906", +"玉州": "101300907", +"福绵": "101300908", +"百色": "101301001", +"那坡": "101301002", +"田阳": "101301003", +"德保": "101301004", +"靖西": "101301005", +"田东": "101301006", +"平果": "101301007", +"隆林": "101301008", +"西林": "101301009", +"乐业": "101301010", +"凌云": "101301011", +"田林": "101301012", +"右江": "101301013", +"钦州": "101301101", +"浦北": "101301102", +"灵山": "101301103", +"钦南": "101301104", +"钦北": "101301105", +"河池": "101301201", +"天峨": "101301202", +"东兰": "101301203", +"巴马": "101301204", +"环江": "101301205", +"罗城": "101301206", +"宜州": "101301207", +"凤山": "101301208", +"南丹": "101301209", +"都安": "101301210", +"大化": "101301211", +"金城江": "101301212", +"北海": "101301301", +"合浦": "101301302", +"海城": "101301304", +"银海": "101301305", +"铁山港": "101301306", +"防城港": "101301401", +"上思": "101301402", +"东兴": "101301403", +"港口": "101301404", +"防城": "101301405", +"海口": "101310101", +"秀英": "101310102", +"龙华": "101310103", +"琼山": "101310104", +"美兰": "101310105", +"三亚": "101310201", +"东方": "101310202", +"临高": "101310203", +"澄迈": "101310204", +"儋州": "101310205", +"昌江": "101310206", +"白沙": "101310207", +"琼中": "101310208", +"定安": "101310209", +"屯昌": "101310210", +"琼海": "101310211", +"文昌": "101310212", +"海棠": "101310213", +"保亭": "101310214", +"万宁": "101310215", +"陵水": "101310216", +"吉阳": "101310218", +"天涯": "101310219", +"乐东": "101310221", +"五指山": "101310222", +"崖州": "101310223", +"三沙": "101310301", +"西沙": "101310302", +"南沙": "101310304", +"香港": "101320101", +"新界": "101320103", +"油尖旺": "14606", +"东区": "1B6D3", +"中西区": "1D255", +"屯门": "1DC87", +"大埔": "275A5", +"深水埗": "28FE1", +"九龙城": "2BBD1", +"葵青": "2BC09", +"元朗": "39CD9", +"湾仔": "407DA", +"西贡": "4622E", +"沙田": "55E7E", +"观塘": "8A9CA", +"北区": "8E1C5", +"离岛": "9173", +"黄大仙": "D5EC3", +"荃湾": "DD9B5", +"南区": "E87DC", +"澳门": "101330101", +"氹仔岛": "101330102", +"路环岛": "101330103", +"路凼城": "1444B", +"风顺堂区": "2E3F1", +"嘉模堂区": "4B3A7", +"望德堂区": "5102", +"大堂区": "7C47E", +"圣方济各堂区": "83145", +"花王堂区": "AEF10", +"花地玛堂区": "D3B7A", +"钓鱼岛": "101231001", +"台北": "101340101", +"桃园": "101340102", +"新竹": "101340103", +"宜兰市": "101340104", +"高雄": "101340201", +"嘉义市": "101340202", +"台南市": "101340203", +"台东市": "101340204", +"屏东市": "101340205", +"台中市": "101340401", +"苗栗市": "101340402", +"彰化市": "101340403", +"南投市": "101340404", +"花莲市": "101340405", +"来义乡": "1064D", +"楠西区": "1131A", +"冬山乡": "125A0", +"后里区": "12705", +"鸟松区": "12AC3", +"林口区": "1353E", +"太平区": "14A62", +"秀林乡": "14B6", +"关山镇": "15F5F", +"中正区": "15FC8", +"中西区": "16946", +"朴子市": "1851B", +"苓雅区": "1860C", +"峨眉乡": "198CA", +"莺歌区": "1A131", +"丰滨乡": "1AFB5", +"大安区": "1BD97", +"卑南乡": "1C2F9", +"清水区": "1C370", +"三峽区": "1CB96", +"茂林区": "1D33", +"高树乡": "1EA6F", +"九如乡": "1ECA0", +"仁爱乡": "1F7E2", +"深坑区": "1F906", +"阿莲区": "1FCAC", +"乌日区": "2.2E+51", +"东势乡": "2008A", +"伸港乡": "2045E", +"士林区": "215EB", +"龙井区": "232B2", +"五结乡": "25B67", +"埔心乡": "27297", +"大肚区": "274A6", +"田寮区": "2787E", +"水林乡": "2897F", +"芦竹区": "28C50", +"竹崎乡": "2A0EA", +"平镇区": "2A17C", +"梓官区": "2A7A9", +"南化区": "2ADEC", +"官田区": "2B8F1", +"牡丹乡": "2BD74", +"土城区": "2BF61", +"暖暖区": "2CA40", +"新埔镇": "2CEDF", +"口湖乡": "2DD33", +"关庙区": "2DF8D", +"长滨乡": "2E+301", +"埤头乡": "2F677", +"香山区": "300A2", +"仁德区": "30712", +"苏澳镇": "30EB5", +"海端乡": "33D54", +"北投区": "34272", +"阿里山乡": "34B85", +"泰安乡": "3561E", +"长治乡": "35EA", +"中和区": "36039", +"集集镇": "36375", +"大村乡": "37981", +"楠梓区": "394EA", +"文山区": "3981F", +"瑞芳区": "39A9E", +"满州乡": "3A2F4", +"安平区": "3A428", +"大里区": "3B30D", +"中埔乡": "3B81C", +"北屯区": "3BD59", +"六脚乡": "3D16B", +"仑背乡": "3EC6B", +"二水乡": "3ED2D", +"七股区": "3FC8E", +"八德区": "4297D", +"溪口乡": "42F72", +"车城乡": "42F75", +"里港乡": "43E4E", +"西湖乡": "441DA", +"桃源区": "442FB", +"梅山乡": "44BA1", +"潭子区": "44BC2", +"狮潭乡": "4515E", +"大城乡": "46843", +"崁顶乡": "4786", +"安南区": "485D2", +"永康区": "48D19", +"复兴区": "490B9", +"古坑乡": "4A86F", +"七堵区": "4A9E4", +"西湖乡": "4B6DC", +"和美镇": "4CA1B", +"美浓区": "4D0CD", +"中山区": "4D45B", +"名间乡": "4D55F", +"佳里区": "4D572", +"杨梅区": "4E71A", +"番路乡": "4E999", +"太保市": "4EB49", +"南区": "4EE12", +"樹林区": "4F7B8", +"员山乡": "4FB12", +"东山区": "4FC38", +"土库镇": "5029C", +"麦寮乡": "503D8", +"八里区": "50460", +"永靖乡": "50886", +"礁溪乡": "52116", +"永安区": "522A8", +"金峰乡": "528C1", +"龟山区": "5327F", +"国姓乡": "53937", +"绿岛乡": "53C0D", +"大湖乡": "545BD", +"田中镇": "55AF3", +"芳苑乡": "56FB2", +"金山区": "5A624", +"布袋镇": "5B01A", +"燕巢区": "5B14E", +"柳营区": "5D1AC", +"竹田乡": "5DCD0", +"平渓区": "5DE8A", +"莿桐乡": "5F054", +"斗南镇": "5F767", +"信义区": "5FA11", +"太麻里乡": "5FFC8", +"玉井区": "601C9", +"新丰乡": "60301", +"义竹乡": "607B7", +"路竹区": "60C6E", +"池上乡": "6149A", +"万里区": "618B3", +"观音区": "62622", +"石门区": "62C94", +"将军区": "640E", +"大埔乡": "64722", +"前金区": "64DF4", +"西港区": "65449", +"南区": "65B59", +"狮子乡": "6781C", +"林园区": "685A4", +"吉安乡": "687D9", +"大寮区": "69041", +"旗山区": "6946C", +"瑞穗乡": "69C7D", +"汐止区": "6A009", +"潮州镇": "6C288", +"佳冬乡": "6D813", +"西区": "6DB7C", +"三湾乡": "6F1CA", +"盐水区": "6F8E8", +"新港乡": "707A5", +"南澳乡": "71A41", +"坪林区": "72D3F", +"横山乡": "7412D", +"南港区": "74604", +"杉林区": "74A65", +"中区": "75272", +"龙崎区": "7527B", +"马公市": "753A2", +"万荣乡": "77487", +"恒春镇": "77836", +"大安区": "77922", +"大甲区": "77B7", +"大树区": "786AD", +"埔里镇": "78C5D", +"新北市": "79754", +"头城镇": "7AC15", +"旗津区": "7AC37", +"东区": "7AD1B", +"白河区": "7B4D7", +"卓兰镇": "7CD4B", +"新城乡": "7CE62", +"宝山乡": "7E+53", +"麻豆区": "7E91B", +"前镇区": "7EF65", +"湖内区": "7F2B8", +"石冈区": "7F7CD", +"学甲区": "81AE3", +"枋山乡": "82644", +"万華区": "8296C", +"草屯镇": "83F45", +"埔盐乡": "849C0", +"尖石乡": "8647F", +"秀水乡": "86861", +"二林镇": "86AEC", +"北斗镇": "87DA", +"斗六市": "884E", +"西屯区": "88DAC", +"盐埕区": "89FCB", +"大园区": "8A667", +"五股区": "8B37C", +"林内乡": "8C73A", +"竹塘乡": "8D63C", +"冈山区": "8E+183", +"万丹乡": "8E9AA", +"三地门乡": "8ECB0", +"竹东镇": "9.6E+32", +"公馆乡": "92813", +"南州乡": "93143", +"内门区": "9346C", +"新社区": "95178", +"苑里镇": "954F5", +"新营区": "955CC", +"三重区": "95B3C", +"达仁乡": "96121", +"雾台乡": "963DA", +"鹿野乡": "97619", +"后龙镇": "97B1A", +"线西乡": "97F69", +"大埤乡": "98DE7", +"大内区": "98EF7", +"台西乡": "9AB11", +"北区": "9B388", +"乌来区": "9B92A", +"望安乡": "9C6B3", +"永和区": "9D3E5", +"山上区": "9DAAA", +"溪州乡": "9DD7F", +"新化区": "9E358", +"芬园乡": "9E5DD", +"社头乡": "9F928", +"松山区": "A033A", +"湖西乡": "A06E6", +"安定区": "A3EFD", +"通霄镇": "A503E", +"六龟区": "A7AA7", +"雾峰区": "A7B14", +"田尾乡": "A9903", +"兰屿乡": "A9A12", +"新园乡": "A9C2C", +"基隆市": "A9E1", +"麟洛乡": "AACD9", +"弥陀区": "AACF5", +"鹿港镇": "AD033", +"员林镇": "AD179", +"万峦乡": "AD515", +"壮围乡": "AE2EB", +"凤山区": "AEA05", +"后壁区": "AF42E", +"造桥乡": "AF6DE", +"东势区": "B1954", +"林边乡": "B1B72", +"大社区": "B25DD", +"沙鹿区": "B263B", +"貢寮区": "B28D3", +"新市区": "B345C", +"四湖乡": "B40F3", +"大同区": "B4131", +"元长乡": "B4967", +"东港镇": "B509A", +"三义乡": "B57C7", +"卓溪乡": "B75BB", +"寿丰乡": "B7A02", +"竹山镇": "B7B31", +"信义乡": "B82B7", +"枋寮乡": "B8DEC", +"新兴区": "B93FB", +"大雅区": "B99C9", +"春日乡": "BA103", +"西螺镇": "BAD4D", +"光复乡": "BB61B", +"梧栖区": "BBB19", +"褒忠乡": "BBE60", +"泰山区": "BCAE1", +"白沙乡": "BCF10", +"南屯区": "BD890", +"三芝区": "BDA5B", +"大武乡": "BE9AC", +"小港区": "BEA9E", +"水里乡": "BF01C", +"淡水区": "BFA38", +"北门区": "BFFEA", +"玛家乡": "C1078", +"竹北市": "C30C", +"中山区": "C346", +"内埔乡": "C4AF1", +"龙潭区": "C4BED", +"外埔区": "C532F", +"罗东镇": "C5445", +"水上乡": "C5473", +"五峰乡": "C87FF", +"头份镇": "CA3", +"凤林镇": "CA83B", +"三民区": "CAE8D", +"六甲区": "CB526", +"善化区": "CB5C1", +"左营区": "CD807", +"北埔乡": "CF68C", +"东势乡": "CFCC4", +"民雄乡": "D05D7", +"西屿乡": "D0809", +"石碇区": "D0B00", +"信义区": "D13AC", +"竹南镇": "D257A", +"七美乡": "D2EBD", +"安乐区": "D3A0D", +"中正区": "D3A1D", +"鹿草乡": "D4650", +"内湖区": "D54D9", +"中坜区": "D6C2D", +"鹿谷乡": "D7481", +"东河乡": "D7D8E", +"甲仙区": "D8B7D", +"蘆洲区": "D9469", +"板桥区": "D96E9", +"大林镇": "D9B1A", +"芎林乡": "DA4C5", +"铜锣乡": "DACF8", +"中寮乡": "DB374", +"二仑乡": "DBA28", +"北区": "DCECC", +"南庄乡": "DE049", +"湖口乡": "DE3B7", +"成功镇": "DE8A4", +"东区": "DED29", +"泰武乡": "DFB95", +"头屋乡": "E00DD", +"那玛夏区": "E3120", +"三星乡": "E427E", +"下营区": "E4AB6", +"左镇区": "E5098", +"虎尾镇": "E5452", +"花坛乡": "E6107", +"福兴乡": "E8B22", +"丰原区": "E8BA8", +"新埤乡": "E9D19", +"茄萣区": "E9D4F", +"新庄区": "E9DC2", +"富里乡": "EA346", +"鱼池乡": "EB721", +"大同乡": "EBC37", +"玉里镇": "EBCEC", +"琉球乡": "EC5FB", +"鼓山区": "ED37A", +"大溪区": "ED785", +"仁武区": "EDE8C", +"延平乡": "EE096", +"关西镇": "EE328", +"和平区": "EED7A", +"新店区": "EFBAC", +"归仁区": "F0DE1", +"仁爱区": "F116A", +"桥头区": "F178D", +"双渓区": "F21E4", +"神冈区": "F2FA2", +"北港镇": "F35CF", +"盐埔乡": "F4F1", +"新屋区": "F69B", +}; + // 发送十六进制字符串(如 "6666tfgf" 转十六进制) +async function sendHexString(wifi_ssd,wifi_pass,city) { + if (!rxtxCharacteristic) { + addLog('❌ 服务不可用,请先连接蓝牙'); + return false; + } + // 或者简写 + if (!chinaCities[city]||!wifi_ssd||!wifi_pass) { + alert(`注意你配置的数据是否正确!账号名:${wifi_ssd},密码:${wifi_pass},城市:${city}!`); + return false; + } + + try { + const encoder = new TextEncoder(); + wifi='WIFI_SSID:'+wifi_ssd+';WIFI_PASS:'+wifi_pass+';CITY:'+chinaCities[city]; + const ssd = encoder.encode(wifi); + await rxtxCharacteristic.writeValueWithResponse(ssd); + addLog(`✅ 发送十六进制: ${wifi} (${ssd.length} bytes)`); + + + return true; + } catch (error) { + addLog(`❌ 发送失败: ${error.message}`); + return false; + } +} + + +const API_BASE_URL = 'http://4638e813e4.qicp.vip:8889/key_generator.php'; + + +function extractActivationCode(response) { + const patterns = [/激活码1[::]\s*([A-Fa-f0-9]{32,})/, /激活码[::]\s*([A-Fa-f0-9]{32,})/, /[A-Fa-f0-9]{32,}/]; + for (let p of patterns) { + const m = response.match(p); + if (m && m[1]) return m[1].replace(/[^A-Fa-f0-9]/g, ''); + } + throw new Error('无法提取激活码'); +} + + +async function requestActivationCode(fixedId, deviceKeyId) { + const url = `${API_BASE_URL}?id=${encodeURIComponent(fixedId)}&key=${encodeURIComponent(deviceKeyId)}`; + addLog(`请求激活码...`); + const res = await fetch(url); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const text = await res.text(); + return extractActivationCode(text); +} + + +async function sendActivationToDevice(code) { + const clean = code.replace(/^EF/i, ''); + const cmd = 'EF' + clean.toUpperCase(); + addLog(`开始激活....`); + + + if (await rxTxSendCommand(hexToBytes(cmd))) { + await delay(400); + await rxTxSendCommand(hexToBytes('e2')); + addLog('激活完成'); + return true; + } + return false; +} + + +let isWaitingForDeviceId = false; +let deviceIdResolve = null; +let pendingIdBytes = []; + +function getDeviceIdFromScreen() { + return new Promise(async (resolve, reject) => { + if (!rxtxCharacteristic) return reject('蓝牙未连接'); + + isWaitingForDeviceId = true; + pendingIdBytes = []; + deviceIdResolve = resolve; + + const timeout = setTimeout(() => { + isWaitingForDeviceId = false; + deviceIdResolve = null; + reject('读取设备ID超时'); + }, 8000); + + addLog('开始获取设备ID!'); + await rxTxSendCommand(hexToBytes('EFEF')); + + + window._deviceIdTimeout = timeout; + }); +} + + +async function fullAutoActivate() { + if (!rxtxCharacteristic) { addLog('请先连接蓝牙'); return; } + const fixedId = document.getElementById('fixedId').value.trim(); + if (!fixedId) { addLog('请输入固定ID'); return; } + + addLog('=== 开始一键激活 ==='); + try { + const deviceId = await getDeviceIdFromScreen(); + + const code = await requestActivationCode(fixedId, deviceId); + addLog(`获取到激活码: ${code}`); + + await sendActivationToDevice(code); + addLog('✅ 激活成功'); + await delay(500); + await rxTxSendCommand(hexToBytes('e2')) + } catch(e) { + addLog(`❌ 失败: ${e.message}`); + } finally { + clearTimeout(window._deviceIdTimeout); + isWaitingForDeviceId = false; + pendingIdBytes = []; + } +} + async function setri(cmd) { + addLog(`指令: ${cmd}`) + await rxTxSendCommand(hexToBytes(cmd)); + await delay(300); + addLog(`指令: e2`) + await rxTxSendCommand(hexToBytes('e2')) + } + async function triggerRxTxCmd(cmd) { + addLog(`发送指令: ${cmd}`) + await rxTxSendCommand(hexToBytes(cmd)); + } + async function triggerRxTxCmd2(cmd) { + addLog(`发送指令: ${cmd}`) + await rxTxSendCommand2(hexToBytes(cmd)); + } + async function triggerEpdCmd(cmd) { + addLog(`发送指令: ${cmd}`) + await sendCommand(hexToBytes(cmd)); + } + async function triggerEpdRed_lut(cmd) { + if(cmd==0) return; + addLog(`开始红色校准: ${cmd}`) + cmd = 'E6' + cmd; + await rxTxSendCommand(hexToBytes(cmd)); + } + async function triggerEpdJH(cmd) { + addLog(`激活指令: ${cmd}`) + cmd = 'EF' + cmd; + await rxTxSendCommand(hexToBytes(cmd)); + await delay(500); + await rxTxSendCommand(hexToBytes('e2')) + } + async function triggerEpdCHEPAI(cmd) { + addLog(`号码: ${cmd}`) + let cp = ''; + for (let index = 0; index < cmd.length; index++) { + cp = cp + cmd[index] + cmd[index]; + } + cp = 'EF' + cp; + await rxTxSendCommand(hexToBytes(cp)); + await delay(500); + await rxTxSendCommand(hexToBytes('e2')) + } + async function triggerRxTxCmd_LED(cmd) { + let color = document.getElementById('led_color').value; + let D_S_hour = document.getElementById('D_S_hour').value; + let D_S_min = document.getElementById('D_S_min').value; + D_S_hour = D_S_hour > 9 ? D_S_hour : '0' + D_S_hour; + D_S_min = D_S_min > 9 ? D_S_min : '0' + D_S_min; + let note = color != '07' ? '01' : '00'; + cmd = cmd + note + color + D_S_hour + D_S_min; + addLog(`LED: ${cmd}`) + await rxTxSendCommand(hexToBytes(cmd)); + } + async function triggerRxTxCmd_XM(cmd) { + + let D_S_hour = document.getElementById('D_S_hour').value; + let D_S_min = document.getElementById('D_S_min').value; + D_S_hour = D_S_hour > 9 ? D_S_hour : '0' + D_S_hour; + D_S_min = D_S_min > 9 ? D_S_min : '0' + D_S_min; + + cmd = cmd + D_S_hour + D_S_min; + addLog(`设置不刷新时间段: ${cmd}`) + await rxTxSendCommand(hexToBytes(cmd)); + } + + async function triggerRxTxCmd_LB(cmd) { + + let L_B_H = document.getElementById('L_B_NUM').value; + const hexDigits = '0123456789ABCDEF'; + let hex = ''; + let num = L_B_H; + + while (num > 0) { + const remainder = num % 16; + hex = hexDigits[remainder] + hex; + num = Math.floor(num / 16); + } + + cmd = 'e901'+ hex +cmd; + addLog(`设置图片轮播: ${cmd}`) + await rxTxSendCommand(hexToBytes(cmd)); + } + + function disconnect() { + resetVariables(); + addLog('链接已断开.'); + document.getElementById("connectbutton").innerHTML = '链接'; + } + async function preConnect() { + if (gattServer != null && gattServer.connected) { + if (bleDevice != null && bleDevice.gatt.connected) + bleDevice.gatt.disconnect(); + } else { + reconnectTrys = 0; + bleDevice = await navigator.bluetooth.requestDevice({ + filters: [ + { namePrefix: 'NRF-' } + ], optionalServices: ['0000221f-0000-1000-8000-00805f9b34fb', '00001f10-0000-1000-8000-00805f9b34fb', '13187b10-eba9-a3ba-044e-83d3217d9a38'] + }); + await bleDevice.addEventListener('gattserverdisconnected', disconnect); + try { + await connect(); + } catch (e) { + await handleError(e); + } + } + } + function formatReceivedData(data) { + let buffer; + + if (data instanceof DataView) { + buffer = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength); + } else if (data instanceof ArrayBuffer) { + buffer = data; + } else if (data instanceof Uint8Array) { + buffer = data.buffer; + } else { + return "(不支持的数据类型)"; + } + + const bytes = new Uint8Array(buffer); + if (bytes.length === 0) return "(空数据)"; + + let hexPart = Array.from(bytes).map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(' '); + let asciiPart = ""; + for (let i = 0; i < Math.min(bytes.length, 64); i++) { + let c = bytes[i]; + if (c >= 32 && c <= 126) asciiPart += String.fromCharCode(c); + else asciiPart += '.'; + } + if (bytes.length > 64) asciiPart += "…"; + + let textPart = ""; + try { + const decoder = new TextDecoder('utf-8'); + const text = decoder.decode(bytes); + if (/[\x20-\x7E\u4e00-\u9fa5]/.test(text)) { + textPart = `\n TEXT: ${text}`; + } + } catch(e) {} + + return `[${bytes.length} 字节] HEX: ${hexPart}\n ASCII: ${asciiPart}${textPart}`; +} + async function connectRXTX() { + rxtxService = await gattServer.getPrimaryService('00001f10-0000-1000-8000-00805f9b34fb'); + addLog('> rxtxService 找到串口服务'); + rxtxCharacteristic = await rxtxService.getCharacteristic('00001f1f-0000-1000-8000-00805f9b34fb'); + addLog('> rxtxCharacteristic 串口服务已链接'); + + + // ========== 新增:启用 RXTX 通知监听 ========== + await rxtxCharacteristic.startNotifications(); + rxtxCharacteristic.addEventListener('characteristicvaluechanged', (event) => { + const value = event.target.value; + const bytes = new Uint8Array(value.buffer, value.byteOffset, value.byteLength); + const hexStr = Array.from(bytes).map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(' '); + const textStr = new TextDecoder('utf-8').decode(bytes); + + addLog(`📥 [RXTX接收] HEX: ${hexStr}`); + if (textStr.trim()) { + addLog(`📥 [RXTX接收] TEXT: ${textStr}`); + } + + // 如果有等待设备ID的回调,处理它 + if (isWaitingForDeviceId && deviceIdResolve) { + for (let b of bytes) pendingIdBytes.push(b); + const EXPECTED_ID_BYTES = 14; + if (pendingIdBytes.length >= EXPECTED_ID_BYTES) { + clearTimeout(window._deviceIdTimeout); + const idBytes = pendingIdBytes.slice(0, EXPECTED_ID_BYTES); + let id = ''; + for (let b of idBytes) { + id += (b >= 32 && b <= 126) ? String.fromCharCode(b) : b.toString(16).padStart(2,'0').toUpperCase(); + } + id = id.replace(/[^A-Fa-f0-9]/g, '') || bytesToHex(idBytes.buffer); + addLog(`解析到设备ID: ${id}`); + isWaitingForDeviceId = false; + deviceIdResolve(id); + deviceIdResolve = null; + pendingIdBytes = []; + } + return; + } + // 将数据显示在界面上(如果有显示区域) + const rxtxRxDiv = document.getElementById('rxtxRxData'); + if (rxtxRxDiv) { + const formatted = formatReceivedData(value); + rxtxRxDiv.innerHTML = `📎 接收数据:\n${formatted}`; + } + }); + addLog('> rxtxCharacteristic 通知已启用'); + + } + async function reConnect() { + 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) { + addLog("正在链接: " + bleDevice.name); + gattServer = await bleDevice.gatt.connect(); + addLog('> 找到GATT服务器'); + epdService = await gattServer.getPrimaryService('13187b10-eba9-a3ba-044e-83d3217d9a38'); + addLog('> epdService 找到可用服务'); + epdCharacteristic = await epdService.getCharacteristic('4b646063-6264-f3a7-8941-e65356ea82fe'); + addLog('> epdCharacteristic 服务已连接'); + await epdCharacteristic.startNotifications(); + epdCharacteristic.addEventListener('characteristicvaluechanged', (event) => { + const bytes = new Uint8Array(event.target.value.buffer); + const hexStr = bytesToHex(event.target.value.buffer); + console.log('epd ret', hexStr); + + + if (isWaitingForDeviceId && deviceIdResolve) { + for (let b of bytes) pendingIdBytes.push(b); + + const EXPECTED_ID_BYTES = 14; + if (pendingIdBytes.length >= EXPECTED_ID_BYTES) { + clearTimeout(window._deviceIdTimeout); + + const idBytes = pendingIdBytes.slice(0, EXPECTED_ID_BYTES); + let id = ''; + for (let b of idBytes) { + + id += (b >= 32 && b <= 126) ? String.fromCharCode(b) : b.toString(16).padStart(2,'0').toUpperCase(); + } + + id = id.replace(/[^A-Fa-f0-9]/g, '') || bytesToHex(idBytes.buffer); + + addLog(`解析到设备ID: ${id}`); + isWaitingForDeviceId = false; + deviceIdResolve(id); + deviceIdResolve = null; + pendingIdBytes = []; + } + return; + } + // ---------------------------------------------------- + + + const count = parseInt('0x' + hexStr); + if (!isNaN(count)) { + addLog(`> [来自屏幕]: 收到${count} byte数据!值:${hexStr}!`); + } +}); + document.getElementById("connectbutton").innerHTML = '断开'; + await connectRXTX(); + } + } + function setStatus(statusText) { + document.getElementById("status").innerHTML = statusText; + } + function addLog(logTXT) { + const today = new Date(); + const time = ("0" + today.getHours()).slice(-2) + ":" + ("0" + today.getMinutes()).slice(-2) + ":" + ("0" + today.getSeconds()).slice(-2) + " : "; + const dom = document.getElementById("log"); + dom.innerHTML += time + logTXT + '
'; + dom.scrollTop = dom.scrollHeight; + } + function getUnixTime() { + const hourOffset = 0;//document.getElementById('hour-offset').value; + const unixNow = Math.round(Date.now() / 1000) + (60 * 60 * hourOffset) - new Date().getTimezoneOffset() * 60; + const date = new Date((unixNow + new Date().getTimezoneOffset() * 60) * 1000); + const localeTimeString = date.toLocaleTimeString(); + return { unixNow, localeTimeString, year: date.getFullYear(), month: date.getMonth() + 1, day: date.getDate(), week: date.getDay() || 7 } + } + let currentRotation = 0; + let currentImage = null; + let degrees1 = 0; + function rotateImage(degrees) { + if(degrees){ + currentRotation = (currentRotation + degrees) % 360;} + else {currentRotation =0;} + degrees1 = currentRotation; + if (currentImage) { + drawImageWithRotation(currentImage); + update_image(); + } + } + let currentScaleX = 1.0; + let currentScaleY = 1.0; + function stretchToCanvas() { + if (!currentImage) return; + const canvas = document.getElementById("canvas"); + if (degrees1 == 0 || degrees1 == 180) { + currentScaleX = canvas.width / currentImage.width; + currentScaleY= canvas.height / currentImage.height; + } else { + currentScaleX = canvas.height/ currentImage.width; + currentScaleY = canvas.width / currentImage.height; + } + + updateScaleDisplay(); + drawImageWithRotation(currentImage); + update_image(); + } + function updateScale(value) { + currentScale = value / 100; + updateScaleDisplay(); + if (currentImage) { + drawImageWithRotation(currentImage); + update_image(); + } + } + let currentScale = 1.0; + function updateScaleDisplay() { + document.getElementById('scaleValue').textContent = `${Math.round(currentScale * 100)}%`; + document.getElementById('scaleSlider').value = currentScale * 100; + } + let imageSource = null; + let imageSource1 = null; + async function update_image1() { + imageSource1=null; + update_image(); + + } + async function update_image(imageSource) { + const image_file = document.getElementById('image_file'); + if(imageSource)imageSource1 =imageSource; + if (imageSource1){ + const canvas = document.getElementById("canvas"); + const ctx = canvas.getContext("2d"); + const image = new Image(); + image.src = imageSource1; + image.onload = function (event) { + URL.revokeObjectURL(this.src); + currentImage = image; + drawImageWithRotation(image); + applyImageAdjustments(canvas); + convert_dithering(canvas ); + + } + return; + } + if (image_file.files.length > 0) { + const file = image_file.files[0]; + const canvas = document.getElementById("canvas"); + const ctx = canvas.getContext("2d"); + const image = new Image(); + image.src = URL.createObjectURL(file); + image.onload = function (event) { + URL.revokeObjectURL(this.src); + currentImage = image; + drawImageWithRotation(image); + applyImageAdjustments(canvas); + convert_dithering(canvas ); + } + } + } + function updateRotation(value) { + currentRotation = parseInt(value); + document.getElementById('rotationValue').textContent = value + '°'; + if (currentImage) { + drawImageWithRotation(currentImage); + update_image(); + } + } + function drawImageWithRotation(image) { + const canvas = document.getElementById("canvas"); + const ctx = canvas.getContext("2d"); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.save(); + ctx.translate(canvas.width / 2, canvas.height / 2); + ctx.rotate(currentRotation * Math.PI / 180); + ctx.scale(currentScale, currentScale); + ctx.scale(currentScaleX, currentScaleY); + const width = image.width; + const height = image.height; + ctx.drawImage( + image, + -width / 2, -height / 2, + width, height + ); + ctx.restore(); + } + function get_position(canvas, x, y) { + let rect = canvas.getBoundingClientRect() + return { + x: x - rect.left * (canvas.width / rect.width), + y: y - rect.top * (canvas.height / rect.height) + } + } + function clear_canvas() { + if (confirm('确认清除屏幕?')) { + const canvas = document.getElementById('canvas'); + const ctx = canvas.getContext("2d"); + ctx.fillStyle = 'white'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + } + } + function convert_dithering(canvas) { + // const canvas = document.getElementById('canvas'); + const ctx = canvas.getContext("2d"); + const mode = document.getElementById('dithering').value; + const threshold = parseInt(document.getElementById('threshold').value); + dithering(ctx, canvas.width, canvas.height, threshold, mode); + } + document.body.onload = () => { + setInterval(() => { + const { localeTimeString, year, month, day, week } = getUnixTime(); + document.getElementById('time-setter').innerText = `设置时间为:${year}-${month}-${day} ${localeTimeString} 星期${week}`; + }, 1000); + showConnectPrompt(); + + const connectBtn = document.getElementById("connectbutton"); + let interval = null; + + if(connectBtn) { + let count = 0; + interval = setInterval(() => { + connectBtn.style.backgroundColor = count % 2 === 0 ? '#ff0000' : ''; + count++; + if(count > 60) clearInterval(interval); + }, 500); + + + addLog('请点击上方【链接】按钮连接蓝牙设备'); + + + const stopBlinkAndHint = () => { + if(interval) { + clearInterval(interval); + interval = null; + } + connectBtn.style.backgroundColor = ''; + + addLog('正在连接蓝牙设备...'); + + connectBtn.removeEventListener('click', stopBlinkAndHint); + }; + connectBtn.addEventListener('click', stopBlinkAndHint); + } +} + +function showConnectPrompt() { + const promptDiv = document.createElement('div'); + promptDiv.style.cssText = 'position:fixed;top:20px;left:50%;transform:translateX(-50%);background:#000;color:#fff;padding:12px 24px;border-radius:8px;z-index:9999;cursor:pointer;border:2px solid #ff0000;'; + promptDiv.innerHTML = '🔵 操作设备需要先链接蓝牙!'; + promptDiv.onclick = () => { + preConnect(); + promptDiv.remove(); + }; + document.body.appendChild(promptDiv); + + + setTimeout(() => { + if(promptDiv.parentNode) promptDiv.remove(); + }, 10000); +} \ No newline at end of file diff --git a/analysis/web/hex.js b/analysis/web/hex.js new file mode 100644 index 0000000..d33e6c6 --- /dev/null +++ b/analysis/web/hex.js @@ -0,0 +1,15 @@ + + 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 bytesToHex(data) { + return new Uint8Array(data).reduce( + function (memo, i) { + return memo + ("0" + i.toString(16)).slice(-2); + }, ""); + } + function intToHex(intIn, bytes = 4) { + return intIn.toString(16).padStart(bytes * 2, '0'); + } diff --git a/analysis/web/index.html b/analysis/web/index.html new file mode 100644 index 0000000..123e7f3 --- /dev/null +++ b/analysis/web/index.html @@ -0,0 +1,1310 @@ + + + + + + 作者:平平,的墨水屏上位机 + + + + + + + + +
+ + + + +
+ + +
+

📡 墨水屏·蓝牙智控

+
+ + + + +
+
+

🎭>切换为三色屏幕或者黑白屏幕

+ + +
+
+ + +
+ +
+ + +
+

🕒 时钟模式:

+ + + + +
+
+

📆 日历模式:

+ + + + +
+ +
+

🖼️ 图片模式:

+ + + +
+
+ +
+ + + + + +
+
+ + + + +
+
+ +

💤 休眠时段

+ +

:时至

+ +

:时

+ + + + +
+ +
+ 🚗 停车牌号码: + +
+ +
+ 🛸高级命令输入: + + +
+
+ 🛸WIFI名称和密码: + + + + +
+
+ + + + +
+ +
+ +
+ +

📅倒计时先选择日期,再填写前缀,最后选择界面!

+ + +
+

📆选择终止日期:

+ +
+
+

📝 填写前缀文字(时钟最多16个日历最多8个):

+ + +
+ + +

💟选择显示倒计时的界面

+
+
+ +
⏰ 时钟倒计时界面
+
📅 日历倒计时界面
+
+ + + +

🚧调整设置文字的字体和大小

+
+ +

字体选择(电脑端生效)

+ + + + + +

🔠字体大小 (50%-150%)

+ + 100% + + 调节字体的显示比例,过大可能导致字符截断 + +
+ + +
+ +
+
+ + + + + + + + + +

🚀开始将倒计时发送到墨水屏时钟

+
+ + + +
+
+ + + +
+

📤上传图片到屏幕

+ +
+

📂上传图片:

+ + +

📱 屏幕尺寸:

+ + + + +

👑抖动算法:

+ + + + +

⚫阈值:

+
+ +

🍕修改图片尺寸角度等参数

+
+ + + + + + +
+

🍳对图像的显示效果进行微调

+
+ + + +
+

🎛️图像调整

+
+ + 0 + + +
+
+ + 0 + + +
+
+ + 35 + + +
+
+ + 100 + + +
+
+ 🔄旋转角度:0° +
+
+ + +
+
+ +
+

🚀将设置好的图片上传到设备

+
+ + + + +
+
+ + +
+
+
+
+ +
+
+ +
+ +
+

◉ 模板设计·六版 注释:中文符号【】内的文字为红色!

+
+ + + + + + +
+ + +
+
+
+ +
+ 📌 画布标题 + +
+ + + + + + + + + + + + + +
+ + +
+
+ ◉ 日程/名片/备忘录/课程表 支持点击文字编辑、空白加行 | 二维码 可生成黑白红二维码,下方文字互动 | 自定义文本 自由换行 +
+
+
+

🚀将设置好的模板上传到设备

+
+ + + + +
+ + +
+ + +
+ 日志: +
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/analysis/web/moban.js b/analysis/web/moban.js new file mode 100644 index 0000000..64240bb --- /dev/null +++ b/analysis/web/moban.js @@ -0,0 +1,943 @@ + + (function(){ + + const canvas = document.getElementById('unifiedCanvas'); + const ctx = canvas.getContext('2d'); + // 获取当前选中的屏幕尺寸 +const screenSelect = document.getElementById('epdwh'); +let W = 800, H = 480; + +function updateCanvasSize() { + const [newW, newH] = screenSelect.value.split('x').map(Number); + W = newW; + H = newH; + canvas.width = W; + canvas.height = H; +} + +// 初始化尺寸 +updateCanvasSize(); + + let currentTemplate = 'schedule'; + let globalTitle = "◉ 输入内容·页眉"; + + + let presetLinesData = []; + let hitAreas = []; + + + let courseData = { + days: ["周一", "周二", "周三", "周四", "周五"], + periods: ["第1节", "第2节", "第3节", "第4节"], + cells: {} + }; + function initDefaultCourse() { + courseData.cells = {}; + const defaults = { + "0,0": "语文", "0,1": "数学", "0,2": "英语", "0,3": "体育", + "1,0": "数学", "1,1": "语文", "1,2": "物理", "1,3": "历史", + "2,0": "英语", "2,1": "化学", "2,2": "语文", "2,3": "自习", + "3,0": "物理", "3,1": "英语", "3,2": "数学", "3,3": "政治", + "4,0": "历史", "4,1": "体育", "4,2": "化学", "4,3": "班会" + }; + for(let k in defaults) courseData.cells[k] = defaults[k]; + } + initDefaultCourse(); + let courseHitAreas = []; + + + function applyCourseCustomSettings() { + const dayCount = parseInt(document.getElementById('courseDaysCount').value) || 5; + const periodCount = parseInt(document.getElementById('coursePeriodsCount').value) || 4; + + const dayNames = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]; + courseData.days = dayNames.slice(0, dayCount); + + courseData.periods = []; + for(let i=1; i<=periodCount; i++) { + courseData.periods.push(`第${i}节`); + } + + courseData.cells = {}; + render(); + } + + + let qrText = "https://mspppclock.tech/"; + let qrSize = 150; + let qrCodeImage = null; + let qrCaptionLines = []; + let qrCaptionStyle = { fontSize: 14, fontFamily: "微软雅黑", baseColor: "#000000", invertMode: "none", alignment: "center" }; + + function syncCaptionFromTextarea() { + const raw = document.getElementById('qrCaptionText').value; + const lines = raw.split(/\r?\n/).filter(l => l.trim() !== "" || l === ""); + qrCaptionLines = lines.length ? lines : ["(点击添加文字)"]; + } + function updateCaptionLinesToUI() { + document.getElementById('qrCaptionText').value = qrCaptionLines.join("\n"); + } + + async function generateQRCodeImage(text, size=90, colorDark="#000000", colorLight="#FFFFFF") { + return new Promise((resolve) => { + const tempDiv = document.createElement('div'); + tempDiv.style.position = 'absolute'; + tempDiv.style.left = '-9999px'; + document.body.appendChild(tempDiv); + try { + const qrcode = new QRCode(tempDiv, { + text: text, + width: size, + height: size, + colorDark: colorDark, + colorLight: colorLight, + correctLevel: QRCode.CorrectLevel.M + }); + setTimeout(() => { + const canvasQR = tempDiv.querySelector('canvas'); + if (canvasQR) { + const imgData = canvasQR.toDataURL(); + resolve(imgData); + } else resolve(null); + document.body.removeChild(tempDiv); + }, 60); + } catch(e) { resolve(null); document.body.removeChild(tempDiv); } + }); + } + + async function refreshQRImage() { + qrSize = parseInt(document.getElementById('qrSizeInput').value) || 84; + const img = await generateQRCodeImage(qrText, qrSize, "#000", "#fff"); + qrCodeImage = img; + if(currentTemplate === 'qrcode') render(); + } + + + let customRawText = "小雨在放学路上捡到一枚【闪闪发亮的硬币】。\n她买了两颗【橘子味的糖果】,一颗塞进嘴里,\n甜得她眯起了眼睛;另一颗放在路边的长椅上。\n一只【唱歌的小鸟】飞过来,歪着脑袋看了看\n,叼起糖果飞走了。小雨有点失落,\n但还是继续往家走。第二天早上,她推开窗户,\n发现窗台上放着一朵【金黄色的小野花】,花瓣上还挂着露珠。小雨捧着花笑了:\n“原来快乐会【越分越多】呀。”\n从那天起,她每天都往长椅上放一颗糖。"; + let customStyle = { + fontSize: 17, fontFamily: "宋体", baseColor: "#000000", invertMode: "none", alignment: "center" + }; + + const chineseFonts = ["宋体","微软雅黑","黑体","楷体","思源黑体","苹方"]; + const invertModes = [ + { value: "none", label: "无" }, { value: "red-black", label: "红字黑底" }, { value: "red-white", label: "红字白底" }, + { value: "black-red", label: "黑字红底" }, { value: "black-white", label: "黑字白底" }, + { value: "white-red", label: "白字红底" }, { value: "white-black", label: "白字黑底" } + ]; + + function applyInvertStyle(baseColor, invertMode) { + if (invertMode === "none") return { bgColor: null, textColor: baseColor }; + if (invertMode === "red-black") return { bgColor: "#000000", textColor: "#FF0000" }; + if (invertMode === "red-white") return { bgColor: "#FFFFFF", textColor: "#FF0000" }; + if (invertMode === "black-red") return { bgColor: "#FF0000", textColor: "#000000" }; + if (invertMode === "black-white") return { bgColor: "#FFFFFF", textColor: "#000000" }; + if (invertMode === "white-red") return { bgColor: "#FF0000", textColor: "#FFFFFF" }; + if (invertMode === "white-black") return { bgColor: "#000000", textColor: "#FFFFFF" }; + return { bgColor: null, textColor: baseColor }; + } + + const templatePresets = { + schedule: [ { text: "✅ 今日核心议程", fontSize: 24, fontFamily: "微软雅黑", baseColor: "#FF0000", invertMode: "none", alignment: "center" }, + { text: "【09:30】 敏捷站会 · 【需求对齐】", fontSize: 18,fontFamily: "微软雅黑", baseColor: "#000000", invertMode: "none", alignment: "left" }, + { text: "11:00 前端架构评审会议", fontSize: 18, fontFamily: "黑体", baseColor: "#000000", invertMode: "black-red", alignment: "left" }, + { text: "14:30 跨部门协作同步会", fontSize: 18, fontFamily: "微软雅黑", baseColor: "#FF0000", invertMode: "red-white", alignment: "left" }, + { text: "16:45 代码审查 + 项目复盘", fontSize: 18, fontFamily: "楷体", baseColor: "#000000", invertMode: "none", alignment: "left" } + ], + businesscard: [ { text: "✦ 名 片 精 选 ✦", fontSize: 24, fontFamily: "微软雅黑", baseColor: "#FF0000", invertMode: "none", alignment: "center" }, + { text: "欧阳 静澜", fontSize: 26, fontFamily: "黑体", baseColor: "#000000", invertMode: "black-white", alignment: "center" }, + { text: "创意设计总监 / 合伙人", fontSize: 18, fontFamily: "微软雅黑", baseColor: "#000000", invertMode: "none", alignment: "center" }, + { text: "jinglan@art.cn", fontSize: 17, fontFamily: "宋体", baseColor: "#FF0000", invertMode: "red-black", alignment: "center" }, + { text: "三色哲学 · 红黑白", fontSize: 16, fontFamily: "微软雅黑", baseColor: "#000000", invertMode: "none", alignment: "center" } + ], + memo: [ { text: "📋 紧 急 备 忘 录", fontSize: 28, fontFamily: "微软雅黑", baseColor: "#FF0000", invertMode: "none", alignment: "center" }, + { text: "• 季度总结报告 (周五截止)", fontSize: 24, fontFamily: "微软雅黑", baseColor: "#000000", invertMode: "black-red", alignment: "left" }, + { text: "• 设计评审会议 14:00", fontSize: 24, fontFamily: "宋体", baseColor: "#000000", invertMode: "none", alignment: "left" }, + { text: "• 前端组件库升级计划", fontSize: 24, fontFamily: "黑体", baseColor: "#FF0000", invertMode: "red-white", alignment: "left" }, + { text: "• 团队建设活动策划", fontSize: 23, fontFamily: "微软雅黑", baseColor: "#000000", invertMode: "none", alignment: "left" } + ] + }; + function loadPresetData(templateId) { + if(templatePresets[templateId]) presetLinesData = JSON.parse(JSON.stringify(templatePresets[templateId])); + else presetLinesData = [{ text: "示例行", fontSize: 16, fontFamily: "微软雅黑", baseColor: "#000000", invertMode: "none", alignment: "left" }]; + } + + +function renderCourseTable() { + ctx.fillStyle = "#FFFFFF"; + ctx.fillRect(0, 0, W, H); + + const isSmallScreen = (W === 296 && H === 128); + const isLargeScreen = (W === 800 && H === 480); + + if (!isSmallScreen) { + // 显示页眉 + const headerFontSize = isLargeScreen ? 36 : 18; + ctx.strokeStyle = "#000000"; + ctx.lineWidth = 2; + ctx.strokeRect(4, 4, W-8, H-8); + ctx.fillStyle = "#FF0000"; + ctx.fillRect(8, 8, 26, 4); + ctx.fillStyle = "#000000"; + ctx.fillRect(W-54, 12, 42, 4); + + let title = globalTitle.trim() || "输入学校班级学期等信息"; + + // 页眉标题 + ctx.font = `bold ${headerFontSize}px '微软雅黑'`; + ctx.fillStyle = "#FF0000"; + ctx.fillText(title, 20, headerFontSize + 12); + + ctx.beginPath(); + ctx.strokeStyle = "#FF0000"; + ctx.moveTo(12, headerFontSize + 22); + ctx.lineTo(W-12, headerFontSize + 22); + ctx.stroke(); + + var startY = headerFontSize + 30; + } else { + ctx.strokeStyle = "#FF0000"; + ctx.lineWidth = 2; + ctx.strokeRect(4, 4, W-8, H-8); + var startY = 8; + } + + const dayCount = courseData.days.length; + const periodCount = courseData.periods.length; + const startX = 12; + const cellW = (W - 24) / (dayCount + 1); + const cellH = (H - startY - 8) / (periodCount + 1); + + const headerTextFontSize = isLargeScreen ? 30 : 15; + const cellTextFontSize = isLargeScreen ? 28 : (isSmallScreen ? 11 : 14); + + // 绘制节次表头 + ctx.font = `bold ${headerTextFontSize}px '微软雅黑'`; + ctx.fillStyle = "#000000"; + ctx.fillRect(startX, startY, cellW, cellH); + ctx.fillStyle = "#FF0000"; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillText("节次", startX + cellW/2, startY + cellH/2); + + // 绘制星期表头 + for(let d=0; d maxChars ? courseName.slice(0, maxChars-1) + "…" : courseName; + + // 如果文字仍然太宽,进一步缩小字体 + let tempFontSize = finalFontSize; + let textWidth = ctx.measureText(displayName).width; + while (textWidth > cellW - 8 && tempFontSize > 10) { + tempFontSize--; + ctx.font = `${tempFontSize}px "${cellStyle.fontFamily}"`; + textWidth = ctx.measureText(displayName).width; + } + + ctx.fillText(displayName, x + cellW/2, y + cellH/2); + + courseHitAreas.push({ row: p, col: d, x, y, w: cellW, h: cellH, cellKey, currentName: courseName }); + } + } + + if (!isSmallScreen) { + // 底部装饰线 + ctx.fillStyle = "#000000"; + ctx.fillRect(16, H-11, 42, 2); + ctx.fillStyle = "#FF0000"; + ctx.fillRect(66, H-13, 26, 2); + } + + + ctx.textAlign = "left"; + ctx.textBaseline = "alphabetic"; + + + ctx.beginPath(); + ctx.strokeStyle = "#ff0000"; + ctx.lineWidth = 1; + ctx.setLineDash([]); + + for(let d = 1; d <= dayCount; d++){ + ctx.beginPath(); + ctx.moveTo(startX + d * cellW, startY); + ctx.lineTo(startX + d * cellW, startY + (periodCount + 1) * cellH); + ctx.stroke(); + } + + for(let p = 1; p <= periodCount; p++){ + ctx.beginPath(); + ctx.moveTo(startX, startY + p * cellH); + ctx.lineTo(startX + (dayCount + 1) * cellW, startY + p * cellH); + ctx.stroke(); + } +} + + function editCourseCell(row, col, currentName) { + + const cellKey = `${col},${row}`; + const existingStyle = courseData.cellStyles?.[cellKey] || { + fontSize: 16, + fontFamily: "微软雅黑", + color: "#000000" + }; + + const modal = document.createElement('div'); + modal.className = 'modal-mask'; + modal.innerHTML = ` + + `; + document.body.appendChild(modal); + + modal.querySelector('#confirmBtn').onclick = () => { + const newName = modal.querySelector('#courseNameInput').value.trim() || "自习"; + const newFontSize = parseInt(modal.querySelector('#fontSizeInput').value) || 16; + const newFontFamily = modal.querySelector('#fontFamilyInput').value; + const newColor = modal.querySelector('#colorInput').value; + + + courseData.cells[cellKey] = newName; + + + if (!courseData.cellStyles) courseData.cellStyles = {}; + courseData.cellStyles[cellKey] = { + fontSize: Math.min(48, Math.max(10, newFontSize)), + fontFamily: newFontFamily, + color: newColor + }; + + modal.remove(); + render(); + }; + + modal.querySelector('.cancel').onclick = () => modal.remove(); + modal.onclick = e => e.target === modal && modal.remove(); +} + + + +function renderPresetTemplate() { + ctx.fillStyle = "#ffffffff"; + ctx.fillRect(0, 0, W, H); + + + const isSmallScreen = (W === 296 && H === 128); + const isLargeScreen = (W === 800 && H === 480); + + if (!isSmallScreen) { + + ctx.strokeStyle = "#FF0000"; + ctx.lineWidth = 2; + ctx.strokeRect(4, 4, W-8, H-8); + ctx.fillStyle = "#FF0000"; + ctx.fillRect(8, 8, 26, 4); + ctx.fillStyle = "#000000"; + ctx.fillRect(W-54, 12, 42, 4); + + let titleText = globalTitle.trim() || "输入内容·页眉"; + + + let headerFontSize = isLargeScreen ? 52 : 26; + ctx.font = `bold ${headerFontSize}px '微软雅黑'`; + + let titleWidth = ctx.measureText(titleText.slice(0,24)).width; + let bgX = (W - (titleWidth+40))/2; + ctx.fillStyle = "#FF0000"; + ctx.fillRect(bgX, 7, titleWidth+40, headerFontSize + 10); + ctx.fillStyle = "#FFF"; + ctx.fillText(titleText.slice(0,24), (W-titleWidth)/2, headerFontSize + 5); + ctx.beginPath(); + ctx.strokeStyle = "#FF0000"; + ctx.moveTo(12, headerFontSize + 20); + ctx.lineTo(W-12, headerFontSize + 20); + ctx.stroke(); + + var startY = headerFontSize + 50; + } else { + ctx.strokeStyle = "#FF0000"; + ctx.lineWidth = 2; + ctx.strokeRect(4, 4, W-8, H-8); + var startY = 10; + } + const lineHeightBase = 26; + let currentY = startY; + hitAreas = []; + + for(let i=0; i { + ctx.font = `${line.fontSize}px "${line.fontFamily}"`; + totalWidth += ctx.measureText(seg.text).width; + }); + + let startX = line.alignment === 'center' ? (W - totalWidth) / 2 : + (line.alignment === 'right' ? W - totalWidth - 18 : 22); + + let currentX = startX; + + + segments.forEach(seg => { + ctx.font = `${line.fontSize}px "${line.fontFamily}"`; + let textWidth = ctx.measureText(seg.text).width; + + if(bgColor) { + ctx.fillStyle = bgColor; + ctx.fillRect(currentX, currentY-line.fontSize, textWidth+2, line.fontSize+4); + } + + + if(seg.isRed) { + ctx.fillStyle = "#FF0000"; + } else { + ctx.fillStyle = textColor; + } + + ctx.fillText(seg.text, currentX, currentY); + currentX += textWidth; + }); + + hitAreas.push({ index:i, yTop: currentY-20, yBottom: currentY+10 }); + currentY += line.fontSize + 8; + if(currentY > H-20) break; + } + + ctx.fillStyle = "#000"; + ctx.fillRect(16, H-11, 42, 2); + ctx.fillStyle = "#FF0000"; + ctx.fillRect(66, H-13, 26, 2); +} + + +function parseColoredText(text) { + const segments = []; + let lastIndex = 0; + const regex = /【(.*?)】/g; + let match; + + while ((match = regex.exec(text)) !== null) { + if (match.index > lastIndex) { + segments.push({ + text: text.substring(lastIndex, match.index), + isRed: false + }); + } + segments.push({ + text: match[1], + isRed: true + }); + lastIndex = match.index + match[0].length; + } + + if (lastIndex < text.length) { + segments.push({ + text: text.substring(lastIndex), + isRed: false + }); + } + + return segments.length ? segments : [{ text: text, isRed: false }]; +} + + + async function renderQRCodeMode() { + ctx.fillStyle = "#ffffffff"; + ctx.fillRect(0, 0, W, H); + + const isSmallScreen = (W === 296 && H === 128); + const isLargeScreen = (W === 800 && H === 480); + + if (!isSmallScreen) { + + ctx.strokeStyle = "#FF0000"; + ctx.lineWidth = 2; + ctx.strokeRect(4, 4, W-8, H-8); + ctx.fillStyle = "#FF0000"; + ctx.fillRect(8, 8, 26, 4); + ctx.fillStyle = "#000"; + ctx.fillRect(W-54, 12, 42, 4); + + const headerFontSize = isLargeScreen ? 36 : 18; + ctx.font = `bold ${headerFontSize}px '微软雅黑'`; + ctx.fillStyle = "#FF0000"; + ctx.fillText(globalTitle.slice(0, 20), 33, headerFontSize + 10); + + ctx.beginPath(); + ctx.moveTo(12, headerFontSize + 20); + ctx.lineTo(W-12, headerFontSize + 20); + ctx.stroke(); + + var qrY = headerFontSize + 24; + var captionStartY = headerFontSize + qrSize + 30; + } else { + ctx.strokeStyle = "#FF0000"; + ctx.lineWidth = 2; + ctx.strokeRect(4, 4, W-8, H-8); + + var qrY = 8; + var captionStartY = qrSize + 12; + } + + if(qrCodeImage) { + const img = new Image(); + img.src = qrCodeImage; + await new Promise(r => { img.onload = r; img.onerror = r; }); + + + let drawSize = qrSize; + if (isSmallScreen && drawSize > 90) { + drawSize = 80; + } + ctx.drawImage(img, (W-drawSize)/2, qrY, drawSize, drawSize); + } + + let y = captionStartY; + const captionFontSize = isLargeScreen ? 32 : (isSmallScreen ? 12 : 18); + + for(let line of qrCaptionLines) { + if(!line.trim()) { y += captionFontSize + 4; continue; } + ctx.font = `${captionFontSize}px '微软雅黑'`; + let tw = ctx.measureText(line).width; + ctx.fillStyle = "#f00"; + ctx.fillText(line, (W-tw)/2, y+(H/20)); + y += captionFontSize + 6; + } + + if (!isSmallScreen) { + ctx.fillStyle = "#000"; + ctx.fillRect(16, H-11, 42, 2); + ctx.fillStyle = "#FF0000"; + ctx.fillRect(66, H-13, 26, 2); + } +} + +function renderCustomMode() { + ctx.fillStyle = "#ffffffff"; + ctx.fillRect(0, 0, W, H); + + const isSmallScreen = (W === 296 && H === 128); + const isLargeScreen = (W === 800 && H === 480); + + if (!isSmallScreen) { + + ctx.strokeStyle = "#FF0000"; + ctx.lineWidth = 2; + ctx.strokeRect(4, 4, W-8, H-8); + + const headerFontSize = isLargeScreen ? 40 : 20; + const headerText = globalTitle.slice(0, 24); + ctx.font = `bold ${headerFontSize}px '微软雅黑'`; + const textWidth = ctx.measureText(headerText).width; + + const boxX = 14; + const boxY = 8; + const boxWidth = Math.max(textWidth + 30, 150); + const boxHeight = headerFontSize + 10; + + ctx.fillStyle = "#000"; + ctx.fillRect(boxX, boxY, boxWidth, boxHeight); + ctx.fillStyle = "#FF0000"; + ctx.fillText(headerText, boxX + 15, boxY + headerFontSize + 3); + + ctx.fillStyle = "#FF0000"; + ctx.fillRect(8, 8, 26, 4); + ctx.fillStyle = "#000"; + ctx.fillRect(W-54, 12, 42, 4); + + ctx.beginPath(); + ctx.moveTo(12, headerFontSize + 30); + ctx.lineTo(W-12, headerFontSize + 30); + ctx.stroke(); + + var startY = headerFontSize + 48; + } else { + ctx.strokeStyle = "#FF0000"; + ctx.lineWidth = 2; + ctx.strokeRect(4, 4, W-8, H-8); + + var startY = 8; + } + + const { fontSize, fontFamily, baseColor, invertMode, alignment } = customStyle; + const { bgColor, textColor } = applyInvertStyle(baseColor, invertMode); + ctx.font = `${fontSize}px "${fontFamily}"`; + + let rawLines = customRawText.split('\n'); + let allLines = []; + + for(let line of rawLines) { + let segments = parseColoredText(line); + let currentLineSegments = []; + let currentLineWidth = 0; + + for(let seg of segments) { + ctx.font = `${fontSize}px "${fontFamily}"`; + let segWidth = ctx.measureText(seg.text).width; + + if(currentLineWidth + segWidth > W - 44 && currentLineSegments.length > 0) { + allLines.push([...currentLineSegments]); + currentLineSegments = []; + currentLineWidth = 0; + } + currentLineSegments.push(seg); + currentLineWidth += segWidth; + } + if(currentLineSegments.length > 0) { + allLines.push(currentLineSegments); + } + } + + let y = startY; + for(let lineSegments of allLines) { + if(!lineSegments.length) { y += fontSize + 6; continue; } + + let totalWidth = 0; + for(let seg of lineSegments) { + ctx.font = `${fontSize}px "${fontFamily}"`; + totalWidth += ctx.measureText(seg.text).width; + } + + let startX = alignment === 'center' ? (W - totalWidth) / 2 : + (alignment === 'right' ? W - totalWidth - 18 : 22); + + let currentX = startX; + + for(let seg of lineSegments) { + ctx.font = `${fontSize}px "${fontFamily}"`; + let textWidth = ctx.measureText(seg.text).width; + const textHeight = fontSize; + + if(bgColor) { + ctx.fillStyle = bgColor; + ctx.fillRect(currentX-2, y + fontSize/2 - textHeight+1, textWidth+4, textHeight + 4); + } + + if(seg.isRed) { + ctx.fillStyle = "#FF0000"; + } else { + ctx.fillStyle = textColor; + } + + ctx.fillText(seg.text, currentX, y + fontSize/2); + currentX += textWidth; + } + + y += fontSize + 6; + if(y > H-30) break; + } + + if (!isSmallScreen) { + ctx.fillStyle = "#000"; + ctx.fillRect(16, H-11, 42, 2); + ctx.fillStyle = "#FF0000"; + ctx.fillRect(66, H-13, 26, 2); + } +} + + function render() { + if(currentTemplate === 'course') renderCourseTable(); + else if(currentTemplate === 'qrcode') renderQRCodeMode(); + else if(currentTemplate === 'customText') renderCustomMode(); + else renderPresetTemplate(); + const canvas = document.getElementById('unifiedCanvas'); + applyImageAdjustmentsmb(canvas) + } + function applyImageAdjustmentsmb(canvas) { + const brightness = 0; + const contrast = 100; + const saturation = 100; + const diffusion = 0; + const ctx = canvas.getContext('2d'); + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const data = imageData.data; + if (brightness !== 0) { + const brightnessFactor = brightness / 100; + for (let i = 0; i < data.length; i += 4) { + data[i] = clamp(data[i] + 255 * brightnessFactor, 0, 255); + data[i + 1] = clamp(data[i + 1] + 255 * brightnessFactor, 0, 255); + data[i + 2] = clamp(data[i + 2] + 255 * brightnessFactor, 0, 255); + } + } + if (contrast !== 0) { + const contrastFactor = (contrast + 100) / 100; + const contrastAdjust = (1 - contrastFactor) * 128; + for (let i = 0; i < data.length; i += 4) { + data[i] = clamp(data[i] * contrastFactor + contrastAdjust, 0, 255); + data[i + 1] = clamp(data[i + 1] * contrastFactor + contrastAdjust, 0, 255); + data[i + 2] = clamp(data[i + 2] * contrastFactor + contrastAdjust, 0, 255); + } + } + if (saturation !== 100) { + const saturationFactor = saturation / 100; + for (let i = 0; i < data.length; i += 4) { + const gray = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2]; + data[i] = clamp(gray + (data[i] - gray) * saturationFactor, 0, 255); + data[i + 1] = clamp(gray + (data[i + 1] - gray) * saturationFactor, 0, 255); + data[i + 2] = clamp(gray + (data[i + 2] - gray) * saturationFactor, 0, 255); + } + } + ctx.putImageData(imageData, 0, 0); + return diffusion; + } + + function handleCanvasClick(e) { + if(currentTemplate === 'course') { + const r = canvas.getBoundingClientRect(); + const x = (e.clientX-r.left)*(W/r.width); + const y = (e.clientY-r.top)*(H/r.height); + for(let a of courseHitAreas) { + if(x>=a.x&&x<=a.x+a.w&&y>=a.y&&y<=a.y+a.h) { + editCourseCell(a.row,a.col,a.currentName); return; + } + } + return; + } + + if(currentTemplate === 'qrcode') { + const r = canvas.getBoundingClientRect(); + const y = (e.clientY-r.top)*(H/r.height); + if(y>130) { + const m = document.createElement('div'); + m.className='modal-mask'; + m.innerHTML=``; + document.body.appendChild(m); + m.querySelector('.ok').onclick=()=>{ + qrCaptionLines = m.querySelector('textarea').value.split('\n'); + updateCaptionLinesToUI(); + render(); m.remove(); + }; + m.querySelector('.cancel').onclick=()=>m.remove(); + } + return; + } + + if(currentTemplate !== 'customText') { + const r = canvas.getBoundingClientRect(); + const y = (e.clientY-r.top)*(H/r.height); + for(let a of hitAreas) { + if(y>=a.yTop&&y<=a.yBottom) { + editPresetModal(a.index); return; + } + } + + addPresetNewRow(); + } + } + +function addPresetNewRow() { + const modal = document.createElement('div'); + modal.className = 'modal-mask'; + modal.innerHTML = ` + + `; + document.body.appendChild(modal); + const confirm = () => { + const newRow = { + text: modal.querySelector('#modalText').value, + fontSize: Math.min(52, Math.max(10, parseInt(modal.querySelector('#modalFontSize').value) || 14)), + fontFamily: modal.querySelector('#modalFontFamily').value, + baseColor: modal.querySelector('#modalBaseColor').value, + invertMode: modal.querySelector('#modalInvert').value, + alignment: modal.querySelector('#modalAlign').value + }; + presetLinesData.push(newRow); + renderPresetTemplate(); + modal.remove(); + }; + modal.querySelector('#confirmBtn').onclick = confirm; + modal.querySelector('#cancelBtn').onclick = () => modal.remove(); + modal.onclick = (e) => { if(e.target === modal) modal.remove(); }; + } + + function editPresetModal(idx) { + const line = presetLinesData[idx]; + const modal = document.createElement('div'); + modal.className = 'modal-mask'; + modal.innerHTML = ` + + `; + document.body.appendChild(modal); + const confirm = () => { + presetLinesData[idx] = { + text: modal.querySelector('#modalText').value, + fontSize: Math.min(52, Math.max(10, parseInt(modal.querySelector('#modalFontSize').value) || 14)), + fontFamily: modal.querySelector('#modalFontFamily').value, + baseColor: modal.querySelector('#modalBaseColor').value, + invertMode: modal.querySelector('#modalInvert').value, + alignment: modal.querySelector('#modalAlign').value + }; + renderPresetTemplate(); + modal.remove(); + }; + modal.querySelector('#confirmBtn').onclick = confirm; + modal.querySelector('#cancelBtn').onclick = () => modal.remove(); + modal.onclick = (e) => { if(e.target === modal) modal.remove(); }; + } + + function switchTemplate(tpl) { + currentTemplate = tpl; + document.getElementById('customBlock').style.display = tpl==='customText'?'block':'none'; + document.getElementById('qrcodeBlock').style.display = tpl==='qrcode'?'block':'none'; + document.getElementById('courseBlock').style.display = tpl==='course'?'block':'none'; + document.getElementById('presetHintBlock').style.display = (tpl!=='customText'&&tpl!=='qrcode')?'block':'none'; + + if(tpl === 'course') { loadPresetData('schedule'); render(); } + else if(tpl === 'qrcode') { syncCaptionFromTextarea(); refreshQRImage(); } + else if(tpl === 'customText') { render(); } + else { loadPresetData(tpl); render(); } + } + + function resetCurrent() { + if(currentTemplate === 'customText') { + customRawText = "小雨在放学路上捡到一枚【闪闪发亮的硬币】。\n她买了两颗【橘子味的糖果】,一颗塞进嘴里,\n甜得她眯起了眼睛;另一颗放在路边的长椅上。\n一只【唱歌的小鸟】飞过来,歪着脑袋看了看\n,叼起糖果飞走了。小雨有点失落,\n但还是继续往家走。第二天早上,她推开窗户,\n发现窗台上放着一朵【金黄色的小野花】,花瓣上还挂着露珠。小雨捧着花笑了:\n“原来快乐会【越分越多】呀。”\n从那天起,她每天都往长椅上放一颗糖。"; + document.getElementById('freeTextArea').value = customRawText; + } else if(currentTemplate === 'qrcode') { + qrText = "https://mspppclock.tech/"; + document.getElementById('qrContentInput').value = qrText; + qrCaptionLines = ["扫码访问官网"]; + updateCaptionLinesToUI(); + refreshQRImage(); + } else if(currentTemplate === 'course') { + initDefaultCourse(); + } else { + loadPresetData(currentTemplate); + } + render(); + } + + function escapeHtml(s) { + return String(s).replace(/&/g,'&').replace(//g,'>'); + } + + function init() { + + const fsel = document.getElementById('customFontFamily'); + chineseFonts.forEach(f=>{ let o=document.createElement('option');o.value=f;o.textContent=f;fsel.appendChild(o); }); + const isel = document.getElementById('customInvertMode'); + invertModes.forEach(m=>{ let o=document.createElement('option');o.value=m.value;o.textContent=m.label;isel.appendChild(o); }); + + document.getElementById('freeTextArea').value = customRawText; + document.getElementById('freeTextArea').oninput = e=>{ customRawText=e.target.value; render(); }; + document.querySelectorAll('#customFontSize,#customFontFamily,#customBaseColor,#customInvertMode,#customAlign').forEach(i=>{ + i.onchange=()=>{ + customStyle.fontSize = +document.getElementById('customFontSize').value; + customStyle.fontFamily = document.getElementById('customFontFamily').value; + customStyle.baseColor = document.getElementById('customBaseColor').value; + customStyle.invertMode = document.getElementById('customInvertMode').value; + customStyle.alignment = document.getElementById('customAlign').value; + render(); + }; + }); + + document.getElementById('globalTitleInput').oninput = e=>{ globalTitle=e.target.value; render(); }; + document.getElementById('forceRefreshBtn').onclick = render; + document.getElementById('resetCurrentBtn').onclick = resetCurrent; + document.getElementById('generateQRBtn').onclick = ()=>{ qrText=document.getElementById('qrContentInput').value; refreshQRImage(); }; + document.getElementById('qrCaptionText').oninput = ()=>{ syncCaptionFromTextarea(); render(); }; + document.getElementById('applyCourseSettingBtn').onclick = applyCourseCustomSettings; + canvas.onclick = handleCanvasClick; + + document.querySelectorAll('.temp-btn').forEach(b=>{ + b.onclick=()=>{ + document.querySelectorAll('.temp-btn').forEach(x=>x.classList.remove('active')); + b.classList.add('active'); + switchTemplate(b.dataset.template); + }; + }); + + document.getElementById('epdwh').addEventListener('change', function() { + const [newW, newH] = this.value.split('x').map(Number); + canvas.width = newW; + canvas.height = newH; + render(); + }); + // 监听屏幕尺寸变化 + document.getElementById('epdwh').addEventListener('change', function() { + updateCanvasSize(); + if (currentTemplate === 'qrcode') { + refreshQRImage(); // 二维码模式需要重新生成 + } else { + render(); + } + }); + switchTemplate('schedule'); + } + + init(); + })(); diff --git a/analysis/web/protocol-raw.md b/analysis/web/protocol-raw.md new file mode 100644 index 0000000..99066bc --- /dev/null +++ b/analysis/web/protocol-raw.md @@ -0,0 +1,181 @@ +# 墨水屏 Web 上位机 BLE 协议提取报告(原始证据) + +> 来源:analysis/web/(ble.js 4408 行 / index.html 1309 行 / moban.js 943 行 / timedjs.js 714 行 / timedjs2.js 323 行 / switch2.js 54 行 / time.js 21 行 / hex.js 15 行) +> 设备:4.2寸 400x300 黑白红三色墨水屏,DA14585,设备名前缀 `NRF-` + +## 1. GATT 结构 + +| 项 | 值 | 证据 | +|---|---|---| +| 设备名过滤 | `namePrefix: 'NRF-'` | ble.js:4052-4054 | +| optionalServices | `0000221f-...34fb`、`00001f10-...34fb`、`13187b10-eba9-a3ba-044e-83d3217d9a38` | ble.js:4054 | +| EPD Service | `13187b10-eba9-a3ba-044e-83d3217d9a38` | ble.js:4163 | +| EPD Characteristic | `4b646063-6264-f3a7-8941-e65356ea82fe` | ble.js:4165 | +| RXTX Service | `00001f10-0000-1000-8000-00805f9b34fb` | ble.js:4101 | +| RXTX Characteristic | `00001f1f-0000-1000-8000-00805f9b34fb` | ble.js:4103 | +| `0000221f` | 仅声明未使用 | ble.js:4054 | + +- epdCharacteristic:图像数据块 + 刷屏命令(`00xx`/`01`/`AA`),writeValueWithResponse(ble.js:38-44),订阅 notify(ble.js:4167-4205)。 +- rxtxCharacteristic:全部控制命令(时间/模式/WiFi/激活/倒计时/模板槽位),writeValueWithResponse + 100ms 后 read(ble.js:50-71);另有 writeValueWithoutResponse(ble.js:84-90);notify 是设备ID 14 字节应答主通道(ble.js:4121-4137)。 +- 连接顺序:preConnect()(ble.js:4045)→ connect()(ble.js:4158)先 EPD 后 RXTX(ble.js:4207);断线重连≤5次(ble.js:30-36)。 + +## 2. 命令协议 + +### 2.1 发送封装 + +| 函数 | 行为 | 证据 | +|---|---|---| +| sendCommand | EPD writeValueWithResponse | ble.js:38-44 | +| rxTxSendCommand | RXTX write+100ms read | ble.js:50-75 | +| rxTxSendCommand2 | RXTX writeWithoutResponse | ble.js:84-90 | +| triggerEpdCmd | hex→EPD | ble.js:3971-3974 | +| triggerRxTxCmd | hex→RXTX | ble.js:3963-3966 | +| triggerRxTxCmd2 | hex→RXTX(无响应) | ble.js:3967-3970 | +| setri | RXTX 发 cmd,300ms 后补 `e2` | ble.js:3956-3962 | +| clearScreen | EPD 发 `00{cmd}` 再 `01` | ble.js:45-49 | +| triggerEpdRed_lut | RXTX `E6{cmd}`(0 直接返回) | ble.js:3975-3980 | +| triggerEpdJH | RXTX `EF{cmd}`,500ms 后 `e2` | ble.js:3981-3987 | +| triggerEpdCHEPAI | 字符双写+`EF` 前缀,500ms 后 `e2` | ble.js:3988-3998 | +| triggerRxTxCmd_LED | `cmd+note+color+HH+MM` | ble.js:3999-4009 | +| triggerRxTxCmd_XM | `cmd+HH+MM` | ble.js:4010-4020 | +| triggerRxTxCmd_LB | `e901`+hex(n)+cmd | ble.js:4022-4038 | + +### 2.2 Opcode 一览 + +**EPD 通道:** + +| Opcode | 含义 | 证据 | +|---|---|---| +| `00{xx}` | 刷屏第一步 | ble.js:47 | +| `01` | 刷屏执行/整图结束刷新 | ble.js:48,160,230 | +| `AA` | 日历小图传输结束 | ble.js:159 | +| `03{ff\|00}{offset:2B BE}{data}` | 整图数据块(≤240B/块) | ble.js:98,104 | +| `04{ff\|00}{offset:2B BE}{data}` | 日历小图数据块(hex 总长 7000/18000 时) | ble.js:99,104 | + +**RXTX 通道:** + +| Opcode | 含义 | 证据 | +|---|---|---| +| `e2` | 提交/强制刷新(多数命令收尾) | ble.js:256,3961 等 | +| `dd{unix:4B}{year:2B}{month:1B}{day:1B}{week:1B}` | 设置时间 | ble.js:251-257 | +| `2300`/`2301` | 三色/黑白屏模式 | index.html:840-841 | +| `14` | 12/24 小时制切换 | index.html:845 | +| `15` | 时钟颜色切换 | index.html:881 | +| `e3` | 反色模式 | index.html:878 | +| `e4` | 时钟字体切换 | index.html:880 | +| `e5` | 旋转180° | index.html:886 | +| `e10000`..`e10003` | 图片模式 图一~图四 | index.html:868-873 | +| `e10103`/`e10104`/`e10105` | 日历模式一/二/三 | index.html:860-862 | +| `e10201`/`e10202`/`e10203` | 时钟模式一/二/三 | index.html:853-855 | +| `E6{lut}` | 红/黑 LUT 校准:01–0f 设红、10–f0 设黑 | ble.js:3978; index.html:885-887 | +| `FB01{HH}{MM}` / `FB00{HH}{MM}` | 开/关休眠时段 | index.html:897-898; ble.js:4017 | +| `e901{n:hex}{01\|00}` | 图片轮播间隔(01=分钟 00=小时) | ble.js:4035 | +| `EA0{slot}` | 上传选槽位 EA00–EA03 | ble.js:213,237 | +| `EA000{n-1}` | 轮播数量 1–4 | ble.js:241-249 | +| `EF{激活码hex}` | 激活设备 | ble.js:3891,3983 | +| `EF{逐位双写号码}` | 停车牌("12"→`EF1122`) | ble.js:3990-3995 | +| `EFEF` | 请求设备 ID(14B notify 应答) | ble.js:3924,4123 | +| `FA` | 关闭倒计时 | timedjs2.js:142,308 | +| `ff00{year}{MM}{dd}` | 时钟倒计时日期(year=4位十进制ASCII) | timedjs2.js:250-253 | +| `ffff{year}{MM}{dd}` | 日历倒计时日期 | timedjs2.js:245-248 | +| `04{128B hex}` + `0{charCount}{余hex}` | 时钟倒计时前缀文字位图(>4字符拆两包) | timedjs2.js:206-217 | +| `0{charCount}{位图hex}` | 同上 ≤4字符单包 | timedjs2.js:218-224 | +| `990{j}{72B hex}` | 日历倒计时文字位图(每字符一包,24x24/8) | timedjs2.js:229-233,300-304 | +| ASCII `WIFI_SSID:..;WIFI_PASS:..;CITY:..` | WiFi+天气城市配置(UTF-8 直发) | ble.js:3839-3863 | +| `{cmd}{note}{color}{HH}{MM}` | LED 变体(遗留) | ble.js:4000-4008 | + +## 3. 图像传输协议(sendBufferData ble.js:92-107) + +1. 分块:`my_step=480` hex 字符 = 240 数据字节/块(ble.js:9,100)。 +2. 帧格式(ble.js:104):`cod`(1B: 03整图/04小图) + `code`(1B: ff=bw平面 00=红平面) + offset(2B 大端,=字节序号) + ≤240B 数据。块总长 244B。 +3. cod 判定(ble.js:98-99):hex 长度 7000(=3500B=280x100/8) 或 18000(=9000B=400x180/8) 用 `04`,否则 `03`。 +4. 无应用层 ACK,顺序 writeWithResponse(ble.js:102-106)。 +5. 结束:delay(300) → 整图发 `01`;日历小图(宽280 或 高180)发 `AA`(ble.js:158-160)。 + +上传函数差异: + +| 函数 | 前置 | 打包 | 结束 | +|---|---|---|---| +| upload_image | 无 | 296宽→canvas2bytes_bw;250/212宽→bw;其他→canvas2bytes 双平面 | 01/AA | +| upload_image_moban(bat) | RXTX `EA0{bat}` | 296宽→bw,否则双平面 | 01 | +| upload_image_lb(cmd) | RXTX `EA0{cmd}` | 同 upload_image | 同 | +| upload_image_lb_num(cmd) | RXTX `EA000{cmd-1}` | 不传图 | 无 | + +三色 = 两次发送:bw 平面(code=ff) + 红平面(code=00)(ble.js:154-155,219-222)。 + +## 4. 像素打包(timedjs.js) + +| 函数 | 扫描 | 位序 | 用途 | 证据 | +|---|---|---|---|---| +| canvas2bytes | 行主序 y0→H,x0→W | MSB first,8px/B | 400x300/800x480/280x100/400x180 | timedjs.js:564-584 | +| canvas2bytes_bw | 列主序 x 从 W-1→0,y0→H | MSB first | 2.9寸296x128、2.13寸 | timedjs.js:586-611 | +| canvas2bytes_to | 转置 | MSB first | 未调用 | timedjs.js:612-632 | + +位值:bw 平面 R,G,B 全>0→1(白) 否则 0(黑);bwr 平面 R>0,G=0,B=0→1(红)(timedjs.js:573-576,599-601)。 +400x300 → 每平面 15000B。红色判定:`r>g*1.5 && r>b*1.5 && r>threshold`(timedjs.js:255 等)。 + +抖动算法(dithering() 分发 timedjs.js:513-563): +- 黑白:none/floydsteinberg(7,3,5,1 /16)/atkinson(1/8×6)/bayer(4x4 [[15,135,45,165],[195,75,225,105],[60,180,30,150],[240,120,210,90]])/stucki(/42)/jarvis(/48) +- 黑白红:bwr_ 前缀同名 6 种,默认 bwr_atkinson +- threshold 默认 125;diffusion 0–100/100 误差缩放 +- bwrPalette=黑白红(timedjs.js:504-508) +- 预处理 applyImageAdjustments:亮度/对比度/饱和度(timedjs.js:2-38),灰度 0.299/0.587/0.114 + +## 5. 时钟/日历/倒计时 + +setTime(ble.js:251-257)RXTX 连发 `dd…` + `e2`: + +| 偏移 | 长度 | 含义 | +|---|---|---| +| 0 | 1B | `dd` | +| 1 | 4B | unix 秒(本地时区修正 Date.now()/1000 - tzOffset*60,ble.js:4222) | +| 5 | 2B | 年(大端) | +| 7 | 1B | 月 1–12 | +| 8 | 1B | 日 | +| 9 | 1B | 星期(getDay()||7:周一=1…周日=7) | + +倒计时: +- 时钟界面 gridSize=16:>4 字符(中文=1 英文=0.5)拆两包 `04`+128B / `0{n}`+余;≤4 单包 `0{n}`(timedjs2.js:206-224) +- 日历界面 gridSize=24:每字符一包 `990{j}`+72B,间隔1s(timedjs2.js:228-235) +- 日期帧 `ff00{year}{MM}{dd}`(时钟)/`ffff...`(日历),year=4位十进制ASCII(timedjs2.js:244-254) +- 收尾 `E2`;关闭 `FA`+1s+`E2`(timedjs2.js:141-145) +- upload_rili:`990*`→`FA`→`e10103`→`E2`(timedjs2.js:308-312) + +## 6. 模板系统(moban.js) + +6 模板渲染到 #unifiedCanvas(#epdwh 选 400x300/800x480/296x128),无 BLE 代码;上传走 upload_image_moban(0)→`EA00`+图块+`01`(index.html:1243)。 + +| 模板 | data-template | 渲染 | +|---|---|---| +| 日程 schedule | renderPresetTemplate | moban.js:140-145,369-467 | +| 名片 businesscard | 同上 | moban.js:146-151 | +| 备忘录 memo | 同上 | moban.js:152-157 | +| 课程表 course | renderCourseTable(默认5天x4节,3–7天、2–9节) | moban.js:29-63,165-317 | +| 二维码 qrcode | renderQRCodeMode(qrcodejs CorrectLevel.M) | moban.js:81-113,501-570 | +| 自定义文本 customText | renderCustomMode(自动换行) | moban.js:572-693 | + +红字语法 `【…】`(parseColoredText moban.js:470-498);反色模式 7 种(moban.js:122-137)。 + +## 7. 激活机制 + +- API:`http://4638e813e4.qicp.vip:8889/key_generator.php`(ble.js:3866) +- fullAutoActivate(ble.js:3932-3955): + 1. 读 #fixedId(用户固定ID,UI 隐藏 index.html:930-935) + 2. getDeviceIdFromScreen:RXTX 发 `EFEF`,8s 超时,notify 累计 14 字节(EXPECTED_ID_BYTES=14 ble.js:4123),可打印转 ASCII 否则 hex(ble.js:4121-4137) + 3. requestActivationCode:GET `?id={fixedId}&key={deviceId}`(ble.js:3879-3886) + 4. extractActivationCode:正则 `激活码1:([0-9A-F]{32,})` / `激活码:(…)` / 任意≥32hex(ble.js:3869-3876) + 5. sendActivationToDevice:`EF`+大写激活码,400ms 后 `e2`(ble.js:3889-3902) +- 算法在服务端 key_generator.php,客户端无实现。自助站 qbsg.top,作者微信 2549294317。 +- 固件下载同 host:`http://4638e813e4.qicp.vip:8889/GuJian/file_down.php`(index.html:836),型号列表在服务端。 + +## 8. 高级命令 + +index.html:919-920:#cmdcmd 任意 hex → triggerRxTxCmd 直发 RXTX。 +hex.js:2-15:hexToBytes/bytesToHex/intToHex(大端 pad)。 + +## 9. 变体与其他 + +- LED=灯板变体(遗留);XM=休眠(FB00/FB01);LB=轮播(e901/EA000);CHEPAI=停车牌;JH=激活;lut=LUT 校准 E6 +- 设备名前缀仅 `NRF-`(ble.js:4053) +- chinaCities 城市→天气代码表 ble.js:259 起约3580行,用于 WIFI 配置 CITY 字段 +- 死代码:SendCommand(i 未初始化)、upload_image_lbs(未定义) diff --git a/analysis/web/switch2.js b/analysis/web/switch2.js new file mode 100644 index 0000000..cf48efa --- /dev/null +++ b/analysis/web/switch2.js @@ -0,0 +1,55 @@ + + + document.getElementById('brightness').addEventListener('input', function() { + document.getElementById('brightnessValue').textContent = this.value; + }); + document.getElementById('contrast').addEventListener('input', function() { + document.getElementById('contrastValue').textContent = this.value; + }); + document.getElementById('saturation').addEventListener('input', function() { + document.getElementById('saturationValue').textContent = this.value; + }); + document.getElementById('diffusion').addEventListener('input', function() { + document.getElementById('diffusionValue').textContent = this.value; + }); + // 获取 DOM 元素 + const brightnessSlider = document.getElementById('brightness'); + const contrastSlider = document.getElementById('contrast'); + const saturationSlider = document.getElementById('saturation'); + const diffusionSlider = document.getElementById('diffusion'); + + const brightnessValue = document.getElementById('brightnessValue'); + const contrastValue = document.getElementById('contrastValue'); + const saturationValue = document.getElementById('saturationValue'); + const diffusionValue = document.getElementById('diffusionValue'); + + + + + + // 应用滤镜效果 + function applyFilters() { + update_image(); + } + // 监听滑块变化事件(实时刷新) + brightnessSlider.addEventListener('input', applyFilters); + contrastSlider.addEventListener('input', applyFilters); + saturationSlider.addEventListener('input', applyFilters); + diffusionSlider.addEventListener('input', applyFilters); + + // 重置所有参数 + function resetAll() { + brightnessSlider.value = '0'; + contrastSlider.value = '0'; + saturationSlider.value = '35'; + diffusionSlider.value = '100'; + // 更新显示数值 + brightnessValue.textContent = 0; + contrastValue.textContent = 0; + saturationValue.textContent = 35; + diffusionValue.textContent = 100; + updateRotation(0) ; + updateScale(100) ; + update_image(); + } + \ No newline at end of file diff --git a/analysis/web/time.js b/analysis/web/time.js new file mode 100644 index 0000000..171d671 --- /dev/null +++ b/analysis/web/time.js @@ -0,0 +1,21 @@ + +function toggleSwitch(e) { + const rect = e.currentTarget.getBoundingClientRect(); + const clickX = e.clientX - rect.left; + + + if (clickX < rect.width / 2) { + // 选中时钟 + switchBg.style.left = '0'; + + gridSizeSelect.value=16; + } else { + // 选中日历 + + switchBg.style.left = '50%'; + gridSizeSelect.value=24; + } + +} + + diff --git a/analysis/web/timedjs.js b/analysis/web/timedjs.js new file mode 100644 index 0000000..cc9fa7c --- /dev/null +++ b/analysis/web/timedjs.js @@ -0,0 +1,715 @@ + + function applyImageAdjustments(canvas) { + const brightness = parseInt(document.getElementById('brightness').value); + const contrast = parseInt(document.getElementById('contrast').value); + const saturation = parseInt(document.getElementById('saturation').value); + const diffusion = parseInt(document.getElementById('diffusion').value) / 100; + const ctx = canvas.getContext('2d'); + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const data = imageData.data; + if (brightness !== 0) { + const brightnessFactor = brightness / 100; + for (let i = 0; i < data.length; i += 4) { + data[i] = clamp(data[i] + 255 * brightnessFactor, 0, 255); + data[i + 1] = clamp(data[i + 1] + 255 * brightnessFactor, 0, 255); + data[i + 2] = clamp(data[i + 2] + 255 * brightnessFactor, 0, 255); + } + } + if (contrast !== 0) { + const contrastFactor = (contrast + 100) / 100; + const contrastAdjust = (1 - contrastFactor) * 128; + for (let i = 0; i < data.length; i += 4) { + data[i] = clamp(data[i] * contrastFactor + contrastAdjust, 0, 255); + data[i + 1] = clamp(data[i + 1] * contrastFactor + contrastAdjust, 0, 255); + data[i + 2] = clamp(data[i + 2] * contrastFactor + contrastAdjust, 0, 255); + } + } + if (saturation !== 100) { + const saturationFactor = saturation / 100; + for (let i = 0; i < data.length; i += 4) { + const gray = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2]; + data[i] = clamp(gray + (data[i] - gray) * saturationFactor, 0, 255); + data[i + 1] = clamp(gray + (data[i + 1] - gray) * saturationFactor, 0, 255); + data[i + 2] = clamp(gray + (data[i + 2] - gray) * saturationFactor, 0, 255); + } + } + ctx.putImageData(imageData, 0, 0); + return diffusion; + } + function clamp(value, min, max) { + return Math.min(Math.max(value, min), max); + } + function applyNoneDithering(ctx, width, height, threshold, isColor = false) { + const imageData = ctx.getImageData(0, 0, width, height); + const data = imageData.data; + for (let i = 0; i < data.length; i += 4) { + const gray = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2]; + const value = gray < threshold ? 0 : 255; + data[i] = data[i + 1] = data[i + 2] = value; + } + ctx.putImageData(imageData, 0, 0); + } + function applyFloydSteinbergDithering(ctx, width, height, threshold, diffusion = 1.0, isColor = false) { + const imageData = ctx.getImageData(0, 0, width, height); + const data = imageData.data; + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + const idx = (y * width + x) * 4; + const oldR = data[idx]; + const oldG = data[idx + 1]; + const oldB = data[idx + 2]; + const gray = 0.299 * oldR + 0.587 * oldG + 0.114 * oldB; + const newPixel = gray < threshold ? 0 : 255; + const err = (gray - newPixel) * diffusion; + data[idx] = data[idx + 1] = data[idx + 2] = newPixel; + if (x + 1 < width) { + addError(data, idx + 4, err, 7/16); + } + if (y + 1 < height) { + if (x - 1 >= 0) { + addError(data, idx + width * 4 - 4, err, 3/16); + } + addError(data, idx + width * 4, err, 5/16); + if (x + 1 < width) { + addError(data, idx + width * 4 + 4, err, 1/16); + } + } + } + } + ctx.putImageData(imageData, 0, 0); + } + function applyAtkinsonDithering(ctx, width, height, threshold, diffusion = 1.0, isColor = false) { + const imageData = ctx.getImageData(0, 0, width, height); + const data = imageData.data; + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + const idx = (y * width + x) * 4; + const oldR = data[idx]; + const oldG = data[idx + 1]; + const oldB = data[idx + 2]; + const gray = 0.299 * oldR + 0.587 * oldG + 0.114 * oldB; + const newPixel = gray < threshold ? 0 : 255; + const err = (gray - newPixel) * diffusion; + data[idx] = data[idx + 1] = data[idx + 2] = newPixel; + if (x + 1 < width) { + addError(data, idx + 4, err, 1/8); + } + if (x + 2 < width) { + addError(data, idx + 8, err, 1/8); + } + if (y + 1 < height) { + if (x - 1 >= 0) { + addError(data, idx + width * 4 - 4, err, 1/8); + } + addError(data, idx + width * 4, err, 1/8); + if (x + 1 < width) { + addError(data, idx + width * 4 + 4, err, 1/8); + } + } + if (y + 2 < height) { + addError(data, idx + width * 8, err, 1/8); + } + } + } + ctx.putImageData(imageData, 0, 0); + } + function applyBayerDithering(ctx, width, height, threshold, diffusion = 1.0, isColor = false) { + const bayerThresholdMap = [ + [15, 135, 45, 165], + [195, 75, 225, 105], + [60, 180, 30, 150], + [240, 120, 210, 90] + ]; + const imageData = ctx.getImageData(0, 0, width, height); + const data = imageData.data; + for (let i = 0; i < data.length; i += 4) { + const gray = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2]; + const x = (i / 4) % width; + const y = Math.floor((i / 4) / width); + const mapValue = bayerThresholdMap[x % 4][y % 4]; + const adjustedThreshold = threshold * (1 + (mapValue - 128) / 128 * diffusion); + const finalThreshold = clamp(adjustedThreshold, 0, 255); + const newPixel = gray < finalThreshold ? 0 : 255; + data[i] = data[i + 1] = data[i + 2] = newPixel; + } + ctx.putImageData(imageData, 0, 0); + } + function applyStuckiDithering(ctx, width, height, threshold, diffusion = 1.0, isColor = false) { + const imageData = ctx.getImageData(0, 0, width, height); + const data = imageData.data; + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + const idx = (y * width + x) * 4; + const oldR = data[idx]; + const oldG = data[idx + 1]; + const oldB = data[idx + 2]; + const gray = 0.299 * oldR + 0.587 * oldG + 0.114 * oldB; + const newPixel = gray < threshold ? 0 : 255; + const err = (gray - newPixel) * diffusion; + data[idx] = data[idx + 1] = data[idx + 2] = newPixel; + if (x + 1 < width) { + addError(data, idx + 4, err, 8/42); + } + if (x + 2 < width) { + addError(data, idx + 8, err, 4/42); + } + if (y + 1 < height) { + if (x - 2 >= 0) { + addError(data, idx + width * 4 - 8, err, 2/42); + } + if (x - 1 >= 0) { + addError(data, idx + width * 4 - 4, err, 4/42); + } + addError(data, idx + width * 4, err, 8/42); + if (x + 1 < width) { + addError(data, idx + width * 4 + 4, err, 4/42); + } + if (x + 2 < width) { + addError(data, idx + width * 4 + 8, err, 2/42); + } + } + if (y + 2 < height) { + if (x - 2 >= 0) { + addError(data, idx + width * 8 - 8, err, 1/42); + } + if (x - 1 >= 0) { + addError(data, idx + width * 8 - 4, err, 2/42); + } + addError(data, idx + width * 8, err, 4/42); + if (x + 1 < width) { + addError(data, idx + width * 8 + 4, err, 2/42); + } + if (x + 2 < width) { + addError(data, idx + width * 8 + 8, err, 1/42); + } + } + } + } + ctx.putImageData(imageData, 0, 0); + } + function applyJarvisJudiceNinkeDithering(ctx, width, height, threshold, diffusion = 1.0, isColor = false) { + const imageData = ctx.getImageData(0, 0, width, height); + const data = imageData.data; + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + const idx = (y * width + x) * 4; + const oldR = data[idx]; + const oldG = data[idx + 1]; + const oldB = data[idx + 2]; + const gray = 0.299 * oldR + 0.587 * oldG + 0.114 * oldB; + const newPixel = gray < threshold ? 0 : 255; + const err = (gray - newPixel) * diffusion; + data[idx] = data[idx + 1] = data[idx + 2] = newPixel; + if (x + 1 < width) { + addError(data, idx + 4, err, 7/48); + } + if (x + 2 < width) { + addError(data, idx + 8, err, 5/48); + } + if (y + 1 < height) { + if (x - 2 >= 0) { + addError(data, idx + width * 4 - 8, err, 3/48); + } + if (x - 1 >= 0) { + addError(data, idx + width * 4 - 4, err, 5/48); + } + addError(data, idx + width * 4, err, 7/48); + if (x + 1 < width) { + addError(data, idx + width * 4 + 4, err, 5/48); + } + if (x + 2 < width) { + addError(data, idx + width * 4 + 8, err, 3/48); + } + } + if (y + 2 < height) { + if (x - 2 >= 0) { + addError(data, idx + width * 8 - 8, err, 1/48); + } + if (x - 1 >= 0) { + addError(data, idx + width * 8 - 4, err, 3/48); + } + addError(data, idx + width * 8, err, 5/48); + if (x + 1 < width) { + addError(data, idx + width * 8 + 4, err, 3/48); + } + if (x + 2 < width) { + addError(data, idx + width * 8 + 8, err, 1/48); + } + } + } + } + ctx.putImageData(imageData, 0, 0); + } + function addError(data, idx, err, factor) { + data[idx] = clamp(data[idx] + err * factor, 0, 255); + data[idx + 1] = clamp(data[idx + 1] + err * factor, 0, 255); + data[idx + 2] = clamp(data[idx + 2] + err * factor, 0, 255); + } + function applyBWRNoneDithering(ctx, width, height, threshold, diffusion = 1.0) { + const imageData = ctx.getImageData(0, 0, width, height); + const data = imageData.data; + for (let i = 0; i < data.length; i += 4) { + const r = data[i]; + const g = data[i + 1]; + const b = data[i + 2]; + if (r > g * 1.5 && r > b * 1.5 && r > threshold) { + data[i] = 255; + data[i + 1] = 0; + data[i + 2] = 0; + } else { + const gray = 0.299 * r + 0.587 * g + 0.114 * b; + const value = gray < threshold ? 0 : 255; + data[i] = data[i + 1] = data[i + 2] = value; + } + } + ctx.putImageData(imageData, 0, 0); + } + function applyBWRFloydSteinbergDithering(ctx, width, height, threshold, diffusion = 1.0) { + const imageData = ctx.getImageData(0, 0, width, height); + const data = imageData.data; + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + const idx = (y * width + x) * 4; + const oldR = data[idx]; + const oldG = data[idx + 1]; + const oldB = data[idx + 2]; + if (oldR > oldG * 1.5 && oldR > oldB * 1.5 && oldR > threshold) { + data[idx] = 255; + data[idx + 1] = 0; + data[idx + 2] = 0; + continue; + } + const gray = 0.299 * oldR + 0.587 * oldG + 0.114 * oldB; + const newPixel = gray < threshold ? 0 : 255; + const errR = (oldR - newPixel) * diffusion; + const errG = (oldG - newPixel) * diffusion; + const errB = (oldB - newPixel) * diffusion; + data[idx] = data[idx + 1] = data[idx + 2] = newPixel; + if (x + 1 < width) { + addColorError(data, idx + 4, errR, errG, errB, 7/16); + } + if (y + 1 < height) { + if (x - 1 >= 0) { + addColorError(data, idx + width * 4 - 4, errR, errG, errB, 3/16); + } + addColorError(data, idx + width * 4, errR, errG, errB, 5/16); + if (x + 1 < width) { + addColorError(data, idx + width * 4 + 4, errR, errG, errB, 1/16); + } + } + } + } + ctx.putImageData(imageData, 0, 0); + } + function applyBWRAtkinsonDithering(ctx, width, height, threshold, diffusion = 1.0) { + const imageData = ctx.getImageData(0, 0, width, height); + const data = imageData.data; + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + const idx = (y * width + x) * 4; + const oldR = data[idx]; + const oldG = data[idx + 1]; + const oldB = data[idx + 2]; + if (oldR > oldG * 1.5 && oldR > oldB * 1.5 && oldR > threshold) { + data[idx] = 255; + data[idx + 1] = 0; + data[idx + 2] = 0; + continue; + } + const gray = 0.299 * oldR + 0.587 * oldG + 0.114 * oldB; + const newPixel = gray < threshold ? 0 : 255; + const errR = (oldR - newPixel) * diffusion; + const errG = (oldG - newPixel) * diffusion; + const errB = (oldB - newPixel) * diffusion; + data[idx] = data[idx + 1] = data[idx + 2] = newPixel; + if (x + 1 < width) { + addColorError(data, idx + 4, errR, errG, errB, 1/8); + } + if (x + 2 < width) { + addColorError(data, idx + 8, errR, errG, errB, 1/8); + } + if (y + 1 < height) { + if (x - 1 >= 0) { + addColorError(data, idx + width * 4 - 4, errR, errG, errB, 1/8); + } + addColorError(data, idx + width * 4, errR, errG, errB, 1/8); + if (x + 1 < width) { + addColorError(data, idx + width * 4 + 4, errR, errG, errB, 1/8); + } + } + if (y + 2 < height) { + addColorError(data, idx + width * 8, errR, errG, errB, 1/8); + } + } + } + ctx.putImageData(imageData, 0, 0); + } + function applyBWRBayerDithering(ctx, width, height, threshold, diffusion = 1.0) { + const bayerThresholdMap = [ + [15, 135, 45, 165], + [195, 75, 225, 105], + [60, 180, 30, 150], + [240, 120, 210, 90] + ]; + const imageData = ctx.getImageData(0, 0, width, height); + const data = imageData.data; + for (let i = 0; i < data.length; i += 4) { + const r = data[i]; + const g = data[i + 1]; + const b = data[i + 2]; + const x = (i / 4) % width; + const y = Math.floor((i / 4) / width); + const mapValue = bayerThresholdMap[x % 4][y % 4]; + const adjustedThreshold = threshold * (1 + (mapValue - 128) / 128 * diffusion); + const finalThreshold = clamp(adjustedThreshold, 0, 255); + if (r > g * 1.5 && r > b * 1.5 && r > finalThreshold) { + data[i] = 255; + data[i + 1] = 0; + data[i + 2] = 0; + } else { + const gray = 0.299 * r + 0.587 * g + 0.114 * b; + const value = gray < finalThreshold ? 0 : 255; + data[i] = data[i + 1] = data[i + 2] = value; + } + } + ctx.putImageData(imageData, 0, 0); + } + function applyBWRStuckiDithering(ctx, width, height, threshold, diffusion = 1.0) { + const imageData = ctx.getImageData(0, 0, width, height); + const data = imageData.data; + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + const idx = (y * width + x) * 4; + const oldR = data[idx]; + const oldG = data[idx + 1]; + const oldB = data[idx + 2]; + if (oldR > oldG * 1.5 && oldR > oldB * 1.5 && oldR > threshold) { + data[idx] = 255; + data[idx + 1] = 0; + data[idx + 2] = 0; + continue; + } + const gray = 0.299 * oldR + 0.587 * oldG + 0.114 * oldB; + const newPixel = gray < threshold ? 0 : 255; + const errR = (oldR - newPixel) * diffusion; + const errG = (oldG - newPixel) * diffusion; + const errB = (oldB - newPixel) * diffusion; + data[idx] = data[idx + 1] = data[idx + 2] = newPixel; + if (x + 1 < width) { + addColorError(data, idx + 4, errR, errG, errB, 8/42); + } + if (x + 2 < width) { + addColorError(data, idx + 8, errR, errG, errB, 4/42); + } + if (y + 1 < height) { + if (x - 2 >= 0) { + addColorError(data, idx + width * 4 - 8, errR, errG, errB, 2/42); + } + if (x - 1 >= 0) { + addColorError(data, idx + width * 4 - 4, errR, errG, errB, 4/42); + } + addColorError(data, idx + width * 4, errR, errG, errB, 8/42); + if (x + 1 < width) { + addColorError(data, idx + width * 4 + 4, errR, errG, errB, 4/42); + } + if (x + 2 < width) { + addColorError(data, idx + width * 4 + 8, errR, errG, errB, 2/42); + } + } + if (y + 2 < height) { + if (x - 2 >= 0) { + addColorError(data, idx + width * 8 - 8, errR, errG, errB, 1/42); + } + if (x - 1 >= 0) { + addColorError(data, idx + width * 8 - 4, errR, errG, errB, 2/42); + } + addColorError(data, idx + width * 8, errR, errG, errB, 4/42); + if (x + 1 < width) { + addColorError(data, idx + width * 8 + 4, errR, errG, errB, 2/42); + } + if (x + 2 < width) { + addColorError(data, idx + width * 8 + 8, errR, errG, errB, 1/42); + } + } + } + } + ctx.putImageData(imageData, 0, 0); + } + function applyBWRJarvisJudiceNinkeDithering(ctx, width, height, threshold, diffusion = 1.0) { + const imageData = ctx.getImageData(0, 0, width, height); + const data = imageData.data; + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + const idx = (y * width + x) * 4; + const oldR = data[idx]; + const oldG = data[idx + 1]; + const oldB = data[idx + 2]; + if (oldR > oldG * 1.5 && oldR > oldB * 1.5 && oldR > threshold) { + data[idx] = 255; + data[idx + 1] = 0; + data[idx + 2] = 0; + continue; + } + const gray = 0.299 * oldR + 0.587 * oldG + 0.114 * oldB; + const newPixel = gray < threshold ? 0 : 255; + const errR = (oldR - newPixel) * diffusion; + const errG = (oldG - newPixel) * diffusion; + const errB = (oldB - newPixel) * diffusion; + data[idx] = data[idx + 1] = data[idx + 2] = newPixel; + if (x + 1 < width) { + addColorError(data, idx + 4, errR, errG, errB, 7/48); + } + if (x + 2 < width) { + addColorError(data, idx + 8, errR, errG, errB, 5/48); + } + if (y + 1 < height) { + if (x - 2 >= 0) { + addColorError(data, idx + width * 4 - 8, errR, errG, errB, 3/48); + } + if (x - 1 >= 0) { + addColorError(data, idx + width * 4 - 4, errR, errG, errB, 5/48); + } + addColorError(data, idx + width * 4, errR, errG, errB, 7/48); + if (x + 1 < width) { + addColorError(data, idx + width * 4 + 4, errR, errG, errB, 5/48); + } + if (x + 2 < width) { + addColorError(data, idx + width * 4 + 8, errR, errG, errB, 3/48); + } + } + if (y + 2 < height) { + if (x - 2 >= 0) { + addColorError(data, idx + width * 8 - 8, errR, errG, errB, 1/48); + } + if (x - 1 >= 0) { + addColorError(data, idx + width * 8 - 4, errR, errG, errB, 3/48); + } + addColorError(data, idx + width * 8, errR, errG, errB, 5/48); + if (x + 1 < width) { + addColorError(data, idx + width * 8 + 4, errR, errG, errB, 3/48); + } + if (x + 2 < width) { + addColorError(data, idx + width * 8 + 8, errR, errG, errB, 1/48); + } + } + } + } + ctx.putImageData(imageData, 0, 0); + } + function addColorError(data, idx, errR, errG, errB, factor) { + data[idx] = clamp(data[idx] + errR * factor, 0, 255); + data[idx + 1] = clamp(data[idx + 1] + errG * factor, 0, 255); + data[idx + 2] = clamp(data[idx + 2] + errB * factor, 0, 255); + } + 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 diffusion = parseInt(document.getElementById('diffusion').value) / 100; + if (type.startsWith('bwr_')) { + const bwrType = type.replace('bwr_', ''); + switch(bwrType) { + case 'none': + applyBWRNoneDithering(ctx, width, height, threshold, diffusion); + break; + case 'floydsteinberg': + applyBWRFloydSteinbergDithering(ctx, width, height, threshold, diffusion); + break; + case 'atkinson': + applyBWRAtkinsonDithering(ctx, width, height, threshold, diffusion); + break; + case 'bayer': + applyBWRBayerDithering(ctx, width, height, threshold, diffusion); + break; + case 'stucki': + applyBWRStuckiDithering(ctx, width, height, threshold, diffusion); + break; + case 'jarvis': + applyBWRJarvisJudiceNinkeDithering(ctx, width, height, threshold, diffusion); + break; + default: + applyBWRNoneDithering(ctx, width, height, threshold, diffusion); + } + } else { + switch(type) { + case 'none': + applyNoneDithering(ctx, width, height, threshold, false); + break; + case 'floydsteinberg': + applyFloydSteinbergDithering(ctx, width, height, threshold, diffusion, false); + break; + case 'atkinson': + applyAtkinsonDithering(ctx, width, height, threshold, diffusion, false); + break; + case 'bayer': + applyBayerDithering(ctx, width, height, threshold, diffusion, false); + break; + case 'stucki': + applyStuckiDithering(ctx, width, height, threshold, diffusion, false); + break; + case 'jarvis': + applyJarvisJudiceNinkeDithering(ctx, width, height, threshold, diffusion, false); + break; + default: + applyNoneDithering(ctx, width, height, threshold, false); + } + } + } + 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 * 4 * 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 canvas2bytes_bw(canvas, type='bw') { + const ctx = canvas.getContext("2d"); + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + + const arr = []; + let buffer = []; + + + for (let x =canvas.width-1; x >= 0; x--) { + for (let y = 0; y < canvas.height; y++) { + const index = (canvas.width * 4 * 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 canvas2bytes_to(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.width; y++) { + for (let x = 0; x < canvas.height; x++) { + const index = (canvas.width * 4 * x) + y * 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 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/analysis/web/timedjs2.js b/analysis/web/timedjs2.js new file mode 100644 index 0000000..1943f8d --- /dev/null +++ b/analysis/web/timedjs2.js @@ -0,0 +1,323 @@ + + document.addEventListener('DOMContentLoaded', function () { + const textInput = document.getElementById('textInput'); + const fontFamily = document.getElementById('fontFamily'); + const invertColors = document.getElementById('invertColors'); + const showGrid = document.getElementById('showGrid'); + const generateBtn = document.getElementById('generateBtn'); + const upload_rili_text = document.getElementById('upload_rili_text'); + + const previewCanvas = document.getElementById('previewCanvas'); + const codeOutput = document.getElementById('codeOutput'); + const copyBtn = document.getElementById('copyBtn'); + const gridInfoText = document.getElementById('gridInfoText'); + const fontSizeSlider = document.getElementById('fontSizeSlider'); + const fontSizeValue = document.getElementById('fontSizeValue'); + const gridSizeSelect = document.getElementById('gridSizeSelect'); + const currentGridSize = document.getElementById('currentGridSize'); + const sizeHint = document.getElementById('sizeHint'); + const ctx = previewCanvas.getContext('2d'); + document.getElementById('fullAutoActivateBtn').addEventListener('click', fullAutoActivate); + + const previewScale = 8; + let fontSizeRatio = 0.85; + + const grid_Size=16; + updateGridSize(); + drawPreview(); + generateBtn.addEventListener('click', function () { + drawPreview(); + generateCode(); + }); + upload_rili_text.addEventListener('click', function () { + drawPreview(); + upload_rili(); + }); + copyBtn.addEventListener('click', function () { + sendcode(); + }); + fontSizeSlider.addEventListener('input', function () { + const value = this.value; + fontSizeValue.textContent = `${value}%`; + fontSizeRatio = value / 100; + drawPreview(); + }); + gridSizeSelect.addEventListener('change', function () { + updateGridSize(); + drawPreview(); + }); + function getCurrentGridSize() { + return parseInt(gridSizeSelect.value); + } + function updateGridSize() { + const gridSize = getCurrentGridSize(); + currentGridSize.textContent = ``; + } + function formatDate(date) { + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + return `${year}年${month}月${day}日`; + } + + function drawPreview() { +const text = textInput.value || 'ABCDEFGH'; +const charCount = text.length; +const font = fontFamily.value; +const invert = invertColors.checked; +const grid = showGrid.checked; +const gridSize = getCurrentGridSize(); +const gridSize_all=gridSize==16?128:96; +// 计算实际显示宽度 +function getCharWidth(char) { + if (/[\u4e00-\u9fff\u3000-\u303f\uff00-\uffef]/.test(char)) { + return gridSize; // 汉字/中文标点 + } + return gridSize/2; // 英文/数字/英文标点 +} + +function getTotalWidth(str) { + let width = 0; + for (let char of str) { + width += getCharWidth(char); + } + return width; +} + + +const totalDisplayWidth = getTotalWidth(text); + + +const actualCharWidth = gridSize * getCharWidth(text[0] || 'A'); +if (totalDisplayWidth > gridSize_all) { + alert(`超出总字符(当前:${totalDisplayWidth},时钟倒计时最大支持16个英文或8个汉字,日历倒计时最大支持8个英文或4个汉字)`); + + return; +} +previewCanvas.width = totalDisplayWidth * previewScale; +previewCanvas.height = gridSize * previewScale; + +ctx.clearRect(0, 0, previewCanvas.width, previewCanvas.height); +ctx.fillStyle = invert ? '#000000' : '#ffffff'; +ctx.fillRect(0, 0, previewCanvas.width, previewCanvas.height); + + +let currentX = 0; +ctx.font = `bold ${gridSize * fontSizeRatio * previewScale}px ${font}`; +ctx.textBaseline = 'middle'; +ctx.fillStyle = invert ? '#ffffff' : '#000000'; + +for (let i = 0; i < charCount; i++) { + const currentChar = text[i]; + const charDisplayWidth = getCharWidth(currentChar); + const charPixelWidth = charDisplayWidth * previewScale; + const charX = currentX + charPixelWidth / 2; + + ctx.textAlign = 'center'; + ctx.fillText(currentChar, charX, previewCanvas.height / 2); + + currentX += charPixelWidth; +} + if (grid) { + ctx.strokeStyle = invert ? '#333333' : '#dddddd'; + for (let x = 0; x <= charCount * gridSize; x++) { + const lineX = x * previewScale; + ctx.beginPath(); + ctx.moveTo(lineX, 0); + ctx.lineTo(lineX, previewCanvas.height); + ctx.lineWidth = x % gridSize === 0 ? 2 : 1; + ctx.stroke(); + } + for (let y = 0; y <= gridSize; y++) { + const lineY = y * previewScale; + ctx.beginPath(); + ctx.moveTo(0, lineY); + ctx.lineTo(previewCanvas.width, lineY); + ctx.lineWidth = 1; + ctx.stroke(); + } + } + } + async function sendcode() { + triggerRxTxCmd('FA'); + await delay(1000); + triggerRxTxCmd('E2'); + } + + + function calculateTextWidth(text) { + let totalWidth = 0; + + for (let i = 0; i < text.length; i++) { + const char = text[i]; + + if (/[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff\u2e80-\u2eff\u3000-\u303f\u31c0-\u31ef\u2f00-\u2fdf\u2e80-\u2eff\u1100-\u11ff\u3130-\u318f\uac00-\ud7af]/.test(char)) { + totalWidth += 2; // 中文字符宽度 + } else { + totalWidth += 1; // 英文字符及数字、标点等宽度 + } + } + + return Math.ceil(totalWidth / 2); +} + + + async function generateCode() { + const text = textInput.value || 'ABCDEFGH'; + const gridSize = getCurrentGridSize(); + const charCount =calculateTextWidth(text) ; + const font = fontFamily.value; + const invert = invertColors.checked; + + const totalGridWidth = charCount * gridSize; + const totalGridHeight = gridSize; + const tempCanvas = document.createElement('canvas'); + tempCanvas.width = totalGridWidth; + tempCanvas.height = totalGridHeight; + const tempCtx = tempCanvas.getContext('2d'); + tempCtx.fillStyle = invert ? '#000000' : '#ffffff'; + tempCtx.fillRect(0, 0, totalGridWidth, totalGridHeight); + tempCtx.font = `bold ${totalGridHeight * fontSizeRatio}px ${font}`; + tempCtx.textAlign = 'center'; + tempCtx.textBaseline = 'middle'; + tempCtx.fillStyle = invert ? '#ffffff' : '#000000'; + tempCtx.fillText(text, totalGridWidth / 2, totalGridHeight / 2); + const imageData = tempCtx.getImageData(0, 0, totalGridWidth, totalGridHeight); + const data = imageData.data; + const allBytes = []; + for (let y = 0; y < totalGridHeight; y++) { + const bytesPerLine = totalGridWidth / 8; + const lineBytes = new Array(bytesPerLine).fill(0); + for (let x = 0; x < totalGridWidth; x++) { + const pixelIndex = (y * totalGridWidth + x) * 4; + const brightness = (data[pixelIndex] + data[pixelIndex + 1] + data[pixelIndex + 2]) / 3; + const bitValue = invert ? (brightness > 128 ? 0 : 1) : (brightness > 128 ? 1 : 0); + const byteIndex = Math.floor(x / 8); + const bitPosition = 7 - (x % 8); + lineBytes[byteIndex] |= (bitValue << bitPosition); + } + allBytes.push(...lineBytes); + } + + + + if (gridSize == 16) { + if (charCount > 8) {addLog(`设置字符超过了16个(纯中文字符最多4个,纯英文字符最多8个)`); return;} + if (charCount > 4) { + let code = '0' + 4; + for (let i = 0; i < allBytes.length - 1 - (charCount - 4) * 32; i++) { + code += `${allBytes[i].toString(16).padStart(2, '0')}`; + } + triggerRxTxCmd(code); + await delay(1000); + let code1 = '0' + charCount; + for (let i = 128; i < allBytes.length - 1; i++) { + code1 += `${allBytes[i].toString(16).padStart(2, '0')}`; + } + triggerRxTxCmd(code1); + } else { + let code = '0' + (charCount); + for (let i = 0; i < allBytes.length - 1; i++) { + code += `${allBytes[i].toString(16).padStart(2, '0')}`; + } + triggerRxTxCmd(code); + } + } else if (gridSize == 24) { + if (charCount > 4) {addLog(`设置字符超过了8个(纯中文字符最多4个,纯英文字符最多8个)`); return;} + + for (let j = 0; j < charCount; j++) { + let code = '990' + j; + for (let i = 0; i < 72; i++) { + code += `${allBytes[i + j * 72].toString(16).padStart(2, '0')}`; + } + triggerRxTxCmd(code); + await delay(1000); + } + } + const selectedDate = new Date(datePicker.value); + let year = selectedDate.getFullYear(); + let month = selectedDate.getMonth() + 1; + let day = selectedDate.getDate(); + await delay(1000); + if (month < 10) month = '0' + month; + if (day < 10) day = '0' + day; + if (gridSize == 24) { + let code2 = 'ffff' + year + month + day; + if (year > 2000) { + triggerRxTxCmd(code2); + }; + } else { + let code2 = 'ff00' + year + month + day; + if (year > 2000) { + triggerRxTxCmd(code2); + }; + } + await delay(1000); + triggerRxTxCmd('E2'); + } + + + async function upload_rili() { + const text = textInput.value || 'ABCDEFGH'; + const gridSize = getCurrentGridSize(); + const charCount = calculateTextWidth(text); + const font = fontFamily.value; + const invert = invertColors.checked; + + const totalGridWidth = charCount * gridSize; + const totalGridHeight = gridSize; + const tempCanvas = document.createElement('canvas'); + tempCanvas.width = totalGridWidth; + tempCanvas.height = totalGridHeight; + const tempCtx = tempCanvas.getContext('2d'); + tempCtx.fillStyle = invert ? '#000000' : '#ffffff'; + tempCtx.fillRect(0, 0, totalGridWidth, totalGridHeight); + tempCtx.font = `bold ${totalGridHeight * fontSizeRatio}px ${font}`; + tempCtx.textAlign = 'center'; + tempCtx.textBaseline = 'middle'; + tempCtx.fillStyle = invert ? '#ffffff' : '#000000'; + tempCtx.fillText(text, totalGridWidth / 2, totalGridHeight / 2); + const imageData = tempCtx.getImageData(0, 0, totalGridWidth, totalGridHeight); + const data = imageData.data; + const allBytes = []; + for (let y = 0; y < totalGridHeight; y++) { + const bytesPerLine = totalGridWidth / 8; + const lineBytes = new Array(bytesPerLine).fill(0); + for (let x = 0; x < totalGridWidth; x++) { + const pixelIndex = (y * totalGridWidth + x) * 4; + const brightness = (data[pixelIndex] + data[pixelIndex + 1] + data[pixelIndex + 2]) / 3; + const bitValue = invert ? (brightness > 128 ? 0 : 1) : (brightness > 128 ? 1 : 0); + const byteIndex = Math.floor(x / 8); + const bitPosition = 7 - (x % 8); + lineBytes[byteIndex] |= (bitValue << bitPosition); + } + allBytes.push(...lineBytes); + } + if (gridSize == 24) { + if (charCount > 4) {addLog(`设置字符超过了8个(纯中文字符最多4个,纯英文字符最多8个)`); return;} + + for (let j = 0; j < charCount; j++) { + let code = '990' + j; + for (let i = 0; i < 72; i++) { + code += `${allBytes[i + j * 72].toString(16).padStart(2, '0')}`; + } + triggerRxTxCmd(code); + await delay(1000); + } + } + triggerRxTxCmd('FA'); + await delay(1000); + triggerRxTxCmd('e10103'); + await delay(1000); + triggerRxTxCmd('E2'); + } + + + + textInput.addEventListener('input', drawPreview); + fontFamily.addEventListener('change', drawPreview); + invertColors.addEventListener('change', drawPreview); + showGrid.addEventListener('change', drawPreview); + window.drawPreview = drawPreview; + + }); diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..63b34a8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +androguard==4.1.4 +bleak==3.0.2 +capstone==5.0.9 +pillow==12.3.0 +pytest==9.1.1 diff --git a/tools/check_env.py b/tools/check_env.py new file mode 100644 index 0000000..0bc99d2 --- /dev/null +++ b/tools/check_env.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +"""环境自检:验证分析/CLI 依赖可用,打印版本表。退出码 0=全部通过。""" +import importlib +import shutil +import sys + +DEPS = ["bleak", "PIL", "pytest", "capstone", "androguard"] + + +def main() -> int: + ok = True + for mod in DEPS: + try: + m = importlib.import_module(mod) + ver = getattr(m, "__version__", "?") + print(f"OK {mod:<12} {ver}") + except ImportError as e: + ok = False + print(f"FAIL {mod:<12} {e}") + for tool in ["git"]: + path = shutil.which(tool) + print(f"{'OK ' if path else 'FAIL'} {tool:<12} {path or 'missing'}") + ok = ok and bool(path) + print("ENV:", "PASS" if ok else "FAIL") + return 0 if ok else 1 + + +if __name__ == "__main__": + sys.exit(main())