/
VoRoN
/
HTML
Обзор
Документация
Войти
/
VoRoN
/
HTML
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
Power_BLE.html
580 строк
21 KB
Александр Владимирович
create Power_BLE.html
04 янв 2024, 20:16
04 янв 2024, 20:16
c0e6657
Код
Авторство
О чём код?
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Allpowers BLE</title> <script> var writer = null, history = {},buffer = null,$ = document.querySelector.bind(document); var device = {}, fwFile = null, deviceInfo = { acOpen: null, dcOpen: null, is60Hz: null, ledOpen: null, voiceOpen: null, canUseDay: null, canUseDayHour: null, canUseHour: null, canUseMinute: null, cycleNumber: null, inPower: null, outPower: null, powerAmount: null, sleepTime: null, beepOpen: null, screenOpen: null, usbOpen: null, ecoMode: null, chargingMode: null, acMode: null, carPortEn: null, ecoTime: null, chargeTime: null, chargeTimeHour: null, chargeTimeMinute: null, hardwareVersion: null, softwareVersion: null, }, updateInfo = { 101:"Пакет обновления был успешно загружен", 102:"Не удалось загрузить, недостаточно места", 103:"Ошибка загрузки, переполнение памяти", 104:"Не удалось загрузить, время ожидания запроса на загрузку истекло", 105:"Не удалось загрузить, разряжен аккумулятор", 106:"Не удалось загрузить, плохой сигнал", 107:"Ошибка загрузки, неизвестное исключение", 201:"Успешное обновление", 202:"Обновление не удалось, и батарея разряжена", 203:"Ошибка обновления, переполнение памяти", 204:"Не удалось выполнить обновление, и пакет обновления несовместим с целевой версией текущей задачи", 205:"Не удалось выполнить обновление, не удалось выполнить проверку CRC16", 206:"Ошибка обновления, неизвестное исключение", 207:"Выполните максимальное количество повторных попыток (", 208:"Срок обновления оборудования истек", }; var params = [ {text:"Свет",param:"ledOpen",check:"hasLedSwitch",icon:""}, {text:"Звук",param:"beepOpen",check:"hasBuzzerSwitch",icon:""}, {text:"Выход 220V",param:"acOpen",check:null,icon:""}, {text:"Выход 12V",param:"dcOpen",check:null,icon:""}, {text:"Выход USB",param:"usbOpen",check:"dcSwitchSeparate",icon:""}, ]; var devices = [{ pic: "S400", code: "ALLPOWERS S400", hasWifi: 0, hasBle: 1, name: "AP-SS-005", distributionNet: 0, hasMainSwitch: 0, hasBuzzerSwitch: 0, hasLedSwitch: 1, bleProtocolVersion: 1, dcSwitchSeparate: 1, }, { pic: "S500", code: "ALLPOWERS S500", hasWifi: 0, hasBle: 1, name: "AP-SS-007", distributionNet: 0, hasMainSwitch: 0, hasBuzzerSwitch: 0, hasLedSwitch: 1, bleProtocolVersion: 1, dcSwitchSeparate: 1, }, { pic: "S1000", code: "ALLPOWERS S1000", hasWifi: 0, hasBle: 1, name: "AP-SS-008", distributionNet: 0, hasMainSwitch: 0, hasBuzzerSwitch: 0, hasLedSwitch: 1, bleProtocolVersion: 1, dcSwitchSeparate: 1, }, { pic: "S2000", code: "ALLPOWERS S2000", hasWifi: 0, hasBle: 1, name: "AP-SS-009", distributionNet: 0, hasMainSwitch: 0, hasBuzzerSwitch: 0, hasLedSwitch: 0, bleProtocolVersion: 1, dcSwitchSeparate: 1, }, { pic: "S400", code: "AP S300 V2.0", hasWifi: 0, hasBle: 1, name: "AP-SS-005", distributionNet: 0, hasMainSwitch: 0, hasBuzzerSwitch: 0, hasLedSwitch: 1, bleProtocolVersion: 2, dcSwitchSeparate: 0, }, { pic: "S500", code: "AP S700 V2.0", hasWifi: 0, hasBle: 1, name: "AP-SS-007", distributionNet: 0, hasMainSwitch: 0, hasBuzzerSwitch: 0, hasLedSwitch: 1, bleProtocolVersion: 2, dcSwitchSeparate: 0, }, { pic: "S1000", code: "AP S1500 V2.0", hasWifi: 0, hasBle: 1, name: "AP-SS-008", distributionNet: 0, hasMainSwitch: 0, hasBuzzerSwitch: 0, hasLedSwitch: 1, bleProtocolVersion: 2, dcSwitchSeparate: 0, }, { pic: "S2000", code: "AP S2000 V2.0", hasWifi: 0, hasBle: 1, name: "AP-SS-009", distributionNet: 0, hasMainSwitch: 0, hasBuzzerSwitch: 0, hasLedSwitch: 0, bleProtocolVersion: 2, dcSwitchSeparate: 0, }, { pic: "S2000V2P", code: "AP S2000P V2.0", hasWifi: 0, hasBle: 1, name: "AP-SS-009 PRO", distributionNet: 0, hasMainSwitch: 1, hasBuzzerSwitch: 0, hasLedSwitch: 0, bleProtocolVersion: 2, dcSwitchSeparate: 0, }, { pic: "R3000", code: "AP R600 V2.0", hasWifi: 0, hasBle: 1, name: "AP-SS-005PRO", distributionNet: 0, hasMainSwitch: 0, hasBuzzerSwitch: 0, hasLedSwitch: 1, bleProtocolVersion: 2, dcSwitchSeparate: 1, }, { pic: "R3000", code: "AP R700 V2.0", wifiProtocolVersion: 1, hasWifi: 0, hasBle: 1, name: "AP-SS-007PRO", distributionNet: 0, hasMainSwitch: 0, hasBuzzerSwitch: 0, hasLedSwitch: 1, bleProtocolVersion: 2, dcSwitchSeparate: 1, }, { pic: "S2000", code: "AP S1500MI V2.0", wifiProtocolVersion: 1, hasWifi: 0, hasBle: 1, name: "AP-SS-009MINI", distributionNet: 0, hasMainSwitch: 0, hasBuzzerSwitch: 0, hasLedSwitch: 0, bleProtocolVersion: 2, dcSwitchSeparate: 0, }, { pic: "R3000", code: "AP R3500 V2.0", wifiProtocolVersion: 1, hasWifi: 1, hasBle: 1, name: "AP-SS-010MAX", distributionNet: 1, hasMainSwitch: 1, hasBuzzerSwitch: 0, hasLedSwitch: 0, bleProtocolVersion: 2, dcSwitchSeparate: 1, }, { pic: "R3000", code: "AP R2500 V2.0", wifiProtocolVersion: 1, hasWifi: 1, hasBle: 1, name: "AP-SS-010PRO", distributionNet: 0, hasMainSwitch: 1, hasBuzzerSwitch: 0, hasLedSwitch: 0, bleProtocolVersion: 2, dcSwitchSeparate: 1, }, { pic: "R3000", code: "AP R1500 V2.0", wifiProtocolVersion: 1, hasWifi: 1, hasBle: 1, name: "AP-SS-010", distributionNet: 0, hasMainSwitch: 0, hasBuzzerSwitch: 0, hasLedSwitch: 0, bleProtocolVersion: 2, dcSwitchSeparate: 1, }, { pic: "S4000V2", code: "AP R4000 V2.0", wifiProtocolVersion: 1, hasWifi: 1, hasBle: 1, name: "AP-SS-011", distributionNet: 0, hasMainSwitch: 1, hasBuzzerSwitch: 1, hasLedSwitch: 0, bleProtocolVersion: 2, dcSwitchSeparate: 0, }, { pic: "R3000", code: "AP B3000 V2.0", wifiProtocolVersion: 1, hasWifi: 1, hasBle: 1, name: "AP-EB-003", distributionNet: 1, hasMainSwitch: 1, hasBuzzerSwitch: 0, hasLedSwitch: 0, bleProtocolVersion: 2, dcSwitchSeparate: 1, } ]; function setDevice(d){ $('#connect').innerHTML="Подключен к "+d.name; for(n in devices)if(devices[n].code == d.name)device = devices[n]; device.dev = d; device.dev.ongattserverdisconnected = function(){$('#connect').innerHTML="Соединение";device = {};writer = null;$('#device').style.display = "none";}; var html = ""; for(i in params)if(params[i].check == null || device[params[i].check] == 1)html +='<div class="param">'+params[i].text+'<span class="switch"><input id="'+params[i].param+'" type="checkbox" onchange="send({'+params[i].param+':this.checked})"/><label for="'+params[i].param+'" >Toggle</label></span></div>'; $('#params').innerHTML=html; $('#device').style.display = "block"; } function update(info){ Object.assign(deviceInfo,info); for(k in info){var el = $('#'+k); if(el!=undefined)el[typeof info[k] == "boolean"?"checked":"innerHTML"] = info[k]}; } if(Object.assign == undefined)Object.assign = function() {var o = {};for(var i=0; i < arguments.length; i += 1) {var obj = arguments[i],keys = Object.keys(obj);for(var j=0; j < keys.length; j += 1)o[keys[j]] = obj[keys[j]]}return o}; function hex_string(data){return Array.from(data).map(x=>x.toString(16).padStart(2, '0').toUpperCase()).join("-");} function crc8(data){let crc=0xFF;for(var i=0;i<data.length;i++){crc^=data[i]&0xFF;for(var j=0;j<8;j++)if(crc & 1){crc>>=1;crc^=0xb8}else{crc>>=1}}return crc & 0xFFFF;} function log(msg){$("#data").value=msg+"\n"+$("#data").value;} function showConsole(x){$('#data').style.display = x?"block":"none";} var url = x=>URL.createObjectURL(x); function main(){ if(navigator.bluetooth!=undefined){ navigator.bluetooth.requestDevice({"optionalServices":['0000fff0-0000-1000-8000-00805f9b34fb'],"acceptAllDevices": true}).then(device=>device.gatt.connect()) .then(server=>{setDevice(server.device);return server.getPrimaryService("0000fff0-0000-1000-8000-00805f9b34fb")}) .then(service=>service.getCharacteristic("0000fff2-0000-1000-8000-00805f9b34fb")) .then(characterisristic=>{ writer = characterisristic; log("подключен к "+characterisristic); return characterisristic.service; }) .then(service=>service.getCharacteristic("0000fff1-0000-1000-8000-00805f9b34fb")) .then(characterisristic=>characterisristic.startNotifications()) .then(characterisristic=>{ txChar = characterisristic; characterisristic.oncharacteristicvaluechanged=function(e){ let packet = new Uint8Array(e.target.value.buffer),result=device.bleProtocolVersion == 2 ? decodeNewBle(packet) : decodeOldBle(packet); if(result.command in [1,3])update(result); if(result.command == 5 && result.status == 0 && fwFile != null) fwFile.slice(0,result.singlePackSize).stream().getReader().read().then(x=>{ log("Начало обновления"); writer.writeValueWithoutResponse(x.value); }); if(result.command == 5 && result.status != 0)log(result.status == 2?"Обновление запрещено на выходе устройства":"Разряженная батарея, обновления нет"); if(result.command == 7 && fwFile != null) fwFile.slice(result.hasSendPackCount*result.singlePackSize,(result.hasSendPackCount+1)*result.singlePackSize).stream().getReader().read().then(x=>{ log(result.step<=100?"Загрузка: "+result.step+"%":updateInfo[result.step]); writer.writeValueWithoutResponse(x.value); }); log(">> "+hex_string(packet)+" "+JSON.stringify(result)); }; log("ожидаем данные"); }) .catch(error=>{console.error(error);}); } } function send(info){ Object.assign(deviceInfo,info); if(writer!=null){ var packet = device.bleProtocolVersion == 2?encodeNewBle(deviceInfo):encodeOldBle(deviceInfo); log("<< "+hex_string(packet)+" "+JSON.stringify(info)); writer.writeValueWithoutResponse(packet); } } function update_firmware(){ var input = document.createElement('input'); input.type = 'file'; input.click(); input.onchange = function(e){ fwFile = e.target.files[0]; writer.writeValueWithoutResponse(updateStartCommand({ checkCode:fwFile.name, size: fwFile.size, retryTime: 5000, intervalTime: 10000, })); }; } var valueAtBit = function (e, t) {return e >> t - 1 & 1}, valueAtIndex2Bit = function (e, t) { return e >> t - 1 & 3 }, crc = function(t){var a = 0;for (s = 0; s < t.length - 1; s++)0 == s ? a = t[s] : a ^= t[s];return a;}, checkCode = function(e){ if ("string" === typeof e) return e; for (var t = "",n=e,i=0; i < n.length; i++){ var r = n[i].toString(2),a = r.match(/^1+?(?=0)/); if (a && 8 == r.length){ for (var s = a[0].length, o = n[i].toString(2).slice(7 - s), c = 1; c < s; c++) o += n[c + i].toString(2).slice(2); t += String.fromCharCode(parseInt(o,2)), i += s - 1 } else t += String.fromCharCode(n[i]) } return t }, checkCodeR = function (e) { var t,n,i = new Array; t = e.length; for (var r = 0; r < t; r++) n = e.charCodeAt(r), n >= 65536 && n <= 1114111 ? (i.push(n >> 18 & 7 | 240), i.push(n >> 12 & 63 | 128), i.push(n >> 6 & 63 | 128), i.push(63 & n | 128)) : n >= 2048 && n <= 65535 ? (i.push(n >> 12 & 15 | 224), i.push(n >> 6 & 63 | 128), i.push(63 & n | 128)) : n >= 128 && n <= 2047 ? (i.push(n >> 6 & 31 | 192), i.push(63 & n | 128)) : i.push(255 & n); return i }, decodeOldBle = function(e){ var s = 256 * e[8] + e[9]; return { dcOpen: 1 == valueAtBit(e[0], 1), usbOpen: 1 == valueAtBit(e[0], 2), acOpen: 1 == valueAtBit(e[0], 3), ledOpen: 1 == valueAtBit(e[0], 4), isLowElectric: 1 == valueAtBit(e[0], 5), is60Hz: 1 == valueAtBit(e[0], 6), powerAmount: e[1], inPower: 256 * e[2] + e[3], outPower: 256 * e[4] + e[5], cycleNumber: 256 * e[6] + e[7], canUseDay: Math.floor(s / 60 / 24), canUseDayHour: Math.floor(s / 60 % 24), canUseHour: Math.floor(s / 60), canUseMinute: s % 60 }; },encodeOldBle = function (e) { var t = "00"; t += e.is60Hz ? "1" : "0", t += "0", t += e.ledOpen ? "1" : "0", t += e.acOpen ? "1" : "0", t += e.usbOpen ? "1" : "0", t += e.dcOpen ? "1" : "0"; var d = new Uint8Array([187,parseInt(t, 2)]); return d; },decodeNewBle = function(e){ if (e[0] != 0xa5 || e[1] != 0x65 || e[5]+8 != e.length || e[e.length - 1] != crc(e)) return log("Ошибка в принятом сообщении"),{}; switch(e[6]){ case 1: var s = 256 * e[13] + e[14]; return { command: e[6], dcOpen: 1 == valueAtBit(e[7], 1), acOpen: 1 == valueAtBit(e[7], 2), is60Hz: 1 == valueAtBit(e[7], 3), beepOpen: 1 == valueAtBit(e[7], 4), ledOpen: 1 == valueAtBit(e[7], 5), screenOpen: 1 == valueAtBit(e[7], 6), voiceOpen: 1 == valueAtBit(e[7], 7), powerAmount: e[8], inPower: 256 * e[9] + e[10], outPower: 256 * e[11] + e[12], canUseDay: Math.floor(s / 60 / 24), canUseDayHour: Math.floor(s / 60 % 24), canUseHour: Math.floor(s / 60), canUseMinute: s % 60 }; case 3: return { command: e[6], ecoMode: 1 == valueAtBit(e[7], 1), chargingMode: valueAtIndex2Bit(e[7], 2), acMode: 1 == valueAtBit(e[7], 4), carPortEn: 1 == valueAtBit(e[7], 5), ecoTime: e[8], chargeTime: 256 * e[9] + e[10], chargeTimeHour: Math.floor(256 * e[9] + e[10] / 60), chargeTimeMinute: 256 * e[9] + e[10] % 60, hardwareVersion: e[11].toString(16) / 10, softwareVersion: e[12].toString(16) / 10 }; case 5: return { command: e[6], status: e[7], singlePackSize: 256 * e[8] + e[9], cutPackCount: 256 * e[10] + e[11] }; case 7: return { command: e[6], step: e[7], singlePackSize: 256 * e[8] + e[9], hasSendPackCount: 256 * e[10] + e[11], checkCode: checkCode([e[12], e[13]]) }; default: return { command: e[6], }; } },encodeNewBle = function (e) { var t = e.voiceOpen ? "1" : "0" t += e.screenOpen ? "1" : "0", t += e.ledOpen ? "1" : "0", t += e.beepOpen ? "1" : "0", t += e.is60Hz ? "1" : "0", t += e.closeBle ? "1" : "0", t += e.acOpen ? "1" : "0", t += e.dcOpen ? "1" : "0"; var d = new Uint8Array([0xA5,0x65,0,0xB1,1,1,0,parseInt(t, 2),0]); d[8] = crc(d); return d; },updateStartCommand = function (e) { var i = checkCodeR(e.checkCode); var d = new Uint8Array([ 0xA5,0x65,0,0xB1,1,2,4, Math.floor(e.size / 16777216), Math.floor(e.size / 65536) % 256, Math.floor(e.size / 256) % 256, e.size % 256, e.retryTime, e.intervalTime, i[0],i[1],0 ]); d[15]=crc(d); return d },serviceSendToDeviceNewModelStatus = function(e){ var t = "000"; t += e.carPortEn ? "1" : "0", t += e.acMode ? "1" : "0", 0 == e.chargingMode ? t += "00" : 1 == e.chargingMode ? t += "01" : 2 == e.chargingMode && (t += "10"); t += e.ecoMode ? "1" : "0"; var d = new Uint8Array([0xA5,0x65,0,0xB1,1,2,2,parseInt(d, 2),e.ecoTime,0]); d[9] = crc(d); return d; }; </script> <style> body { margin:0; background:#FFFFFF; font-family: Helvetica, Arial, sans-serif; } #menu {width:auto;display:block;text-align:left;line-height:1.2;} #menu ul {display:block;margin:0;padding:0;position:relative;width:auto;font-size:0;text-align:left;color:#EEE;background-color:#333;border:transparent;list-style:none;z-index:999999990;border-radius: 3px;} #menu li {display:inline-block;position:relative;font-size:0;margin:0;padding:0;} #menu >ul>li>span, #menu >ul>li>a {font-size:12px;color:inherit;text-decoration:none;padding:10px 15px;font-weight:400;text-transform:uppercase;letter-spacing:2px;display:block;position:relative;transition:all 0.3s;} #menu li:hover > span, #menu li:hover > a {color:#333;background-color:#F3F3F3;} #menu .smenu {text-align:left;left:0;color: #333;background-color:#F3F3F3;border:none;position:absolute;box-shadow: 0 4px 8px rgba(0,0,0,0.3);display:none;opacity:0;cursor:default;} #menu .smenu li {display: block;} #menu .smenu li > span, #menu .smenu li > a {font-size:14px;font-weight:400;font-family:inherit;margin:0;padding:8px 20px; display:block;color:inherit;text-decoration:none;} #menu .smenu li:hover > span, #menu .smenu li:hover > a{color:#333;background-color:#DDD;} #menu .smenu li > .smenu.right0 {left:auto;right:100%;} #menu li:hover > .smenu {display:block;opacity:1;z-index:1;} #menu .smenu li > .smenu {left:100%; right:auto;top:0;} #menu ul.smenu {min-width:240px;} #menu div.smenu {text-align:center;} #menu .arrow {color:inherit;border-style:solid; border-width:2px 2px 0 0; padding:4px; transform:rotate(135deg);margin-top:-8.5px;margin-left:4px;position:relative;display: inline-block;width: 0;height: 0;vertical-align:middle;overflow:hidden;} #menu .smenu .arrow {transform:rotate(45deg);top:50%;margin-top:-6px;position:absolute;left:auto;right:20px;} @keyframes topItemAnimation {from {opacity: 0; transform:translate3d(0, -60px, 0);} to {opacity: 1; transform:translate3d(0, 0, 0);}} #menu li a {animation:none;} #menu.active li a { animation: topItemAnimation 0.5s cubic-bezier(.16,.84,.44,1) 0.083s backwards;} #menu.active li:nth-of-type(1) a { animation: topItemAnimation 0.5s cubic-bezier(.16,.84,.44,1) 0.5s backwards;} #menu.active li:nth-of-type(2) a { animation: topItemAnimation 0.5s cubic-bezier(.16,.84,.44,1) 0.41s backwards;} #menu.active li:nth-of-type(3) a { animation: topItemAnimation 0.5s cubic-bezier(.16,.84,.44,1) 0.33s backwards;} #menu.active li:nth-of-type(4) a { animation: topItemAnimation 0.5s cubic-bezier(.16,.84,.44,1) 0.25s backwards;} #menu.active li:nth-of-type(5) a { animation: topItemAnimation 0.5s cubic-bezier(.16,.84,.44,1) 0.16s backwards;}@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { #menu.active li a{animation:none!important;}} .switch input[type=checkbox]{height:0;width:0;visibility:hidden;} .switch label{cursor: pointer;text-indent: -9999px;width: 40px;height:20px;background:grey;display:block;border-radius:20px;position:relative;} .switch label:after {content:'';position:absolute;top:1px;left:1px;width:18px;height:18px;background:#fff;border-radius:18px;transition: 0.3s;} .switch input:checked + label {background: #bada55;} .switch input:checked + label:after {left: calc(100% - 1px);transform: translateX(-100%);} .switch label:active:after {width: 26px;} .param {font-size: 28px;padding: 10px;margin: 20px;border: 1px dotted #555;border-radius: 10px;} .data {font-size: 28px;padding: 10px;} .param .switch {float: right;} #params { float: left; padding: 20px } #data {position:absolute;bottom: 0px;left:0px;width: 100vw;height: 120px;} </style> </head> <body> <div id="menu"> <ul> <li> <span>Устройство <i class="arrow"></i></span> <ul class="smenu"> <li><a href="javascript:main()" id="connect">Соединение</a></li> <li><span>Свет <i class="arrow"></i></span> <ul class="smenu"> <li><a href="javascript:send({ledOpen:true})">Включить</a></li> <li><a href="javascript:send({ledOpen:false})">Отключить</a></li> </ul> </li> <li><span>Выход 220V <i class="arrow"></i></span> <ul class="smenu"> <li><a href="javascript:send({acOpen:true})">Включить</a></li> <li><a href="javascript:send({acOpen:false})">Отключить</a></li> </ul> </li> <li><span>Выход 12V/USB <i class="arrow"></i></span> <ul class="smenu"> <li><a href="javascript:send({dcOpen:true})">Включить</a></li> <li><a href="javascript:send({dcOpen:false})">Отключить</a></li> </ul> </li> <li><span>Консоль отладки <i class="arrow"></i></span> <ul class="smenu"> <li><a href="javascript:showConsole(false)">Скрыть</a></li> <li><a href="javascript:showConsole(true)">Показать</a></li> </ul> </li> <li><a href="javascript:update_firmware()">Обновление прошивки</a></li> </ul> </li> </ul> </div> <div id="device" style="display:none;"> <div class="data"><span id="canUseHour"></span>:<span id="canUseMinute"></span></div> <div class="data">Заряд: <span id="powerAmount"></span>%</div> <div class="data">Питание: <span id="inPower"></span>W</div> <div class="data">Отдача: <span id="outPower"></span>W</div> <div id="params"></div></div> <textarea id="data" style="display: none;" rows=20 cols=50></textarea> </body> </html>