1403 lines
42 KiB
HTML
1403 lines
42 KiB
HTML
<html class="phy6222Class"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<style type="text/css">
|
||
/* basic sytles */
|
||
|
||
body {
|
||
font-family: Arial, 'Open Sans', sans-serif;
|
||
color: #204056;
|
||
}
|
||
|
||
h1 {
|
||
font-size: 28px;
|
||
font-weight: 400;
|
||
text-align: center;
|
||
margin-top: 12px;
|
||
margin-bottom: 18px;
|
||
}
|
||
|
||
hr {
|
||
height: 10px;
|
||
border: 0;
|
||
box-shadow: 0 10px 10px -10px #8c8b8b inset;
|
||
}
|
||
|
||
span#info {
|
||
font-style: italic;
|
||
}
|
||
|
||
.button, [type='button'] {
|
||
background-color: #1a73e8;
|
||
border: none;
|
||
border-radius: 4px;
|
||
color: white;
|
||
padding: 8px 24px;
|
||
text-align: center;
|
||
text-decoration: none;
|
||
display: inline-block;
|
||
font-size: 14px;
|
||
margin: 6px 6px;
|
||
cursor: pointer;
|
||
box-shadow: 0 3px 1px -2px #0003, 0 2px 2px #00000024, 0 1px 5px #0000001f;
|
||
}
|
||
|
||
.button, [type='button'].ok {
|
||
background-color: #4CAF50; /* Green */
|
||
color: white;
|
||
border-color: #4CAF50;
|
||
}
|
||
|
||
.button, [type='button'].danger {
|
||
background-color: #f44336; /* Red */
|
||
color: white;
|
||
border-color: #f44336;
|
||
}
|
||
|
||
.button, [type='button']:disabled {
|
||
cursor: not-allowed;
|
||
opacity: 0.6;
|
||
}
|
||
|
||
input[type="checkbox"] + label {
|
||
margin-right: 8px;
|
||
}
|
||
|
||
div#div_v {
|
||
height:400px;
|
||
margin-top: 16px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
div#labdiv {
|
||
margin-top: 16px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
div#log {
|
||
padding: 12px;
|
||
font-style: italic;
|
||
font-size: 16px;
|
||
}
|
||
|
||
div#MAC {
|
||
font-style: smaller;
|
||
margin: 8px;
|
||
}
|
||
|
||
div#txtStatus {
|
||
font-style: italic;
|
||
font-size: 16px;
|
||
text-align: center;
|
||
background-color: #eef6fc;
|
||
padding-top: 5px;
|
||
padding-bottom: 5px;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
div#tempHumiData{
|
||
text-align: center;
|
||
background-color: #eef6fc;
|
||
padding-top: 5px;
|
||
padding-bottom: 5px;
|
||
}
|
||
|
||
input {
|
||
padding: 4px;
|
||
margin: 4px;
|
||
}
|
||
|
||
select {
|
||
padding: 4px;
|
||
}
|
||
|
||
/* menu */
|
||
|
||
.navbar {
|
||
width: 95%;
|
||
/* box-shadow: 0 1px 4px rgb(146 161 176 / 15%);*/
|
||
position: absolute;
|
||
top: 0;
|
||
}
|
||
|
||
.nav-container {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
height: 62px;
|
||
}
|
||
|
||
.navbar .menu-items {
|
||
display: flex;
|
||
}
|
||
|
||
.navbar .nav-container li {
|
||
list-style: none;
|
||
}
|
||
|
||
.navbar .nav-container a {
|
||
font-size: 1.0rem;
|
||
font-weight: 400;
|
||
}
|
||
|
||
.navbar .nav-container a:hover{
|
||
font-weight: bolder;
|
||
}
|
||
|
||
.nav-container {
|
||
display: block;
|
||
position: relative;
|
||
height: 60px;
|
||
}
|
||
|
||
.nav-container .checkbox {
|
||
position: absolute;
|
||
display: block;
|
||
height: 32px;
|
||
width: 32px;
|
||
top: 20px;
|
||
left: 20px;
|
||
z-index: 5;
|
||
opacity: 0;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.nav-container .hamburger-lines {
|
||
display: block;
|
||
height: 26px;
|
||
width: 32px;
|
||
position: absolute;
|
||
top: 17px;
|
||
left: 20px;
|
||
z-index: 2;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.nav-container .hamburger-lines .line {
|
||
display: block;
|
||
height: 4px;
|
||
width: 100%;
|
||
border-radius: 10px;
|
||
background: #0e2431;
|
||
}
|
||
|
||
.nav-container .hamburger-lines .line1 {
|
||
transform-origin: 0% 0%;
|
||
transition: transform 0.4s ease-in-out;
|
||
}
|
||
|
||
.nav-container .hamburger-lines .line2 {
|
||
transition: transform 0.2s ease-in-out;
|
||
}
|
||
|
||
.nav-container .hamburger-lines .line3 {
|
||
transform-origin: 0% 100%;
|
||
transition: transform 0.4s ease-in-out;
|
||
}
|
||
|
||
.navbar .menu-items {
|
||
position: relative;
|
||
padding-top: 55px;
|
||
box-shadow: 5px 3px 13px 0px rgb(204 204 204 / 80%);
|
||
min-height: 100vh;
|
||
width: 60%;
|
||
transform: translate(-150%);
|
||
display: flex;
|
||
flex-direction: column;
|
||
transition: transform 0.5s ease-in-out;
|
||
text-align: center;
|
||
z-index: 1;
|
||
background: white;
|
||
}
|
||
|
||
.navbar .menu-items li {
|
||
margin-bottom: 12px;
|
||
font-size: 1.2rem;
|
||
font-weight: 800;
|
||
}
|
||
|
||
|
||
.nav-container input[type="checkbox"]:checked ~ .menu-items {
|
||
transform: translateX(0);
|
||
}
|
||
|
||
.nav-container input[type="checkbox"]:checked ~ .hamburger-lines .line1 {
|
||
transform: rotate(45deg);
|
||
}
|
||
|
||
.nav-container input[type="checkbox"]:checked ~ .hamburger-lines .line2 {
|
||
transform: scaleY(0);
|
||
}
|
||
|
||
.nav-container input[type="checkbox"]:checked ~ .hamburger-lines .line3 {
|
||
transform: rotate(-45deg);
|
||
}
|
||
|
||
.nav-container input[type="checkbox"]:checked ~ .logo{
|
||
display: none;
|
||
}
|
||
|
||
|
||
.shadowbox {
|
||
width: 15em;
|
||
border: 1px solid #333;
|
||
box-shadow: 8px 8px 5px #444;
|
||
padding: 8px 12px;
|
||
background-image: linear-gradient(180deg, #fff, #ddd 40%, #ccc);
|
||
}
|
||
|
||
.shadowprogress {
|
||
width: 15em;
|
||
border: 1px solid #333;
|
||
/* box-shadow: 8px 8px 5px #444; */
|
||
padding: 8px 12px;
|
||
/* background-image: linear-gradient(180deg, #fff, #ddd 40%, #ccc); */
|
||
}
|
||
|
||
.shadowerror {
|
||
color: red;
|
||
width: 15em;
|
||
border: 1px solid #333;
|
||
box-shadow: 8px 8px 5px #444;
|
||
padding: 8px 12px;
|
||
background-image: linear-gradient(180deg, #fff, #ddd 40%, #ccc);
|
||
}
|
||
|
||
|
||
/* Style the tab */
|
||
.tab {
|
||
overflow: hidden;
|
||
border: 1px solid #ccc;
|
||
background-color: #f1f1f1;
|
||
}
|
||
|
||
/* Style the buttons inside the tab */
|
||
.tab button {
|
||
background-color: inherit;
|
||
float: left;
|
||
border: none;
|
||
outline: none;
|
||
cursor: pointer;
|
||
padding: 14px 16px;
|
||
transition: 0.3s;
|
||
font-size: 17px;
|
||
}
|
||
|
||
/* Change background color of buttons on hover */
|
||
.tab button:hover {
|
||
background-color: #ddd;
|
||
}
|
||
|
||
/* Create an active/current tablink class */
|
||
.tab button.active {
|
||
background-color: #ccc;
|
||
}
|
||
|
||
/* Style the tab content */
|
||
.tabcontent {
|
||
display: none;
|
||
padding: 6px 12px;
|
||
border: 1px solid #ccc;
|
||
border-top: none;
|
||
}
|
||
</style>
|
||
<title>PHY62x2-BTHome v0.2</title>
|
||
</head>
|
||
<body>
|
||
<script>
|
||
//BLE values
|
||
const FLASH_SIZE = 0x80000;
|
||
var bluetoothDevice, gattServer, otaCharacteristic, cmdCharacteristic, infoService;
|
||
|
||
var devinfo = {};
|
||
var devsrs = {};
|
||
var devcfg = {};
|
||
var devcfs = {};
|
||
|
||
var startTime = 0,
|
||
isConnected = false;
|
||
|
||
var ota = {
|
||
fwArray: null,
|
||
fwname: "",
|
||
fwsize: 0,
|
||
fwmaxsize: 196608,
|
||
ext_flg: false,
|
||
blockCount: 0,
|
||
program_offset: 0x11010000
|
||
};
|
||
|
||
//Connection values
|
||
var connectRetries = 0;
|
||
|
||
var $ = function(id) { return document.getElementById(id);}
|
||
|
||
function resetVariables() {
|
||
gattServer = null;
|
||
mainService = null;
|
||
otaCharacteristic = null;
|
||
cmdCharacteristic = null;
|
||
$('btnDisconnect').disabled = true;
|
||
$('btnReadAddr').disabled = true;
|
||
$('btnStartDFU').disabled = true;
|
||
$('btnSendData').disabled = true;
|
||
$('btnSendCommand').disabled = true;
|
||
}
|
||
|
||
function handleError(text) {
|
||
showError(text);
|
||
resetVariables();
|
||
if (connectRetries < 5) {
|
||
connectRetries++;
|
||
addLog("Переподключение " + connectRetries + " из " + 5);
|
||
doConnect();
|
||
} else {
|
||
addLog("Подключиться не удалось!");
|
||
connectRetries = 0;
|
||
// disconnect();
|
||
$('btnConnect').disabled = false;
|
||
}
|
||
}
|
||
|
||
function connect() {
|
||
var deviceOptions = {
|
||
optionalServices: [0x1800, 0x180a, 0x180f, 0x181a, 0xfcd2],
|
||
services: [0x1800, 0x180a, 0x180f, 0x181a, 0xfcd2],
|
||
acceptAllDevices: true };
|
||
const namePrefix = $('inpNamePrefix').value;
|
||
if (namePrefix) {
|
||
deviceOptions.acceptAllDevices = false;
|
||
deviceOptions.filters = namePrefix.split(",")
|
||
.map((x) => ({ namePrefix: x }));
|
||
} else {
|
||
deviceOptions.acceptAllDevices = false;
|
||
deviceOptions.filters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz_#@!*0123456789';,.<>{}[]"
|
||
.split("")
|
||
.map((x) => ({ namePrefix: x }));
|
||
}
|
||
if (bluetoothDevice != null) bluetoothDevice.gatt.disconnect();
|
||
resetVariables();
|
||
$('btnReconnect').disabled = true;
|
||
|
||
if(typeof(navigator.bluetooth) != "undefined") {
|
||
$('btnConnect').disabled = true;
|
||
showState("Поиск устройств");
|
||
connectRetries = 0;
|
||
//console.log()
|
||
navigator.bluetooth.requestDevice(deviceOptions).then(device => {
|
||
bluetoothDevice = device;
|
||
bluetoothDevice.addEventListener('gattserverdisconnected', onDisconnected);
|
||
//addLog("Connecting to: " + bluetoothDevice.name);
|
||
doConnect();
|
||
}).catch(handleError);
|
||
} else {
|
||
showError("Browser doesn't support BLE API");
|
||
}
|
||
}
|
||
|
||
function disconnect() {
|
||
addLog("Отключение");
|
||
isConnected = false;
|
||
if (bluetoothDevice != null)
|
||
bluetoothDevice.gatt.disconnect();
|
||
}
|
||
|
||
function reconnect() {
|
||
addLog("Переподключение");
|
||
if (bluetoothDevice != null) {
|
||
bluetoothDevice.gatt.disconnect();
|
||
isConnected = false;
|
||
}
|
||
connectRetries = 0;
|
||
doConnect();
|
||
}
|
||
|
||
|
||
function getStrCharacteristic(infosrv, suuid) {
|
||
return new Promise((resolve, reject) => {
|
||
infosrv.getCharacteristic(suuid).then(characteristic => {
|
||
characteristic.readValue().then(value => {
|
||
return resolve(new TextDecoder("utf-8").decode(value));
|
||
}).catch(error => {console.log(error); return resolve(null); });
|
||
}).catch(error => {console.log(error); return resolve(null); });
|
||
})};
|
||
|
||
function getDevInfo(devInfEnabled) {
|
||
return new Promise((resolve, reject) => {
|
||
devinfo.nrstr = null;
|
||
devinfo.srstr = null;
|
||
devinfo.frstr = null;
|
||
devinfo.hrstr = null;
|
||
devinfo.vrstr = null;
|
||
if (devInfEnabled == true) {
|
||
gattServer.getPrimaryService(0x180a).then(service => {
|
||
console.log("Найден Device Information Service");
|
||
infoService = service;
|
||
return getStrCharacteristic(infoService, 0x2a24).then(value => {
|
||
devinfo.nrstr = value;
|
||
return getStrCharacteristic(infoService, 0x2a25).then(value => {
|
||
devinfo.srstr = value;
|
||
return getStrCharacteristic(infoService, 0x2a26).then(value => {
|
||
devinfo.frstr = value;
|
||
return getStrCharacteristic(infoService, 0x2a27).then(value => {
|
||
devinfo.hrstr = value;
|
||
return getStrCharacteristic(infoService, 0x2a28).then(value => {
|
||
devinfo.vrstr = value;
|
||
return resolve(devinfo.flg);
|
||
}).catch(error => { console.log(error); return resolve(null);});
|
||
}).catch(error => { console.log(error); return resolve(null);});
|
||
}).catch(error => { console.log(error); return resolve(null);});
|
||
}).catch(error => { console.log(error); return resolve(null);});
|
||
}).catch(error => { console.log(error); return resolve(null);});
|
||
}).catch(error => { console.log(error); return resolve(null);});
|
||
} else return resolve(null);
|
||
}).catch(error => { console.log(error); return resolve(null);});
|
||
}
|
||
|
||
function linkOta() {
|
||
return new Promise((resolve, reject) => {
|
||
mainService.getCharacteristic(0xfff3).catch(error => { console.log(error); return resolve(null);})
|
||
.then(characteristic => {
|
||
console.log("Найдена OTA Characteristic");
|
||
otaCharacteristic = characteristic;
|
||
return otaCharacteristic.addEventListener('characteristicvaluechanged', event => OtaBlkParse(event.target.value));
|
||
}).then(_ => {
|
||
return otaCharacteristic.readValue();
|
||
}).then(value => {
|
||
if(value.byteLength > 1)
|
||
addLog("OTA ver: "+ hex(value.getUint8(1),2));
|
||
return resolve(null);});
|
||
}).catch(error => { console.log(error); return resolve(null);});
|
||
}
|
||
|
||
function phyConnect(info_flg) {
|
||
return getDevInfo(info_flg).then(_ => {
|
||
if(devinfo.nrstr != null)
|
||
addLog("Model: "+devinfo.nrstr);
|
||
if(devinfo.srstr != null)
|
||
addLog("Serial: "+devinfo.srstr);
|
||
if(devinfo.frstr != null)
|
||
addLog("Firmware: "+devinfo.frstr);
|
||
if(devinfo.hrstr != null)
|
||
addLog("Hardware: "+devinfo.hrstr);
|
||
if(devinfo.vrstr != null)
|
||
addLog("Software: "+devinfo.vrstr);
|
||
return gattServer.getPrimaryService(0xfcd2);
|
||
}).then(service => {
|
||
console.log("Найден Main Service");
|
||
mainService = service;
|
||
return mainService.getCharacteristic(0xfff4);
|
||
}).then(characteristic => {
|
||
console.log("Найдена CMD Characteristic");
|
||
cmdCharacteristic = characteristic;
|
||
return cmdCharacteristic.addEventListener('characteristicvaluechanged', event => customBlkParse(event.target.value));
|
||
}).then(_ => {
|
||
return cmdCharacteristic.startNotifications();
|
||
}).then(_ => {
|
||
return cmdCharacteristic.readValue();
|
||
}).then(value => {
|
||
if(value.byteLength >= 10)
|
||
if(value.getUint8(0) != 0)
|
||
addLog("Странный ответ устройства!");
|
||
otaCharacteristic = null;
|
||
return linkOta();
|
||
}).then(_ => {
|
||
showState("Устройство подключено.");
|
||
isConnected = true;
|
||
// $('btnConnect').disabled = true;
|
||
$('btnDisconnect').disabled = false;
|
||
$('btnReconnect').disabled = false;
|
||
$('btnSendCommand').disabled = false;
|
||
$('btnReadAddr').disabled = false;
|
||
$('btnSendData').disabled = false;
|
||
if (otaCharacteristic != null && ota.fwArray != null)
|
||
$('btnStartDFU').disabled = false;
|
||
selectConfigTab();
|
||
$('tabConfig').style.display = "block";
|
||
}).catch(handleError);
|
||
}
|
||
|
||
function doConnect() {
|
||
isConnected = false;
|
||
showState("Ожидание соединения с " + bluetoothDevice.name)
|
||
return bluetoothDevice.gatt.connect().then(server => {
|
||
console.log("Найден GATT сервер");
|
||
gattServer = server;
|
||
gattServer.getPrimaryServices().then(services => {
|
||
let phy = false;
|
||
let info = false;
|
||
for (var i = 0; i < services.length; i++) {
|
||
console.log("Services: " + services[i].uuid);
|
||
if (services[i].uuid == "0000180a-0000-1000-8000-00805f9b34fb")
|
||
info = true;
|
||
else if (services[i].uuid == "0000fcd2-0000-1000-8000-00805f9b34fb")
|
||
phy = true;
|
||
}
|
||
if(phy)
|
||
return phyConnect(true);
|
||
addLog("Выбрано неверное устройтво!");
|
||
bluetoothDevice.gatt.disconnect();
|
||
connectRetries = 10;
|
||
return null;
|
||
}).catch(handleError);
|
||
}).catch(handleError);
|
||
}
|
||
|
||
function onDisconnected() {
|
||
isConnected = false;
|
||
resetVariables();
|
||
showState('Устройство отключено.');
|
||
$('btnConnect').disabled = false;
|
||
$('tabConfig').style.display = "none";
|
||
}
|
||
|
||
function startDFU() {
|
||
addLog("Старт программирования...");
|
||
updateBegin();
|
||
}
|
||
|
||
function addLog(logTXT) {
|
||
console.log(logTXT)
|
||
var time = new Date().toLocaleTimeString();
|
||
var logString = time + ": " + logTXT;
|
||
$("log").innerHTML += logString + "<br>";
|
||
}
|
||
|
||
function showState(text) {
|
||
let s = "Состояние: " + text;
|
||
$("lblStatus").className = "shadowbox";
|
||
$("lblStatus").innerHTML = s;
|
||
addLog(text);
|
||
}
|
||
|
||
function showProgress(text) {
|
||
$("lblStatus").className = "shadowprogress";
|
||
$("lblStatus").innerHTML = text;
|
||
}
|
||
|
||
function clearLog() {
|
||
$("log").innerHTML = "";
|
||
}
|
||
|
||
function showError(text) {
|
||
// console.log("Status: " + status);
|
||
let s = "Ошибка: " + text;
|
||
$("lblStatus").className = "shadowerror";
|
||
$("lblStatus").innerHTML = s;
|
||
addLog(text);
|
||
}
|
||
|
||
function updateFail(err) {
|
||
let s = "OTA: " + err;
|
||
showError(s);
|
||
}
|
||
|
||
function decimalToHex(d, padding) {
|
||
var hex = Number(d).toString(16);
|
||
while (hex.length < 4) {
|
||
hex = "0" + hex;
|
||
}
|
||
return hex;
|
||
}
|
||
|
||
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 makeRandomID(length) {
|
||
var result = '';
|
||
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||
var charactersLength = characters.length;
|
||
for (var i = 0; i < length; i++) {
|
||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||
}
|
||
return bytesToHex(new TextEncoder("utf-8").encode(result));
|
||
}
|
||
|
||
function hex(number, len) {
|
||
var str = (number.toString(16)).toUpperCase();
|
||
while (str.length < len) str = '0' + str;
|
||
return str;
|
||
}
|
||
|
||
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 dump(ar, len) {
|
||
let s = '';
|
||
for(let i=0; i < len; i++) {
|
||
s += hex(ar[i],2);
|
||
}
|
||
return s;
|
||
}
|
||
|
||
function dump8(ar, len) {
|
||
let s = '';
|
||
for(let i=0; i < len; i++) {
|
||
s += hex(ar.getUint8(i),2);
|
||
}
|
||
return s;
|
||
}
|
||
|
||
function hex2ascii(hexx) {
|
||
var hex = hexx.toString();
|
||
var str = '';
|
||
for (var i = 0;
|
||
(i < hex.length && hex.substr(i, 2) !== '00'); i += 2)
|
||
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
|
||
return str;
|
||
}
|
||
|
||
function crc16_modbus(buffer) {
|
||
var crc = 0xFFFF;
|
||
var odd;
|
||
for (var i = 0; i < buffer.length; i++) {
|
||
crc = crc ^ buffer[i];
|
||
for (var j = 0; j < 8; j++) {
|
||
odd = crc & 0x0001;
|
||
crc = crc >> 1;
|
||
if (odd) crc = crc ^ 0xA001;
|
||
}
|
||
}
|
||
return crc;
|
||
};
|
||
|
||
function getHexCRC(data) {
|
||
var tempCRC = decimalToHex(crc16_modbus(hexToBytes(data)));
|
||
return tempCRC.substring(2, 4) + tempCRC.substring(0, 2);
|
||
}
|
||
|
||
var crc32 = (function() {
|
||
let table = new Uint32Array(256);
|
||
for(var i=256; i--;) {
|
||
let tmp = i;
|
||
for(let k=8; k--;) {
|
||
tmp = tmp & 1 ? 0xEDB88320 ^ tmp >>> 1 : tmp >>> 1;
|
||
}
|
||
table[i] = tmp;
|
||
}
|
||
return function( data ) {
|
||
let crc = -1;
|
||
let l = data.length;
|
||
for(let i=0; i<l; i++) {
|
||
crc = crc >>> 8 ^ table[ crc & 255 ^ data[i] ];
|
||
}
|
||
return (crc >>> 0);
|
||
};
|
||
})();
|
||
|
||
function testOTAFirmware(data) {
|
||
let fsize = data.byteLength;
|
||
console.log("File size = 0x"+ fsize.toString(16));
|
||
if (fsize < 20)
|
||
return "Неправильный размер двоичной прошивки PHY6 OTA!";
|
||
if(fsize > ota.fwmaxsize)
|
||
return "Размер прошивки более " + (fwmaxsize/1024).toFixed(1) + " кбайт!";
|
||
if(fsize != (fsize & 0x1ffff0) + 4)
|
||
return "Неверный формат размера файла для прошивки PHY6 OTA!";
|
||
ota.fwsize = fsize;
|
||
let head = new DataView(data, 0, 16);
|
||
ota.h = {};
|
||
ota.h.id = head.getUint32(0, true);
|
||
ota.h.segs = head.getUint32(4, true);
|
||
ota.h.start = head.getUint32(8, true);
|
||
ota.h.size = head.getUint32(12, true);
|
||
if(ota.h.id != 0x36594850) { // "PHY6"
|
||
ota.ext_flg = true;
|
||
ota.h.size = fsize - 4;
|
||
ota.h.start = ota.program_offset;
|
||
addLog("Файл id:"+hex(ota.h.id, 4)+" , специальная прошивка.");
|
||
return "Расширенный формат OTA пока не поддерживается!";
|
||
} else {
|
||
ota.ext_flg = false;
|
||
addLog("Файл id:PHY6, Сегментов: " + ota.h.segs + ", Старт: 0x"+hex(ota.h.start, 8)+ ", Размер: " + ota.h.size + " байт");
|
||
if(ota.h.segs > 16)
|
||
return "Неверное количество сегментов в заголовке PHY6 OTA!";
|
||
if(ota.h.size + 4 != ota.fwsize)
|
||
return "Неверный размер в заголовке PHY6 OTA!";
|
||
}
|
||
let crc = crc32(new Uint8Array(data.slice(0, ota.h.size)));
|
||
let x = new DataView(data, ota.h.size, 4);
|
||
ota.fcrc = x.getUint32(0, true);
|
||
console.log("Файл CRC = 0x" + ota.fcrc.toString(16) + ", Расчет CRC = 0x" + crc.toString(16));
|
||
if(ota.fcrc != crc) {
|
||
return "Неправильный CRC в файле OTA!";
|
||
}
|
||
return "ok";
|
||
}
|
||
|
||
function getFwArray(data, filename) {
|
||
addLog("Файл: " + filename);
|
||
let s = testOTAFirmware(data);
|
||
if(s != "ok") {
|
||
$('btnStartDFU').disabled = true;
|
||
addLog(s);
|
||
ota.blockCount = 0;
|
||
ota.fwArray = null;
|
||
fwname = "";
|
||
alert(s);
|
||
return;
|
||
}
|
||
ota.fwArray = bytesToHex(data);
|
||
addLog("Размер файла: " + (ota.fwArray.length/2).toString(10) + " байт");
|
||
if (ota.fwArray.length % 32 !== 0) { // pad last block to 16bytes
|
||
var padHex = "ffffffffffffffffffffffffffffffff";
|
||
ota.fwArray += padHex.substr(0, 32 - ota.fwArray.length % 32);
|
||
}
|
||
ota.blockCount = ota.fwArray.length / 32;
|
||
addLog("Счетчик: " + ota.blockCount + " блоков");
|
||
ota.fwname = filename;
|
||
if(isConnected)
|
||
$('btnStartDFU').disabled = false;
|
||
}
|
||
|
||
var ota_errors = [
|
||
'ok',
|
||
'Неверная команда',
|
||
'Не задан старт',
|
||
'Не заданы параметры',
|
||
'Неверные параметры',
|
||
'Неправильный размер пакета',
|
||
'Ошибка CRC16 пакета',
|
||
'Потеря пакетов',
|
||
'Ошибка записи в Flash',
|
||
'Ошибка в номере пакета',
|
||
'Ошибка идентификатора в файле программы',
|
||
'Ошибка CRC32 переданной программы'];
|
||
|
||
function get_msg_ota_err(err) {
|
||
if(err == 0)
|
||
return "ok";
|
||
if(err == 255)
|
||
return "OTA end";
|
||
if(err <= 11)
|
||
return ota_errors[err];
|
||
return "Неизвестная ошибка";
|
||
}
|
||
|
||
function OtaBlkParse(value) {
|
||
if(value.byteLength < 20) return;
|
||
ota.ind = {};
|
||
ota.ind.err_flag = value.getUint8(0);
|
||
ota.ind.version = value.getUint8(1);
|
||
ota.ind.start_flag = value.getUint8(2);
|
||
ota.ind.debug_flag = value.getUint8(3);
|
||
ota.ind.prg_offset = value.getUint32(4,true);
|
||
ota.ind.pkt_index = value.getUint16(8,true);
|
||
ota.ind.pkt_total = value.getUint16(10,true);
|
||
ota.ind.fw_value = value.getUint32(12,true);
|
||
ota.ind.crc32 = value.getUint32(16,true);
|
||
//console.log('otablk: '+dump8(value, value.byteLength));
|
||
console.log('OTA read: ver: '+hex(ota.ind.version,2)+', err: '+ota.ind.err_flag+' - '+get_msg_ota_err(ota.ind.err_flag)+', dbg: '+ota.ind.debug_flag+', start: '+ota.ind.start_flag+', offs: 0x'+hex(ota.ind.prg_offset,8)+', idx: 0x'+hex(ota.ind.pkt_index,4)+', total: 0x'+hex(ota.ind.pkt_total,4)+', crc: 0x'+hex(ota.ind.crc32,8));
|
||
}
|
||
|
||
function updateBegin() {
|
||
if (ota.blockCount <= 0) {
|
||
showError("Не выбран файл!");
|
||
return;
|
||
}
|
||
setTimeout(function() {
|
||
otaCharSend("00ff").then(_ => { otaCharacteristic.readValue().then(value => {
|
||
let cmd = "01ff";
|
||
// TODO
|
||
// if(ota.ext_flg) {
|
||
// cmd += ota.fwArray.substring(16,24); // program_offset
|
||
// cmd += ota.fwArray.substring(0,8); // fw_id
|
||
// cmd += hex((ota.blockCount >> 8) | ((ota.blockCount & 0xff) << 8),4); // pkt_total
|
||
// }
|
||
otaCharSend(cmd).then(_ => { otaCharacteristic.readValue().then(value => {
|
||
if(value.byteLength >= 2 && value.getUint8(0) == 0) {
|
||
setTimeout(function() {
|
||
startTime = new Date().getTime();
|
||
sendOTAblock(0);
|
||
}, 50);
|
||
} else
|
||
showError("Ошибка N"+value.getUint8(0)+" OTA!");
|
||
})//.catch(function(err) {updateFail(err); });
|
||
})//.catch(function(err) {updateFail(err); });
|
||
})//.catch(function(err) {updateFail(err); });
|
||
}).catch(function(err) {updateFail(err); });
|
||
}, 50);
|
||
}
|
||
|
||
function sendLastOTA() {
|
||
otaCharacteristic.readValue().then(value => {
|
||
if(value.byteLength >= 1 && value.getUint8(0) == 0xff) {
|
||
let s = "Программирование завершено за " + (new Date().getTime() - startTime) / 1000 + " секунды";
|
||
showProgress(s);
|
||
addLog(s);
|
||
/* Сброс - отключен для теста
|
||
var data = "02ff";
|
||
return otaCharSend(data).then(_ => {
|
||
addLog("Программирование завершено за " + (new Date().getTime() - startTime) / 1000 + " секунды");
|
||
}).catch(function(err) {
|
||
updateFail(err);
|
||
});
|
||
*/
|
||
} else
|
||
showError("Ошибка ("+value.getUint8(0)+") на передаче блока "+blockNr+" OTA: "+s);
|
||
}).catch(function(err) { updateFail(err); });
|
||
}
|
||
|
||
function sendOTAblock(blockNr) {
|
||
if (blockNr >= ota.blockCount) {
|
||
return sendLastOTA();
|
||
}
|
||
showProgress("Передан блок " + blockNr + " из " + ota.blockCount + ", " + Math.floor(blockNr / (ota.blockCount * 1.0) * 100) + "% успеха, время от старта " + (new Date().getTime() - startTime) / 1000.0 + " сек");
|
||
var blockNrString = getHexBLockCount(blockNr);
|
||
var blockString = blockNrString + ota.fwArray.substring(blockNr * 32, blockNr * 32 + 32);
|
||
var blockCRC = getHexCRC(blockString);
|
||
otaCharSend(blockString + blockCRC).then(_ => {
|
||
if (blockNr >= ota.blockCount - 1) {
|
||
return sendLastOTA();
|
||
}
|
||
setTimeout(function() {
|
||
if ((blockNr + 1) % 16 == 0) {
|
||
otaCharacteristic.readValue().then(value => {
|
||
if(value.byteLength >= 20 && value.getUint8(0) == 0)
|
||
sendOTAblock(blockNr + 1);
|
||
else {
|
||
let s = get_msg_ota_err(value.getUint8(0));
|
||
if(s != "ok")
|
||
showError("Ошибка ("+value.getUint8(0)+") на передаче блока "+blockNr+" OTA: "+s);
|
||
}
|
||
}).catch(function(err) { updateFail(err); });
|
||
} else
|
||
sendOTAblock(blockNr + 1);
|
||
}, 0);
|
||
}).catch(function(err) {
|
||
updateFail(err);
|
||
});
|
||
}
|
||
|
||
function getHexBLockCount(count) {
|
||
var tempHEX = decimalToHex(count);
|
||
return tempHEX.substring(2, 4) + tempHEX.substring(0, 2);
|
||
}
|
||
|
||
|
||
var otaCharSend = function(data) {
|
||
return new Promise(function(resolve, reject) {
|
||
//console.log("OTA send: " + data);
|
||
otaCharacteristic.writeValue(hexToBytes(data)).then(function(character) {
|
||
resolve("ok");
|
||
}).catch(function(err) {
|
||
reject("Ошибка при отправке данных");
|
||
});
|
||
});
|
||
}
|
||
|
||
var mainCharSend = function(data, characteristic) {
|
||
return new Promise(function(resolve, reject) {
|
||
console.log("Send: " + data);
|
||
characteristic.writeValue(hexToBytes(data)).then(function(character) {
|
||
resolve("ok");
|
||
}).catch(function(err) {
|
||
reject("Ошибка при отправке данных");
|
||
});
|
||
});
|
||
}
|
||
|
||
function readAddr(addr) {
|
||
if (cmdCharacteristic) {
|
||
let blk = new Uint8Array([0xdb, addr&0xff, (addr>>8)&0xff, (addr>>16)&0xff, (addr>>24)&0xff]);
|
||
cmdCharacteristic.writeValue(blk).then(_ => {
|
||
startTime = new Date().getTime();
|
||
showState("Прочитано 16 байт из адреса 0x" + hex(addr,8));
|
||
});
|
||
}
|
||
}
|
||
|
||
function writeAddr(addr, data) {
|
||
if (cmdCharacteristic) {
|
||
len = data.length;
|
||
if(len != 0 && len <= 16) {
|
||
let blk = new Uint8Array(len + 5);
|
||
blk.set([0xdb, addr&0xff, (addr>>8)&0xff, (addr>>16)&0xff, (addr>>24)&0xff]);
|
||
blk.set(data, 5);
|
||
console.log(blk);
|
||
cmdCharacteristic.writeValue(blk);
|
||
showState("Записано " + len + " байт по адресу 0x" + hex(addr,8));
|
||
} else {
|
||
console.log(data);
|
||
showState('Ошибка, длина блока от 1 до 16 байт!');
|
||
}
|
||
}
|
||
}
|
||
|
||
function infoReadAddr() {
|
||
if(cmdCharacteristic) {
|
||
let faddr = parseInt($('inputAddr').value, 16);
|
||
readAddr(faddr);
|
||
}
|
||
}
|
||
|
||
function writeCmd(data) {
|
||
if(cmdCharacteristic) {
|
||
len = data.length;
|
||
if(len != 0 && len <= 20) {
|
||
let blk = new Uint8Array(data);
|
||
cmdCharacteristic.writeValue(blk);
|
||
} else
|
||
showState('Ошибка, длина команды от 1 до 20 байт!');
|
||
}
|
||
}
|
||
|
||
function sendData() {
|
||
let addr = parseInt($('inputAddr').value, 16);
|
||
let data = hexToBytes($('inputData').value);
|
||
if(data.length != 0 && data.length <= 16)
|
||
writeAddr(addr, data);
|
||
else
|
||
console.log('Ошибка, длина от 1 до 16 байт!');
|
||
|
||
}
|
||
|
||
function sendCommand() {
|
||
let data = hexToBytes($('inpCmdData').value);
|
||
if(data.length != 0 && data.length <= 20)
|
||
writeCmd(data);
|
||
else
|
||
console.log('Должно быть от 1 до 20 hex байт!');
|
||
}
|
||
|
||
function customBlkParse(value) {
|
||
let len = value.byteLength;
|
||
if(len == 0) return;
|
||
let blkId = value.getUint8(0);
|
||
if((blkId == 0) && (len > 11)) {
|
||
// CMD_ID_DEVID Get dev id, version, services
|
||
devsrs.revision = value.getUint8(1);
|
||
devsrs.hw_version = value.getUint16(2, true);
|
||
devsrs.sw_version = value.getUint16(4, true);
|
||
devsrs.dev_spec_data = value.getUint16(6, true);
|
||
devsrs.services = value.getUint32(8, true);
|
||
addLog("Dev info # hw: "+hex(devsrs.hw_version,4)+", sw: "+hex(devsrs.sw_version,4)+", services: "+hex(devsrs.services,8)+", sd: "+hex(devsrs.dev_spec_data, 4));
|
||
} else if(blkId == 0x35){
|
||
if(len >= 12) {
|
||
let cnt = value.getUint16(1, true);
|
||
let tc = value.getUint32(3, true);
|
||
let tm = value.getInt16(7, true) / 100.0;
|
||
let hm = value.getUint16(9, true) / 100.0;
|
||
let vb = value.getUint16(11, true);
|
||
let dt = new Date(tc*1000);
|
||
console.log(((dt.toISOString().slice(0, -1)).replace('T',' ')).replace('.000','')+' # Батарея: '+vb+' мВ , Температура: '+tm+'°C, Влажность: '+hm+'%, Счетчик: '+cnt);
|
||
} else if(len == 3) {
|
||
let flg = value.getUint16(1, true);
|
||
console.log('Последний блок: '+flg);
|
||
flg_memo_act = false;
|
||
} else if(len == 2) {
|
||
addLog('Нет сервиса записи истории!');
|
||
} else
|
||
console.log('blk: ' + dump8(value, value.byteLength));
|
||
} else if((blkId == 0x25 || blkId == 0x26) && (len > 12)) {
|
||
// CMD_ID_CFS Get/Set sensor config
|
||
devcfs.temp_k = value.getUint32(1, true);
|
||
devcfs.humi_k = value.getUint32(5, true);
|
||
devcfs.temp_z = value.getInt16(9, true);
|
||
devcfs.humi_z = value.getInt16(11, true);
|
||
|
||
$('inputTempK').value = devcfs.temp_k;
|
||
$('inputHumK').value = devcfs.humi_k;
|
||
$('inputTempZ').value = devcfs.temp_z;
|
||
$('inputHumZ').value = devcfs.humi_z;
|
||
|
||
addLog("Dev sensor # tk: "+devcfs.temp_k+", hk: "+devcfs.humi_k+", tz: "+devcfs.temp_z+", hz: "+devcfs.humi_z);
|
||
if (len > 17) {
|
||
devcfs.mid = value.getUint16(13, false);
|
||
devcfs.vid = value.getUint16(15, false);
|
||
devcfs.i2c_addr = value.getUint8(17);
|
||
s = "???";
|
||
if(devcfs.mid == 0x5959)
|
||
s = "CHT"+hex(devcfs.vid,4);
|
||
else if(devcfs.mid == 0 && devcfs.vid == 0xaaaa)
|
||
s = "AHT20";
|
||
$('txtSensor').innerHTML = s+', I2C адрес: 0x'+hex(devcfs.i2c_addr,2);
|
||
|
||
addLog("Dev sensor # id: "+hex(devcfs.mid,4)+"-"+hex(devcfs.vid,4)+", i2c_addr: 0x"+hex(devcfs.i2c_addr,2));
|
||
}
|
||
} else if((blkId == 0x55 || blkId == 0x56) && (len > 12)) {
|
||
// CMD_ID_CFG Get/Set device config
|
||
devcfg.flg = value.getUint32(1, true);
|
||
devcfg.rf_tx_power = value.getUint8(5);
|
||
devcfg.advertising_interval = value.getUint8(6);
|
||
devcfg.connect_latency = value.getUint8(7);
|
||
devcfg.reserved1 = value.getUint8(8);
|
||
devcfg.measure_interval = value.getUint8(9);
|
||
devcfg.batt_interval = value.getUint8(10);
|
||
devcfg.averaging_measurements = value.getUint8(11);
|
||
devcfg.reserved2 = value.getUint8(12);
|
||
|
||
$('inputFlag').value = devcfg.flg; // пока нет
|
||
$('inputTxPwr').value = devcfg.rf_tx_power; // 0..0x3f -> -20..+5 dBm нелинейное 0x1f = +0 дБм
|
||
$('inputLat').value = (devcfg.connect_latency + 1)*30; // = (connect_latency + 1)*30 ms
|
||
|
||
$('inputAdvInt').value = devcfg.advertising_interval*62.5; // *62.5 = интервала реклам в ms
|
||
$('inputMeasInt').value = devcfg.measure_interval; // *devcfg.advertising_interval*62.5 = опрос датчика в ms, value минимум = 2 (интервала рекламы)
|
||
$('inputAverInt').value = devcfg.averaging_measurements; // запись истории: при 0 - отключена, 1...255 * шаг опроса датчика = интерал записи истории
|
||
$('inputBatInt').value = devcfg.batt_interval; // в секундах, минимум 2 секунды, но кратно интервалу рекламы
|
||
|
||
addLog("Dev config # flg: "+hex(devcfg.flg,8)+", tx: "+devcfg.rf_tx_power+", adi: "+devcfg.advertising_interval+", cly: "+devcfg.connect_latency+", msi: "+devcfg.measure_interval+", bti: "+devcfg.batt_interval+", avi: "+devcfg.averaging_measurements);
|
||
} else if((blkId == 0xdb || blkId == 0xda) && (len > 4)) {
|
||
// CMD_ID_MEM_RW Read/Write memory
|
||
len -= 4;
|
||
let addr = value.getUint32(1,true);
|
||
let s = bytesToHex(value.buffer.slice(5), len);
|
||
$('inputData').value = s;
|
||
addLog(hex(addr,8) + ':' + s);
|
||
showProgress("Считано: " + len + " байт из 0x" + hex(addr,8));
|
||
} else if((blkId == 0x07) && (len > 1)) {
|
||
// CMD_ID_SERIAL Get serial string
|
||
let s = new TextDecoder("utf-8").decode(value.buffer.slice(1));
|
||
addLog("Dev serial # "+s)
|
||
} else if((blkId == 0x06) && (len > 3)) {
|
||
// CMD_ID_FLASH_ID Get Flash JEDEC ID
|
||
addLog("Dev flash # id: " + bytesToHex(value.buffer.slice(1,4)));
|
||
} else {
|
||
console.log('blk: ' + dump8(value, value.byteLength));
|
||
addLog('Ответ на команду (' + hex(blkId,2) + '): ' + bytesToHex(value.buffer.slice(1)));
|
||
}
|
||
}
|
||
|
||
function GetDevCfg() {
|
||
if(cmdCharacteristic != null) {
|
||
//addLog("GetDevCfg...");
|
||
cmdCharacteristic.writeValue(new Uint8Array([0x55])).catch(error => { console.log(error); addLog("GetDevCfg error!") });
|
||
}
|
||
}
|
||
function GetSensCfg() {
|
||
if(cmdCharacteristic != null) {
|
||
//addLog("GetSensCfg...");
|
||
cmdCharacteristic.writeValue(new Uint8Array([0x25])).catch(error => { console.log(error); addLog("GetSensCfg error!"); });
|
||
}
|
||
}
|
||
function SetDevCfg() {
|
||
if(cmdCharacteristic != null) {
|
||
// addLog("SetDevCfg...");
|
||
devcfg.flg = parseInt($('inputFlag').value)&0xffffffff; // пока нет
|
||
devcfg.rf_tx_power = parseInt($('inputTxPwr').value)&0x3f; // 0..0x3f -> -20..+5 dBm нелинейное 0x1f = +0 дБм
|
||
let connect_latency = parseInt($('inputLat').value); // = (connect_latency + 1)*30 ms
|
||
if (connect_latency < 0) {
|
||
connect_latency = 0;
|
||
} else if (connect_latency <= 7680) {
|
||
connect_latency = (connect_latency/30) - 1;
|
||
} else
|
||
connect_latency = 255;
|
||
devcfg.connect_latency = connect_latency & 0xff;
|
||
let advertising_interval = parseInt($('inputAdvInt').value);
|
||
if(advertising_interval <= 62.5)
|
||
advertising_interval = 1;
|
||
else if(advertising_interval >= 15937.5)
|
||
advertising_interval = 255;
|
||
else
|
||
advertising_interval = advertising_interval/62.5;
|
||
devcfg.advertising_interval = advertising_interval & 0xff;
|
||
devcfg.measure_interval = parseInt($('inputMeasInt').value);
|
||
if(devcfg.measure_interval < 2) // опрос датчика в интервалах рекламы, value минимум = 2 (интервала рекламы)
|
||
devcfg.measure_interval = 2;
|
||
else if(devcfg.measure_interval > 255)
|
||
devcfg.measure_interval = 255
|
||
devcfg.averaging_measurements = parseInt($('inputAverInt').value);
|
||
if(devcfg.averaging_measurements < 0) // запись истории: при 0 - отключена, 1...255 * шаг опроса датчика = интерал записи истории
|
||
devcfg.averaging_measurements = 0;
|
||
if(devcfg.averaging_measurements > 255)
|
||
devcfg.averaging_measurements = 255;
|
||
devcfg.batt_interval = parseInt($('inputBatInt').value);
|
||
if(devcfg.batt_interval < 2) // в секундах, минимум 2 секунды, но кратно интервалу рекламы
|
||
devcfg.batt_interval = 2;
|
||
if(devcfg.batt_interval > 255)
|
||
devcfg.batt_interval = 255;
|
||
blk = new Uint8Array([0x55,
|
||
devcfg.flg & 0xff, (devcfg.flg>>8) & 0xff, (devcfg.flg>>16) & 0xff, (devcfg.flg>>24) & 0xff,
|
||
devcfg.rf_tx_power,
|
||
devcfg.advertising_interval,
|
||
devcfg.connect_latency,
|
||
devcfg.reserved1,
|
||
devcfg.measure_interval,
|
||
devcfg.batt_interval,
|
||
devcfg.averaging_measurements,
|
||
devcfg.reserved2
|
||
]);
|
||
cmdCharacteristic.writeValue(blk).catch(error => { console.log(error); addLog("SetDevCfg error!");});
|
||
}
|
||
}
|
||
function SetSensCfg() {
|
||
if(cmdCharacteristic != null) {
|
||
addLog("SetSensCfg...");
|
||
|
||
devcfs.temp_k = parseInt($('inputTempK').value);
|
||
devcfs.humi_k = parseInt($('inputHumK').value);
|
||
devcfs.temp_z = parseInt($('inputTempZ').value);
|
||
devcfs.humi_z = parseInt($('inputHumZ').value);
|
||
|
||
blk = new Uint8Array([0x55,
|
||
devcfs.temp_k & 0xff, (devcfs.temp_k >> 8) & 0xff, (devcfs.temp_k >> 16) & 0xff, (devcfs.temp_k >> 24) & 0xff,
|
||
devcfs.humi_k & 0xff, (devcfs.humi_k >> 8) & 0xff, (devcfs.humi_k >> 16) & 0xff, (devcfs.humi_k >> 24) & 0xff,
|
||
devcfs.temp_z & 0xff, (devcfs.temp_z >> 8) & 0xff,
|
||
devcfs.humi_z & 0xff, (devcfs.humi_z >> 8) & 0xff
|
||
]);
|
||
cmdCharacteristic.writeValue(new Uint8Array([0x25])).catch(error => { console.log(error); addLog("SetSensCfg error!"); });
|
||
}
|
||
}
|
||
|
||
var url;
|
||
function download(data, filename, type) {
|
||
var file = new Blob([data], {type: type});
|
||
if (window.navigator.msSaveOrOpenBlob) { // ie10+
|
||
window.navigator.msSaveOrOpenBlob(file, filename);
|
||
} else { // ff, chrome
|
||
url = URL.createObjectURL(file);
|
||
let a = document.createElement("a");
|
||
a.href = url;
|
||
a.download = filename;
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
setTimeout(function(){document.body.removeChild(a);window.URL.revokeObjectURL(url);},0);
|
||
URL.revokeObjectURL(url);
|
||
}
|
||
}
|
||
|
||
const selectFile = function() {
|
||
|
||
let regex = /[^\\]+$/
|
||
|
||
this.choose,
|
||
this.selected
|
||
|
||
this.msg = str => {
|
||
let prefix = '[selectFile]\n\nError: '
|
||
return alert(prefix+str)
|
||
}
|
||
|
||
this.check = () => {
|
||
if (this.choose && this.selected != null) {
|
||
let choose = $(this.choose),
|
||
selected = $(this.selected)
|
||
choose.addEventListener('change',() => {
|
||
if (choose.value != '') {
|
||
selected.innerHTML = choose.value.match(regex);
|
||
readFile(choose.files[0]);
|
||
}
|
||
})
|
||
} else {
|
||
this.msg('Targets not set.')
|
||
}
|
||
}
|
||
|
||
selectFile.prototype.targets = (trigger, filetext) => {
|
||
this.choose = trigger
|
||
this.selected = filetext
|
||
}
|
||
|
||
selectFile.prototype.simulate = () => {
|
||
if (this.choose != null) {
|
||
let choose = $(this.choose)
|
||
if (typeof choose != 'undefined') {
|
||
choose.click()
|
||
this.check()
|
||
} else {
|
||
this.msg('Could not find element '+this.choose)
|
||
}
|
||
} else {
|
||
this.msg('Targets not set.')
|
||
}
|
||
}
|
||
|
||
};
|
||
|
||
function readFile(file) {
|
||
|
||
var reader = new FileReader();
|
||
|
||
if (file != null) {
|
||
reader.fname = file.name;
|
||
reader.readAsArrayBuffer(file);
|
||
} else {
|
||
showError("Файл не загружен");
|
||
}
|
||
|
||
reader.onload = function() {
|
||
getFwArray(this.result, this.fname);
|
||
}
|
||
|
||
reader.onerror = function() {
|
||
console.log(this.error);
|
||
};
|
||
}
|
||
|
||
var getFile = new selectFile;
|
||
getFile.targets('inpFile','lblFile');
|
||
|
||
function openTab(evt, tabName) {
|
||
var i, tabcontent, tablinks;
|
||
tabcontent = document.getElementsByClassName("tabcontent");
|
||
for (i = 0; i < tabcontent.length; i++) {
|
||
tabcontent[i].style.display = "none";
|
||
}
|
||
tablinks = document.getElementsByClassName("tablinks");
|
||
for (i = 0; i < tablinks.length; i++) {
|
||
tablinks[i].className = tablinks[i].className.replace(" active", "");
|
||
}
|
||
$(tabName).style.display = "block";
|
||
if (!isConnected) $('tabConfig').style.display = "none";
|
||
if (evt != 0) {
|
||
evt.currentTarget.className += " active";
|
||
// console.log(evt.currentTarget.className);
|
||
}
|
||
}
|
||
|
||
function selectConfigTab() {
|
||
openTab(0, "tabConfig");
|
||
tablinks = document.getElementsByClassName("tablinks");
|
||
tablinks[0].className += " active";
|
||
}
|
||
|
||
window.onload = (event) => {
|
||
showState("Нет подключения" );
|
||
selectConfigTab();
|
||
};
|
||
|
||
</script>
|
||
<h2>PHY62x2-BTHome <a href="https://github.com/pvvx/THB2"><small>ⓘ</small></a></h2>
|
||
<label for="inpNamePrefix">Префикс названия устройств(а)</label><br>
|
||
<input type="text" id="inpNamePrefix" value="" placeholder="THB, BT">
|
||
<hr>
|
||
<label id="lblStatus">Состояние: загрузка страницы</label>
|
||
<br><br><hr>
|
||
<button type="button" id="btnConnect" onclick="connect()">Соединение</button>
|
||
<button type="button" id="btnDisconnect" onclick="disconnect()" disabled="true">Отключение</button>
|
||
<button type="button" id="btnReconnect" onclick="reconnect()" disabled="true">Переподключение</button>
|
||
<br><hr>
|
||
|
||
<div class="tab">
|
||
<button class="tablinks" onclick="openTab(event, 'tabConfig')">Config</button>
|
||
<button class="tablinks" onclick="openTab(event, 'tabOTA')">OTA</button>
|
||
<button class="tablinks" onclick="openTab(event, 'tabFlash')">Flash</button>
|
||
<button class="tablinks" onclick="openTab(event, 'tabService')">Service</button>
|
||
</div>
|
||
|
||
<div id="tabConfig" class="tabcontent">
|
||
Параметры связи<br>
|
||
<!--
|
||
Флаг: <input size="8" type="text" id="inputFlag" maxlength="8">
|
||
Tx Power: <input size="8" type="text" id="inputTxPwr" maxlength="8">
|
||
Conn. Latency: <input size="8" type="text" id="inputLat" maxlength="8">
|
||
<br>
|
||
Интервал рекламы: <input size="8" type="text" id="inputAdvInt" maxlength="8">
|
||
Интервал измерений: <input size="8" type="text" id="inputMeasInt" maxlength="8">
|
||
Интервал опроса батареи: <input size="8" type="text" id="inputMeasBat" maxlength="8">
|
||
Интервал усреднения: <input size="8" type="text" id="inputAverInt" maxlength="8">
|
||
<br> -->
|
||
<table>
|
||
<tr>
|
||
<th>Флаг</th>
|
||
<th>Tx Power</th>
|
||
<th>Conn. Latency</th>
|
||
</tr>
|
||
<tr>
|
||
<td><input size="8" type="text" id="inputFlag" maxlength="8"></td>
|
||
<td><input size="4" type="text" id="inputTxPwr" maxlength="4"></td>
|
||
<td><input size="4" type="text" id="inputLat" maxlength="4"></td>
|
||
</tr>
|
||
</table>
|
||
Интервалы
|
||
<table>
|
||
<tr>
|
||
<th>Реклама</th>
|
||
<th>Измерения</th>
|
||
<th>Усреднение</th>
|
||
<th>Уровень батареи</th>
|
||
</tr>
|
||
<tr>
|
||
<td><input size="4" type="text" id="inputAdvInt" maxlength="4"></td>
|
||
<td><input size="4" type="text" id="inputMeasInt" maxlength="4"></td>
|
||
<td><input size="4" type="text" id="inputAverInt" maxlength="4"></td>
|
||
<td><input size="4" type="text" id="inputBatInt" maxlength="4"></td>
|
||
</tr>
|
||
</table>
|
||
<button type="button"id="btnGetDev" onclick="GetDevCfg()" >Прочитать</button>
|
||
<button type="button"id="btnSetDev" onclick="SetDevCfg()" >Записать</button>
|
||
<hr>
|
||
<table>
|
||
<tr>
|
||
<th>Параметры сенсора:</th>
|
||
<th>Температура</th>
|
||
<th>Влажность</th>
|
||
</tr>
|
||
<tr>
|
||
<th><div id="txtSensor">?</div></th>
|
||
</tr>
|
||
<tr>
|
||
<td>Поправочные коэффициенты</td>
|
||
<td><input size="8" type="text" id="inputTempK" maxlength="8"></td>
|
||
<td><input size="8" type="text" id="inputHumK" maxlength="8"></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Коррекция смещения</td>
|
||
<td><input size="8" type="text" id="inputTempZ" maxlength="8"></td>
|
||
<td><input size="8" type="text" id="inputHumZ" maxlength="8"></td>
|
||
</tr>
|
||
</table>
|
||
<br>
|
||
|
||
<!--
|
||
Поправочные коэффициенты:
|
||
Температуры: <input size="8" type="text" id="inputTempK" maxlength="8">
|
||
Влажности:
|
||
<br>
|
||
Коррекция смещения:
|
||
Температуры: <input size="8" type="text" id="inputTempZ" maxlength="8">
|
||
Влажности: <input size="8" type="text" id="inputHumZ" maxlength="8">
|
||
<br>
|
||
Параметры I2C<br>
|
||
Vendor: <input size="4" type="text" id="inputI2CVen" maxlength="4">
|
||
Model: <input size="4" type="text" id="inputI2CMod" maxlength="4">
|
||
Address: <input size="2" type="text" id="inputI2CAddr" maxlength="2">
|
||
<br> -->
|
||
<button type="button"id="btnGetSens" onclick="GetSensCfg()" >Прочитать</button>
|
||
<button type="button"id="btnSetSens" onclick="SetSensCfg()" >Записать</button>
|
||
</div>
|
||
|
||
<div id="tabOTA" class="tabcontent">
|
||
<p>Файл прошивки:
|
||
<input type=file hidden id=inpFile>
|
||
<label id=lblFile>не выбран</label>
|
||
<button type=button onClick=getFile.simulate()>Выбрать</button>
|
||
<p>
|
||
<hr>
|
||
<button type="button"id="btnStartDFU" onclick="startDFU()" disabled="true" >Старт программирования</button>
|
||
</div>
|
||
|
||
<div id="tabFlash" class="tabcontent">
|
||
<p>
|
||
<button type="button" id="btnReadFF" onclick="startReadFF()" disabled="true">Прочитать</button>
|
||
<button type="button" id="btnSave" disabled="true">Сохранить в файл</button>
|
||
<p>
|
||
</div>
|
||
|
||
<div id="tabService" class="tabcontent">
|
||
Чтение и запись памяти:<br>
|
||
Адрес (hex): <input size="8" type="text" id="inputAddr" value="11000000" maxlength="8">
|
||
<button type="button" id="btnReadAddr" onclick="infoReadAddr()" disabled="true">Прочитать</button>
|
||
Данные (hex): <input size="32" type="text" id="inputData" value="?" maxlength="32">
|
||
<button type="button" id="btnSendData" onclick="sendData()" disabled="true">Записать</button><hr>
|
||
<button type="button" id="btnSendCommand" onclick="sendCommand()" disabled="true">Команда</button>
|
||
<input type="text" id="inpCmdData" value="55" size="40" maxlength="40"><hr>
|
||
</div>
|
||
<hr>
|
||
<button type="button" onclick="clearLog()">Очистить лог</button><br>
|
||
<div id="log"></div>
|
||
</body>
|
||
</html>
|