1836 lines
64 KiB
HTML
1836 lines
64 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">
|
||
<title>PHY62x2 BTHome v0.99</title>
|
||
<link rel="stylesheet" type="text/css" href="styles.css" />
|
||
<link rel="stylesheet" type="text/css" href="chart.css" />
|
||
<script type="text/javascript" src="dygraph.min.js" /></script>
|
||
<!--link rel="stylesheet" type="text/css" href="https://dygraphs.com/dist/dygraph.min.css /-->
|
||
<!--script type="text/javascript" src="https://dygraphs.com/dist/dygraph.min.js" /></script-->
|
||
<style type="text/css">
|
||
|
||
div#divChart {
|
||
height: 100%;
|
||
width: 100%;
|
||
margin-top: 16px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<script>
|
||
//BLE values
|
||
const FLASH_SIZE = 0x80000;
|
||
const OTA_MAX_SIZE = 0x30000; // 196608
|
||
|
||
const SERVICE_SCREEN = 0x00000020;
|
||
const SERVICE_TH_TRG = 0x00004000;
|
||
|
||
var bluetoothDevice, gattServer, otaCharacteristic, cmdCharacteristic, infoService;
|
||
|
||
var devInfo = {};
|
||
var devSrv = { services: 0 };
|
||
var devCfg = {};
|
||
var devSens = {};
|
||
var devTrig = {};
|
||
var devTime = {};
|
||
var devName = "";
|
||
|
||
var isConnected = false;
|
||
var isChartEnabled = true;
|
||
var isMemoActive = false;
|
||
|
||
var startTime = 0;
|
||
var memoCount = 0;
|
||
|
||
var ota = {
|
||
fwArray: null,
|
||
fwname: "",
|
||
fwsize: 0,
|
||
// fwmaxsize: 196608,
|
||
ext_flg: false,
|
||
blockCount: 0,
|
||
program_offset: 0x11010000
|
||
};
|
||
|
||
var flashBuffer = null;
|
||
|
||
//Connection values
|
||
var connectRetries = 0;
|
||
|
||
var $ = function(id) { return document.getElementById(id);}
|
||
const isEmpty = str => !str.trim().length;
|
||
|
||
function resetVariables() {
|
||
gattServer = null;
|
||
mainService = null;
|
||
otaCharacteristic = null;
|
||
cmdCharacteristic = null;
|
||
$('btnDisconnect').disabled = true;
|
||
$('btnStartDFU').disabled = true;
|
||
}
|
||
|
||
function handleError(text) {
|
||
showError(text);
|
||
resetVariables();
|
||
if ((bluetoothDevice != null) && (connectRetries < 5)) {
|
||
addLog("Переподключение " + connectRetries + " из " + 5);
|
||
$('btnDisconnect').disabled = true;
|
||
$('btnReconnect').disabled = true;
|
||
connectRetries++;
|
||
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();
|
||
chartData.length = 0;
|
||
resetVariables();
|
||
bluetoothDevice = null;
|
||
$('btnReconnect').disabled = true;
|
||
$('tabConfig').style.display = "none";
|
||
|
||
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;
|
||
}
|
||
$('btnConnect').disabled = true;
|
||
$('btnReconnect').disabled = true;
|
||
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;
|
||
//DOMException: getCharacteristic(s) called with blocklisted UUID. https://goo.gl/4NeimX
|
||
//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 => parseBlkOTA(event.target.value));
|
||
}).then(_ => {
|
||
return otaCharacteristic.readValue();
|
||
}).then(value => {
|
||
if(value.byteLength > 1) {
|
||
addLog("OTA ver: "+ hex(value.getUint8(1),2));
|
||
$("btnStartDFU").value = "Старт программирования"
|
||
}
|
||
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;
|
||
cmdCharacteristic = null;
|
||
return mainService.getCharacteristic(0xfff4);
|
||
}).then(characteristic => {
|
||
console.log("Найдена CMD Characteristic");
|
||
cmdCharacteristic = characteristic;
|
||
return cmdCharacteristic.addEventListener('characteristicvaluechanged', event => parseBlkCustom(event.target.value));
|
||
}).then(_ => {
|
||
return cmdCharacteristic.startNotifications();
|
||
}).then(_ => {
|
||
return cmdCharacteristic.readValue();
|
||
}).then(value => {
|
||
if(value.byteLength >= 10)
|
||
if(value.getUint8(0) != 0)
|
||
addLog("Странный ответ устройства!");
|
||
else if((value.getUint32(8, true) & 1) != 0) // SERVICE_OTA ?
|
||
return linkOta();
|
||
otaCharacteristic = null;
|
||
}).then(_ => {
|
||
showState("Устройство подключено.");
|
||
isConnected = true;
|
||
// $('btnConnect').disabled = true;
|
||
$('btnDisconnect').disabled = false;
|
||
// $('btnReconnect').disabled = false;
|
||
|
||
disableControls(false);
|
||
// selectConfigTab();
|
||
// $('tabConfig').style.display = "block";
|
||
|
||
let el = $('btnStartDFU');
|
||
if (otaCharacteristic != null) {
|
||
el.disabled = ota.fwArray == null;
|
||
el.innerHTML = "Старт";
|
||
el.title = "Старт программирования..."
|
||
} else {
|
||
el.innerHTML = "Режим OTA";
|
||
el.title = "Переключение на BootLoader ..."
|
||
}
|
||
return cmdCharacteristic.writeValue(new Uint8Array([0x33])); // get measure
|
||
}).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 auxControls(state)
|
||
{
|
||
if ( devSrv.services & SERVICE_SCREEN ) {
|
||
$('tblChkCfg').style.display = "block";
|
||
$('tblComfort').style.display = "block";
|
||
} else {
|
||
$('tblChkCfg').style.display = "none";
|
||
$('tblComfort').style.display = "none";
|
||
}
|
||
if ( devSrv.services & SERVICE_TH_TRG ) {
|
||
$('tblTrigger').style.display = "block";
|
||
$('btnGetTrg').disabled = state;
|
||
$('btnSetTrg').disabled = state;
|
||
} else {
|
||
$('tblTrigger').style.display = "none";
|
||
$('btnGetTrg').style.display = "none";
|
||
$('btnSetTrg').style.display = "none";
|
||
}
|
||
if (devSrv.services & (SERVICE_SCREEN|SERVICE_TH_TRG))
|
||
$('hrPres').style.display = "block";
|
||
else
|
||
$('hrPres').style.display = "none";
|
||
}
|
||
|
||
function disableControls(state)
|
||
{
|
||
//--debug devSrv.services = SERVICE_SCREEN|SERVICE_TH_TRG;
|
||
auxControls(state);
|
||
if(state) { // hide
|
||
$('hrSensorData').style.display = "none";
|
||
$('tblSensorData').style.display = "none";
|
||
} else { // show
|
||
$('hrSensorData').style.display = "block";
|
||
$('tblSensorData').style.display = "block";
|
||
}
|
||
$('btnGetDev').disabled = state;
|
||
$('btnSetDev').disabled = state;
|
||
$('btnRstDev').disabled = state;
|
||
$('btnGetSens').disabled = state;
|
||
$('btnSetSens').disabled = state;
|
||
$('btnRstSens').disabled = state;
|
||
$('btnGetDevTime').disabled = state;
|
||
$('btnSetDevTime').disabled = state;
|
||
|
||
$('btnReadAddr').disabled = state;
|
||
$('btnWriteAddr').disabled = state;
|
||
$('btnSendCommand').disabled = state;
|
||
|
||
$('btnGetMAC').disabled = state;
|
||
$('btnSetMAC').disabled = state;
|
||
|
||
$('btnGetName').disabled = state;
|
||
$('btnSetName').disabled = state;
|
||
$('btnRstName').disabled = state;
|
||
|
||
}
|
||
|
||
function onDisconnected() {
|
||
isConnected = false;
|
||
resetVariables();
|
||
showState('Устройство отключено.');
|
||
$('btnConnect').disabled = false;
|
||
$('btnReconnect').disabled = false;
|
||
// $('tabConfig').style.display = "none";
|
||
disableControls(true);
|
||
}
|
||
|
||
function startDFU() {
|
||
if(otaCharacteristic != null && ota.ind.version == 1 && (devSrv.services & 1) == 1) {
|
||
addLog("Старт программирования...");
|
||
updateBegin();
|
||
} else {
|
||
addLog("Переключение на BootLoader...");
|
||
if(cmdCharacteristic != null) {
|
||
cmdCharacteristic.writeValue(new Uint8Array([0x72,0x55])).then(_ => {
|
||
console.log('Comand to reboot to bootloader sent');
|
||
reconnect();
|
||
}).catch(error => { addLog(error); });
|
||
}
|
||
}
|
||
}
|
||
|
||
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 showError(text) {
|
||
// console.log("Status: " + status);
|
||
let s = "Ошибка: " + text;
|
||
$("lblStatus").className = "shadowerror";
|
||
$("lblStatus").innerHTML = s;
|
||
addLog(text);
|
||
}
|
||
|
||
function showProgress(text) {
|
||
$("lblStatus").className = "shadowprogress";
|
||
$("lblStatus").innerHTML = text;
|
||
}
|
||
|
||
function clearLog() {
|
||
$("log").innerHTML = "";
|
||
}
|
||
|
||
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 testFwOTA(data) {
|
||
ota.fwsize = 0;
|
||
let fsize = data.byteLength;
|
||
console.log("File size = 0x"+ fsize.toString(16));
|
||
if (fsize < 20)
|
||
return "Неправильный размер двоичной прошивки PHY6 OTA!";
|
||
if(fsize > OTA_MAX_SIZE)
|
||
return "Размер прошивки более " + (OTA_MAX_SIZE/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 = testFwOTA(data);
|
||
if(s != "OK") {
|
||
$('btnStartDFU').disabled = true;
|
||
addLog(s);
|
||
ota.blockCount = 0;
|
||
ota.fwArray = null;
|
||
fwname = "";
|
||
alert(s);
|
||
showError("OTA файл: " + filename + " не загружен");
|
||
$('lblFile').innerHTML = "не загружен";
|
||
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;
|
||
showProgress("OTA файл: " + filename);
|
||
if(isConnected)
|
||
$('btnStartDFU').disabled = false;
|
||
}
|
||
|
||
var otaErrStrs = [
|
||
'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 otaErrStrs[err];
|
||
return "Неизвестная ошибка";
|
||
}
|
||
|
||
function parseBlkOTA(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() {
|
||
sendCharOTA("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
|
||
// }
|
||
sendCharOTA(cmd).then(_ => { otaCharacteristic.readValue().then(value => {
|
||
if(value.byteLength >= 2 && value.getUint8(0) == 0) {
|
||
setTimeout(function() {
|
||
startTime = new Date().getTime();
|
||
sendBlkOTA(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) {
|
||
return sendCharOTA("02ff").then(_ => {
|
||
let s = "Программирование завершено за " + (new Date().getTime() - startTime) / 1000 + " секунды";
|
||
showProgress(s);
|
||
addLog(s);
|
||
}).catch(function(err) {
|
||
updateFail("Ошибка передачи команды перезагрузки после OTA");
|
||
});
|
||
} else
|
||
showError("Ошибка ("+value.getUint8(0)+") на передаче блока "+blockNr+" OTA: "+s);
|
||
}).catch(function(err) { updateFail(err); });
|
||
}
|
||
|
||
function sendBlkOTA(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);
|
||
sendCharOTA(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)
|
||
sendBlkOTA(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
|
||
sendBlkOTA(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 sendCharOTA = 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 sendCharMain = 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);
|
||
showError('Ошибка, длина блока от 1 до 16 байт!');
|
||
}
|
||
}
|
||
}
|
||
|
||
function writeCmd(data) {
|
||
if(cmdCharacteristic) {
|
||
len = data.length;
|
||
if((len > 0) && (len <= 20)) {
|
||
let blk = new Uint8Array(data);
|
||
cmdCharacteristic.writeValue(blk);
|
||
} else
|
||
showError('Ошибка, длина команды от 1 до 20 байт!');
|
||
}
|
||
}
|
||
|
||
function readAddress() {
|
||
if(cmdCharacteristic) {
|
||
let faddr = parseInt($('inpAddr').value, 16);
|
||
readAddr(faddr);
|
||
}
|
||
}
|
||
|
||
function writeAddress() {
|
||
let addr = parseInt($('inpAddr').value, 16);
|
||
let data = hexToBytes($('inpData').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 showConfig() {
|
||
/*
|
||
if($('inpFlag') != null)
|
||
$('inpFlag').value = devCfg.flg; // пока не реализовано
|
||
$('inpTxPwr').value = devCfg.tx_power; // 0..0x3f -> -20..+5 dBm нелинейное 0x1f = +0 дБм
|
||
$('inpLat').value = (devCfg.connect_latency + 1)*30; // = (connect_latency + 1)*30 ms
|
||
|
||
$('inpAdvInt').value = devCfg.advertising_interval*62.5; // *62.5 = интервала рекламы в ms
|
||
$('inpMeasInt').value = devCfg.measure_interval; // *devCfg.advertising_interval*62.5 = опрос датчика в ms, value минимум = 2 (интервала рекламы)
|
||
$('inpAverInt').value = devCfg.averaging_measurements; // запись истории: при 0 - отключена, 1...255 * шаг опроса датчика = интерал записи истории
|
||
$('inpBatInt').value = devCfg.batt_interval; // в секундах, минимум 2 секунды, но кратно интервалу рекламы
|
||
*/
|
||
$('chkCfgNotify').checked = (devCfg.flg & 1) != 0;
|
||
$('chkCfgClock').checked = (devCfg.flg & 2) != 0;
|
||
$('chkCfgSmiley').checked = (devCfg.flg & 4) != 0;
|
||
$('chkCfgTrg').checked = (devCfg.flg & 8) != 0;
|
||
let txPwr = 31;
|
||
el = $('selTxPwr');
|
||
for(let n = 0; n < el.options.length; n++) if(el.options[n].value >= devCfg.tx_power) txPwr = el.options[n].value;
|
||
el.value = txPwr;
|
||
$('inpLat').value = (devCfg.connect_latency + 1)*30; // = (connect_latency + 1)*30 ms
|
||
$('inpAdvInt').value = devCfg.advertising_interval*62.5; // *62.5 = интервала рекламы в ms
|
||
$('inpMeasInt').value = devCfg.measure_interval; // *devCfg.advertising_interval*62.5 = опрос датчика в ms, value минимум = 2 (интервала рекламы)
|
||
$('inpAverInt').value = devCfg.averaging_measurements; // запись истории: при 0 - отключена, 1...255 * шаг опроса датчика = интерал записи истории
|
||
$('inpBatInt').value = devCfg.batt_interval; // в секундах, минимум 2 секунды, но кратно интервалу рекламы
|
||
}
|
||
|
||
function parseBlkCustom(value) {
|
||
let len = value.byteLength;
|
||
if(len == 0) return;
|
||
let blkId = value.getUint8(0);
|
||
if((blkId == 0x00) && (len >= 12)) {
|
||
// CMD_ID_DEVID Get dev id, version, services
|
||
devSrv.revision = value.getUint8(1);
|
||
devSrv.hw_version = value.getUint16(2, true);
|
||
devSrv.sw_version = value.getUint16(4, true);
|
||
devSrv.dev_spec_data = value.getUint16(6, true);
|
||
devSrv.services = value.getUint32(8, true);
|
||
auxControls(false);
|
||
addLog("Device info # hw: " + hex(devSrv.hw_version,4)
|
||
+ ", sw: " + hex(devSrv.sw_version,4)
|
||
+ ", services: " + hex(devSrv.services,8)
|
||
+ ", sd: " + hex(devSrv.dev_spec_data, 4));
|
||
} else if(blkId == 0x35){
|
||
if(isMemoActive) {
|
||
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);
|
||
addLog(((dt.toISOString().slice(0, -1)).replace('T',' ')).replace('.000','')+' # Батарея: '+vb+' мВ , Температура: '+tm+'°C, Влажность: '+hm+'%, Счетчик: '+cnt);
|
||
memoCount = cnt;
|
||
} else if(len == 3) {
|
||
let flg = value.getUint16(1, true);
|
||
console.log('Memo End: '+flg);
|
||
if(memoCount == 0)
|
||
addLog('Пока нет истории!');
|
||
isMemoActive = false;
|
||
} else if(len == 2) {
|
||
addLog('Нет сервиса записи истории!');
|
||
isMemoActive = false;
|
||
} else {
|
||
console.log('blk: ' + dump8(value, value.byteLength));
|
||
isMemoActive = false;
|
||
}
|
||
}
|
||
} else if(blkId == 0x33 && len >= 10) {
|
||
let count = value.getUint16(1, true);
|
||
let temp = value.getInt16(3, true) / 100.0;
|
||
let humi = value.getInt16(5, true) / 100.0;
|
||
let vbat = value.getUint16(7, true);
|
||
let pbat = value.getUint8(9);
|
||
let flg = 0;
|
||
let rds_count = 0;
|
||
let s = 'Температура: ' + temp.toFixed(2) + '°C'
|
||
+ ', Влажность: ' + humi.toFixed(2) + '%'
|
||
+ ', Vbat: ' + vbat + ' мВ'
|
||
+ ', ID: ' + count;
|
||
if(len > 12) {
|
||
flg = value.getUint8(9);
|
||
rds_count = value.getUint32(10, true);
|
||
s += ', счетчик срабатываний: '+ rds_count
|
||
+ ', флаги: 0x' + hex(flg,2) + ':r' + (flg&1) + '/t' + ((flg>>1)&1);
|
||
}
|
||
console.log(s);
|
||
$('lblTemp').innerHTML = temp + ' °C';
|
||
$('lblHumi').innerHTML = humi + ' %RH';
|
||
$('lblBat').innerHTML = pbat + ' % (' + vbat / 1000 + ' V)';
|
||
if(len > 10) {
|
||
flg = value.getUint8(10);
|
||
s += ", флаги: 0x" + hex(flg,2);
|
||
$('lblTrg').innerHTML = "In: " + (flg&1)
|
||
+ ", Out: " + ((flg&2)? 1:0)
|
||
+ ", Cmf: " + ((flg&4)? 1:0)
|
||
+ ", Te: " + ((flg&16)? 1:0)
|
||
+ ", He: " + ((flg&32)? 1:0);
|
||
}
|
||
console.log(s);
|
||
addChartData(temp, humi);
|
||
if(isChartEnabled)
|
||
chart.updateOptions(
|
||
{
|
||
file: chartData,
|
||
axes: {
|
||
y: { 'valueRange': rangeT },
|
||
y2: { 'valueRange': rangeH }
|
||
}
|
||
}
|
||
);
|
||
getDevTime();
|
||
} else if((blkId == 0x25 || blkId == 0x26) && (len > 12)) {
|
||
// CMD_ID_CFS Get/Set sensor config
|
||
devSens.temp_k = value.getUint32(1, true);
|
||
devSens.humi_k = value.getUint32(5, true);
|
||
devSens.temp_z = value.getInt16(9, true);
|
||
devSens.humi_z = value.getInt16(11, true);
|
||
|
||
$('inpTempK').value = devSens.temp_k;
|
||
$('inpHumK').value = devSens.humi_k;
|
||
$('inpTempZ').value = devSens.temp_z;
|
||
$('inpHumZ').value = devSens.humi_z;
|
||
|
||
addLog("Device Sensor "
|
||
+ "# Kt: " + devSens.temp_k
|
||
+ ", Kh: " + devSens.humi_k
|
||
+ ", Zt: " + devSens.temp_z
|
||
+ ", Zh: " + devSens.humi_z);
|
||
if (len > 17) {
|
||
devSens.mid = value.getUint16(13, false);
|
||
devSens.vid = value.getUint16(15, false);
|
||
devSens.i2c_addr = value.getUint8(17);
|
||
s = "???";
|
||
if(devSens.mid == 0x5959)
|
||
s = "CHT"+hex(devSens.vid,4);
|
||
else if(devSens.mid == 0 && devSens.vid == 0xaaaa)
|
||
s = "AHT20";
|
||
$('lblSensor').innerHTML = s+', I2C адрес: 0x'+hex(devSens.i2c_addr,2);
|
||
|
||
addLog("Dev sensor # id: "+hex(devSens.mid,4)+"-"+hex(devSens.vid,4)+", i2c_addr: 0x"+hex(devSens.i2c_addr,2));
|
||
}
|
||
} else if((blkId == 0x55 || blkId == 0x56) && (len > 12)) {
|
||
// CMD_ID_CFG Get/Set device config
|
||
devCfg.flg = value.getUint32(1, true);
|
||
// bit0 - measure notify enable
|
||
// bit1 - lcd chow time enable
|
||
devCfg.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);
|
||
showConfig();
|
||
console.log("Dev config # flg: " + hex(devCfg.flg,8)
|
||
+ ", tx: " + devCfg.tx_power
|
||
+ ", adi: " + devCfg.advertising_interval
|
||
+ ", cli: " + devCfg.connect_latency
|
||
+" , msi: " + devCfg.measure_interval
|
||
+ ", bti: " + devCfg.batt_interval
|
||
+ ", avi: " + devCfg.averaging_measurements);
|
||
addLog("Строка конфигурации: "+ bytesToHex(value.buffer));
|
||
} else if((blkId == 0xdb) && (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);
|
||
$('inpData').value = s;
|
||
addLog("Данные по адресу 0x" + hex(addr,8) + ': ' + s);
|
||
showProgress("Считано: " + len + " байт из 0x" + hex(addr,8));
|
||
} else if((blkId == 0xda) && (len > 8)) {
|
||
let addr = value.getUint32(1,true);
|
||
let reg_data = value.getUint32(5,true);
|
||
addLog("Регистр по адресу 0x"+ hex(addr,8) + " имеет значение 0x" + hex(reg_data,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("Серийный номер: "+s)
|
||
} else if((blkId == 0x06) && (len > 3)) {
|
||
// CMD_ID_FLASH_ID Get Flash JEDEC ID
|
||
addLog("Flash id: " + bytesToHex(value.buffer.slice(1,4)));
|
||
} else if(blkId == 0x60 && len >= 6) {
|
||
s = 'LCD data # '+bytesToHex(value.buffer.slice(1));
|
||
addLog(s);
|
||
} else if(blkId == 0x18 && len >= 1) { // Get/set beacon bkey in EEP
|
||
if(len >= 16) {
|
||
devkeys.cbindkey = value.buffer.slice(1);
|
||
let s = bytesToHex(devkeys.cbindkey,16);
|
||
addLog("Bindkey # "+ s);
|
||
//if($("cbind_key"))
|
||
// $("cbind_key").value = s;
|
||
} else {
|
||
if(len == 1 && value.getUint8(1) == 0xff)
|
||
addLog("Bindkey не запсан!");
|
||
else
|
||
addAlog("Ошибка чтения Bindkey!");
|
||
//if($("cbind_key"))
|
||
// $("cbind_key").value = '?';
|
||
}
|
||
} else if(blkId == 0x23 && len >= 4) {
|
||
devTime.cur = value.getUint32(1,true);
|
||
console.log('Device Time: 0x' + hex(devTime.cur, 8));
|
||
let dt = new Date(devTime.cur * 1000);
|
||
let sDateTime = (dt.toISOString().slice(0, -5)).replace('T',' ');
|
||
$('lblTime').innerHTML = sDateTime
|
||
let time = Date.now() / 1000 - (new Date()).getTimezoneOffset() * 60;
|
||
let delta = devTime.cur - time;
|
||
console.log('Время на устройстве: ' + sDateTime + ', Уход: ' + Math.round(delta) + ' сек');
|
||
if(len >= 8) {
|
||
devTime.set = value.getUint32(5,true);
|
||
console.log('Последняя установка времени: 0x' + hex(devTime.set,8));
|
||
if(devTime.step == 1) {
|
||
if(devTime.set > 0x60000000) {
|
||
devTime.period = devTime.cur - devTime.set;
|
||
devTime.cmp = time;
|
||
let odt = new Date(devTime.set * 1000);
|
||
addLog('Последняя установка времени: '+(odt.toISOString().slice(0, -5)).replace('T',' '));
|
||
console.log('Прошло на устройстве: ' + devTime.period.toFixed(1) + ' секунд');
|
||
let rp = devTime.cmp - devTime.set;
|
||
let delta = rp - devTime.period;
|
||
console.log('Прошло реально: ' + rp.toFixed(1) + ' секунд, разница: ' + delta.toFixed(1) + ' секунд');
|
||
if(rp >= 10800) { // 10800
|
||
devTime.step = 2;
|
||
console.log("Send cmd Get StepTimeSec...");
|
||
settingsCharacteristics.writeValue(new Uint8Array([0x24])).then(_ => {
|
||
console.log('Get StepTimeSec...');
|
||
});
|
||
} else {
|
||
devTime.step = 0;
|
||
addLog('Минимальный перод для расчета ухода часов 3 часа!');
|
||
}
|
||
|
||
} else {
|
||
devTime.step = 0;
|
||
addLog('Часы необходимо настроить заранее!');
|
||
}
|
||
}
|
||
}
|
||
} else if((blkId == 0x44) && (len > 8)) {
|
||
if(len != 9) {
|
||
devTrig.temp_min = value.getInt16(1, true);
|
||
devTrig.temp_max = value.getInt16(3, true);
|
||
devTrig.humi_min = value.getInt16(5, true);
|
||
devTrig.humi_max = value.getInt16(7, true);
|
||
if ( devSrv.services & SERVICE_SCREEN ) {
|
||
$("inpTempMin").value = (devTrig.temp_min/100.0).toFixed(2);
|
||
$("inpTempMax").value = (devTrig.temp_max/100.0).toFixed(2);
|
||
$("inpHumiMin").value = (devTrig.humi_min/100.0).toFixed(2);
|
||
$("inpHumiMax").value = (devTrig.humi_max/100.0).toFixed(2);
|
||
}
|
||
}
|
||
let x = 0;
|
||
if(len == 10) x = 1;
|
||
else if(len >= 18) x = 9;
|
||
if(x > 0) {
|
||
devTrig.temp_threshold = value.getInt16(x, true);
|
||
devTrig.humi_threshold = value.getInt16(x+2, true);
|
||
devTrig.temp_hysteresis = value.getInt16(x+4, true);
|
||
devTrig.humi_hysteresis = value.getInt16(x+6, true);
|
||
devTrig.flg = value.getUint8(x+8);
|
||
if( devSrv.services & SERVICE_TH_TRG ) { //
|
||
$("chkInvOut").checked = (devTrig.flg & 1) != 0;
|
||
$("inpTempThr").value = (devTrig.temp_threshold/100.0).toFixed(2);
|
||
$("inpHumiThr").value = (devTrig.humi_threshold/100.0).toFixed(2);
|
||
$("inpTempHsr").value = (devTrig.temp_hysteresis/100.0).toFixed(2);
|
||
$("inpHumiHsr").value = (devTrig.humi_hysteresis/100.0).toFixed(2);
|
||
}
|
||
}
|
||
addLog("Строка настроек триггера: "+ bytesToHex(value.buffer));
|
||
} else if(blkId == 0x20 && len >= 8) {
|
||
devcmf.tmp_lo = value.getInt16(1, true); // temp lo
|
||
devcmf.tmp_hi = value.getInt16(3, true); // temp hi
|
||
devcmf.hm_lo = value.getUint16(5, true); // humi lo
|
||
devcmf.hm_hi = value.getUint16(7, true); // humi lo
|
||
let s = 'Комфорт Температура: ' + (devcmf.tmp_lo/100.0).toFixed(2) + '..' + (devcmf.tmp_hi/100.0).toFixed(2) + '°C'
|
||
+ ', Влажность: ' + (devcmf.hm_lo/100.0).toFixed(2) + '..' + (devcmf.hm_hi/100.0).toFixed(2) + '%';
|
||
addLog(s); // UpdCmf();
|
||
} else if(blkId == 0x01 && len >= 1) {
|
||
devName = TextDecoder("utf-8").decode(value.buffer.slice(1));
|
||
$("inpDevName").value = devName;
|
||
addLog("Имя устройства: '" + devName + "'");
|
||
} else if(blkId == 0x10 && len > 6) {
|
||
let mac = new Uint8Array(6);
|
||
mac[0] = value.getUint8(6);
|
||
mac[1] = value.getUint8(5);
|
||
mac[2] = value.getUint8(4);
|
||
mac[3] = value.getUint8(3);
|
||
mac[4] = value.getUint8(2);
|
||
mac[5] = value.getUint8(1);
|
||
let mac_txt = bytesToHex(mac);
|
||
$("inpDevMAC").value = mac_txt;
|
||
addLog("MAC устройства: "+ mac_txt);
|
||
} else {
|
||
console.log('blk: ' + dump8(value, value.byteLength));
|
||
addLog('Ответ на команду (' + hex(blkId,2) + '): ' + bytesToHex(value.buffer.slice(1)));
|
||
}
|
||
}
|
||
function getMemo(num) {
|
||
if(cmdCharacteristic != null) {
|
||
//addLog("getSensCfg...");
|
||
isMemoActive = true;
|
||
memoCount = 0;
|
||
cmdCharacteristic.writeValue(new Uint8Array([0x35, num&0xff, (num>>8)&0xff])).catch(error => { console.log(error); addLog("getMemo() error!"); });
|
||
}
|
||
}
|
||
|
||
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 getDevTime() {
|
||
if(cmdCharacteristic != null) {
|
||
// addLog("Получить время от устройства...");
|
||
cmdCharacteristic.writeValue(new Uint8Array([0x23])).then(_ => { console.log('Send GetDevTime ok'); });
|
||
}
|
||
}
|
||
|
||
function getDevMAC() {
|
||
if(cmdCharacteristic != null) {
|
||
cmdCharacteristic.writeValue(new Uint8Array([0x10])).catch(error => { console.log(error); addLog("getMAC() error!"); });
|
||
}
|
||
}
|
||
|
||
function getTrgCfg() {
|
||
if(cmdCharacteristic != null) {
|
||
cmdCharacteristic.writeValue(new Uint8Array([0x44])).catch(error => { console.log(error); addLog("getTrgCfg() error!"); });
|
||
}
|
||
}
|
||
|
||
function getDevName() {
|
||
if(cmdCharacteristic != null) {
|
||
cmdCharacteristic.writeValue(new Uint8Array([0x01])).catch(error => { console.log(error); addLog("getDevName() error!"); });
|
||
}
|
||
}
|
||
|
||
function setDevCfg() {
|
||
if(cmdCharacteristic != null) {
|
||
// addLog("setDevCfg...");
|
||
devCfg.flg = parseInt($('inpFlag').value)&0xffffffff; // пока не реализовано
|
||
devCfg.tx_power = parseInt($('inpTxPwr').value)&0x3f; // 0..0x3f -> -20..+5 dBm нелинейное 0x1f = +0 дБм
|
||
devCfg.flg = ($('chkCfgNotify').checked) ? 1 : 0;
|
||
devCfg.flg |= ($('chkCfgClock').checked) ? 2 : 0;
|
||
devCfg.flg |= ($('chkCfgSmiley').checked) ? 4 : 0;
|
||
devCfg.flg |= ($('chkCfgTrg').checked) ? 8 : 0;
|
||
devCfg.tx_power = $('selTxPwr').value & 0x3f; // 0..0x1f -> -20..+5 dBm ? нелинейное 0x1f = +0 дБм
|
||
let connect_latency = parseInt($('inpLat').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($('inpAdvInt').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($('inpMeasInt').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($('inpAverInt').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($('inpBatInt').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.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...");
|
||
|
||
devSens.temp_k = parseInt($('inpTempK').value);
|
||
devSens.humi_k = parseInt($('inpHumK').value);
|
||
devSens.temp_z = parseInt($('inpTempZ').value);
|
||
devSens.humi_z = parseInt($('inpHumZ').value);
|
||
|
||
blk = new Uint8Array([0x55,
|
||
devSens.temp_k & 0xff, (devSens.temp_k >> 8) & 0xff, (devSens.temp_k >> 16) & 0xff, (devSens.temp_k >> 24) & 0xff,
|
||
devSens.humi_k & 0xff, (devSens.humi_k >> 8) & 0xff, (devSens.humi_k >> 16) & 0xff, (devSens.humi_k >> 24) & 0xff,
|
||
devSens.temp_z & 0xff, (devSens.temp_z >> 8) & 0xff,
|
||
devSens.humi_z & 0xff, (devSens.humi_z >> 8) & 0xff
|
||
]);
|
||
cmdCharacteristic.writeValue(new Uint8Array([0x25])).catch(error => { console.log(error); addLog("setSensCfg() error!"); });
|
||
}
|
||
}
|
||
|
||
function setDevTime() {
|
||
let time = Date.now()/1000;
|
||
time -= (new Date()).getTimezoneOffset() * 60;
|
||
blk = new Uint8Array(5);
|
||
blk[0] = 0x23;
|
||
blk[1] = time & 0xff;
|
||
blk[2] = (time >> 8) & 0xff;
|
||
blk[3] = (time >> 16) & 0xff;
|
||
blk[4] = (time >> 24) & 0xff;
|
||
addLog("Установка времени на устройстве (" + dump(blk, blk.length) + ")...");
|
||
cmdCharacteristic.writeValue(blk).then(_ => {
|
||
console.log('Время на устройстве синхронизировано');
|
||
});
|
||
}
|
||
|
||
function setDevMAC() {
|
||
if(cmdCharacteristic != null) {
|
||
let el = $("inpDevMAC").value;
|
||
let len = el.length;
|
||
if(len == 12) {
|
||
let mac = hexToBytes(el);
|
||
len = mac.length;
|
||
if(len == 6 || len == 8) {
|
||
let blk = new Uint8Array(len+2);
|
||
blk[0] = 0x10;
|
||
blk[1] = mac[5];
|
||
blk[2] = mac[4];
|
||
blk[3] = mac[3];
|
||
blk[4] = mac[2];
|
||
blk[5] = mac[1];
|
||
blk[6] = mac[0];
|
||
if(len == 8) {
|
||
blk[7] = mac[7];
|
||
blk[8] = mac[6];
|
||
}
|
||
console.log("Send cmd New MAC ("+dump(blk, blk.length)+")...");
|
||
cmdCharacteristic.writeValue(blk).then(_ => {
|
||
s = "Передача нового MAC: "+dump(mac, 6);
|
||
if(len == 8)
|
||
s += " RAND:" +dump(mac.slice(6), 2);
|
||
addLog(s+" ok");
|
||
});
|
||
return;
|
||
}
|
||
}
|
||
addLog("Строка MAC должна быть 6 байт в HEX виде!")
|
||
return;
|
||
}
|
||
}
|
||
|
||
function setTrgCfg() {
|
||
if(cmdCharacteristic != null) {
|
||
let len = 0;
|
||
if((devSrv.services & 0x00000020) != 0) len = 8;
|
||
if((devSrv.services & 0x00004000) != 0) len += 9;
|
||
if(len > 0) {
|
||
let blk = new Uint8Array(len + 1);
|
||
blk[0] = 0x44;
|
||
let idx = 1;
|
||
if(len != 9) {
|
||
devTrig.temp_min = Math.round($("inpTempMin").value * 100);
|
||
devTrig.temp_max = Math.round($("inpTempMax").value * 100);
|
||
devTrig.humi_min = Math.round($("inpHumiMin").value * 100);
|
||
devTrig.humi_max = Math.round($("inpHumiMax").value * 100);
|
||
blk[1] = devTrig.temp_min & 0xff;
|
||
blk[2] = (devTrig.temp_min >> 8) & 0xff;
|
||
blk[3] = devTrig.temp_max & 0xff;
|
||
blk[4] = (devTrig.temp_max >> 8) & 0xff;
|
||
blk[5] = devTrig.humi_min & 0xff;
|
||
blk[6] = (devTrig.humi_min >> 8) & 0xff;
|
||
blk[7] = devTrig.humi_max & 0xff;
|
||
blk[8] = (devTrig.humi_max >> 8) & 0xff;
|
||
idx = 9;
|
||
}
|
||
if(len > 8) {
|
||
devTrig.temp_threshold = Math.round($("inpTempThr").value * 100);
|
||
devTrig.humi_threshold = Math.round($("inpHumiThr").value * 100);
|
||
devTrig.temp_hysteresis = Math.round($("inpTempHsr").value * 100);
|
||
devTrig.humi_hysteresis = Math.round($("inpHumiHsr").value * 100);
|
||
devTrig.flg = ($("chkInvOut").checked)? 1: 0;
|
||
blk[idx+0] = devTrig.temp_threshold & 0xff;
|
||
blk[idx+1] = (devTrig.temp_threshold >> 8) & 0xff;
|
||
blk[idx+2] = devTrig.humi_threshold & 0xff;
|
||
blk[idx+3] = (devTrig.humi_threshold >> 8) & 0xff;
|
||
blk[idx+4] = devTrig.temp_hysteresis & 0xff;
|
||
blk[idx+5] = (devTrig.temp_hysteresis >> 8) & 0xff;
|
||
blk[idx+6] = devTrig.humi_hysteresis & 0xff;
|
||
blk[idx+7] = (devTrig.humi_hysteresis >> 8) & 0xff;
|
||
blk[idx+8] = devTrig.flg & 0xff;
|
||
}
|
||
cmdCharacteristic.writeValue(blk).catch(error => { console.log(error); addLog("setTrgCfg() error!"); });
|
||
}
|
||
}
|
||
}
|
||
|
||
function setDevName(flg) {
|
||
if(cmdCharacteristic != null) {
|
||
if(flg) {
|
||
let eltxt = $("inpDevName").value;
|
||
let len = eltxt.length;
|
||
if(len > 1 && len < 20) {
|
||
let blk = new Uint8Array(1 + len);
|
||
blk[0] = 0x01;
|
||
blk.set((new TextEncoder()).encode(eltxt), 1);
|
||
console.log(blk);
|
||
cmdCharacteristic.writeValue(blk).catch(error => { console.log(error); addLog("setDevName() error!"); });
|
||
} else
|
||
addLog("Имя устройства должно быть от 1 до 19 символов!");
|
||
} else
|
||
cmdCharacteristic.writeValue(new Uint8Array([0x01, 0])).catch(error => { console.log(error); addLog("setDevName() error!"); });
|
||
}
|
||
}
|
||
|
||
function resetDevCfg() {
|
||
if(cmdCharacteristic != null) {
|
||
addLog("Restore connection default settings...");
|
||
cmdCharacteristic.writeValue(new Uint8Array([0x56])).catch(error => { console.log(error); addLog("resetDevCfg() error!"); });
|
||
}
|
||
}
|
||
|
||
function resetSensCfg() {
|
||
if(cmdCharacteristic != null) {
|
||
addLog("Restore sensor default settings...");
|
||
cmdCharacteristic.writeValue(new Uint8Array([0x26])).catch(error => { console.log(error); addLog("resetSensCfg() error!"); });
|
||
}
|
||
}
|
||
|
||
function readFile(file) {
|
||
|
||
var reader = new FileReader();
|
||
|
||
if (file != null) {
|
||
reader.fname = file.name;
|
||
reader.readAsArrayBuffer(file);
|
||
} else {
|
||
$('lblFile').innerHTML = "не загружен";
|
||
showError("Файл не загружен");
|
||
}
|
||
|
||
reader.onload = function() {
|
||
getFwArray(this.result, this.fname);
|
||
if (ota.fwsize > 0) {
|
||
// regex = /[^\\]+$/;
|
||
// text = fname.match(regex);
|
||
$('lblFile').innerHTML = this.fname;
|
||
}
|
||
}
|
||
|
||
reader.onerror = function() {
|
||
console.log(this.error);
|
||
};
|
||
}
|
||
|
||
|
||
// var url;
|
||
function download(data, path, type) {
|
||
var file = new Blob([data], {type: type} );
|
||
if (window.navigator.msSaveOrOpenBlob) { // ie10+
|
||
window.navigator.msSaveOrOpenBlob(file, path);
|
||
} else { // ff, chrome
|
||
url = URL.createObjectURL(file);
|
||
let link = document.createElement("a");
|
||
link.href = path;
|
||
link.download = url;
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
setTimeout( function() {
|
||
document.body.removeChild(link);
|
||
window.URL.revokeObjectURL(url);
|
||
}, 0);
|
||
URL.revokeObjectURL(url);
|
||
}
|
||
}
|
||
/*
|
||
// BUG: Access-Control-Allow-Origin
|
||
function download(url, filename) {
|
||
fetch(url)
|
||
.then(response => response.blob())
|
||
.then(blob => {
|
||
const link = document.createElement("a");
|
||
link.href = URL.createObjectURL(blob);
|
||
link.download = filename;
|
||
link.click();
|
||
})
|
||
.catch(console.error);
|
||
}
|
||
download("https://get.geojs.io/v1/ip/geo.json","geoip.json")
|
||
*/
|
||
/*
|
||
var getFile = new selectFile;
|
||
getFile.targets('inpFile','lblFile');
|
||
|
||
function uploadFile() {
|
||
if ( isEmpty($("inpUrl").value) ) {
|
||
getFile.simulate()
|
||
} else {
|
||
let regex = /[^\\]+$/;
|
||
// match(regex);
|
||
//download(, $("inpUrl").value, "application/octet-stream");
|
||
download($("inpUrl").value, "OTA.bin");
|
||
// readFile(file);
|
||
}
|
||
}
|
||
*/
|
||
|
||
function readFlash() {
|
||
// if "OK"
|
||
$('btnSaveFlash').disabled = false;
|
||
}
|
||
|
||
var chart = null;
|
||
|
||
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);
|
||
}
|
||
if (tabName == 'tabCharts') {
|
||
if ((chartData.length > 0) && (chart != null))
|
||
chart.updateOptions({'file': chartData});
|
||
resizeChart();
|
||
isChartEnabled = true;
|
||
} else {
|
||
isChartEnabled = false;
|
||
}
|
||
}
|
||
|
||
function selectConfigTab() {
|
||
openTab(0, "tabConfig");
|
||
tablinks = document.getElementsByClassName("tablinks");
|
||
tablinks[0].className += " active";
|
||
}
|
||
|
||
// Буфер входящих данных
|
||
var chart = null;
|
||
var chartData = [];
|
||
var rslT = 1;
|
||
var rslH = 1;
|
||
var rangeT = [15, 25]; // y: { valueRange: [15, 25] },
|
||
var rangeH = [25, 85]; // y2: { valueRange: [25, 85] }
|
||
|
||
const arrayColumn = (arr, n) => arr.map(x => x[n]);
|
||
|
||
function addChartData(temp, humi) {
|
||
|
||
let dt = new Date();
|
||
chartData.push([dt, temp, humi]);
|
||
if(chartData.length >= 1000) chartData.shift();
|
||
|
||
//console.log(arrayColumn(a, 1));
|
||
//console.log(Math.min(...arrayColumn(a, 1)));
|
||
//console.log(Math.max(...arrayColumn(a, 1)));
|
||
let yMin = Math.min(...arrayColumn(chartData, 1));
|
||
let yMax = Math.max(...arrayColumn(chartData, 1));
|
||
let xMin = Math.floor(yMin / rslT);
|
||
let xMax = Math.floor(yMax / rslT) + 1;
|
||
rangeT = [xMin*rslT, xMax*rslT];
|
||
|
||
yMin = Math.min(...arrayColumn(chartData, 2));
|
||
yMax = Math.max(...arrayColumn(chartData, 2));
|
||
xMin = Math.floor(yMin / rslH);
|
||
xMax = Math.floor(yMax / rslH) + 1;
|
||
rangeH = [xMin*rslH, xMax*rslH];
|
||
//console.log(rangeT, rangeH);
|
||
|
||
/*********************************
|
||
if (chartData.length < 3) return;
|
||
if (chart == null) {
|
||
|
||
chart = new Dygraph(
|
||
$("divChart"),
|
||
"ny-vs-sf.txt",
|
||
{
|
||
rollPeriod: 14,
|
||
showRoller: true,
|
||
customBars: true,
|
||
title: 'NYC vs. SF',
|
||
ylabel: 'Temperature (F)',
|
||
legend: 'always'
|
||
}
|
||
);
|
||
*******************************/
|
||
/******************************
|
||
chart = new Dygraph(
|
||
$("divDygraph"),
|
||
"ny-vs-sf.txt", // chartData,
|
||
{
|
||
//title: "Live data",
|
||
//showRangeSelector: true,
|
||
//showRoller: true,
|
||
//rollPeriod: 1,
|
||
//xlabel: 'Time(sec)',
|
||
//ylabel: 'Temp(C°)',
|
||
//y2label: 'RH(%)',
|
||
colors: ['green', 'blue'],
|
||
series : { 'RH(%)': { axis: 'y2' } },
|
||
labels: ['T(сек)', 'T(°C)', 'RH(%)'],
|
||
//divLabels: $('divLabels'),
|
||
legend: 'always', // "follow"
|
||
digitsAfterDecimal: 3,
|
||
rollPeriod: 14,
|
||
showRoller: true,
|
||
customBars: true,
|
||
title: 'NYC vs. SF',
|
||
ylabel: 'Temperature (F)',
|
||
legend: 'always'
|
||
}
|
||
);
|
||
******************************/
|
||
//chart.updateOptions({'file': chartData})
|
||
}
|
||
|
||
function resizeChart() {
|
||
if (chart == null) return;
|
||
// console.log('resize однако');
|
||
var element = $('divChart');
|
||
var positionInfo = element.getBoundingClientRect();
|
||
// console.log(positionInfo);
|
||
// var width = positionInfo.width;
|
||
// var height = positionInfo.height;
|
||
var width = $('tabCharts').clientWidth;
|
||
var height = $('tabCharts').clientHeight;
|
||
// console.log(width, height);
|
||
if (height < (320 + 100))
|
||
height = 320;
|
||
else
|
||
height -= 100;
|
||
|
||
if (width < (480 + 40))
|
||
width = 480;
|
||
else
|
||
width -= 40;
|
||
chart.resize(width, height);
|
||
// console.log(width, height);
|
||
}
|
||
|
||
window.onload = function() {
|
||
|
||
document.querySelector("#inpFile").addEventListener("change", function() {
|
||
var file = this.files[0];
|
||
if (file != null) {
|
||
text = "не загружен";
|
||
readFile(file);
|
||
} else {
|
||
$('lblFile').innerHTML = "не выбран";
|
||
}
|
||
}, false);
|
||
|
||
showState("Не подключено");
|
||
|
||
disableControls(true);
|
||
selectConfigTab();
|
||
chart = new Dygraph(
|
||
$('divChart'),
|
||
[],
|
||
{
|
||
//title: "Live data",
|
||
//showRangeSelector: true,
|
||
//showRoller: true,
|
||
//rollPeriod: 1,
|
||
//xlabel: 'Time(sec)',
|
||
//ylabel: 'Temp(C°)',
|
||
//y2label: 'RH(%)',
|
||
/******************************/
|
||
axes: {
|
||
y: { valueRange: rangeT },
|
||
y2: { valueRange: rangeH }
|
||
},
|
||
/******************************/
|
||
colors: ['green', 'blue'],
|
||
series : { 'RH(%)': { axis: 'y2' } },
|
||
labels: ['T(сек)', 'T(°C)', 'RH(%)'],
|
||
//labelsDiv: $('labelsDiv'),
|
||
legend: 'always', // "follow"
|
||
digitsAfterDecimal: 2,
|
||
}
|
||
);
|
||
/******************************************
|
||
// debug
|
||
// let a = [[1, 21, 81], [2, 20, 79], [3, 19, 83]];
|
||
// addChartData(21, 81);
|
||
// addChartData(20, 79);
|
||
// addChartData(19, 83);
|
||
|
||
for (i = 0; i < 128; i++) {
|
||
let t = 20 + (1 - Math.random() * 2);
|
||
let h = 70 + (5 - Math.random() * 10);
|
||
chartData.push([i, t, h]);
|
||
//addChartData(t, h);
|
||
}
|
||
chart.updateOptions(
|
||
{
|
||
file: chartData,
|
||
axes: {
|
||
y: { 'valueRange': rangeT },
|
||
y2: { 'valueRange': rangeH }
|
||
}
|
||
}
|
||
);
|
||
// chart.updateOptions({'file': chartData});
|
||
******************************************/
|
||
addEventListener("resize", resizeChart);
|
||
};
|
||
|
||
</script>
|
||
<h2>PHY62x2 BTHome <a href="https://github.com/pvvx/THB2"><small>ⓘ</small></a></h2>
|
||
<label for="inpNamePrefix">Префикс имени устройств(а)</label>
|
||
<input type="text" id="inpNamePrefix" value="" placeholder="THB, BT, TH">
|
||
<br>
|
||
<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>
|
||
<hr id="hrSensorData" hidden>
|
||
<table id="tblSensorData" hidden>
|
||
<tr>
|
||
<th>Время</th>
|
||
<th>Температура</th>
|
||
<th>Влажность</th>
|
||
<th>Состояние батареи</th>
|
||
</tr>
|
||
<tr align="center">
|
||
<td><label id="lblTime">---/--/-- --:--:--</label></td>
|
||
<td><label id="lblTemp">-- °C</label></td>
|
||
<td><label id="lblHumi">-- %RH</label></td>
|
||
<td><label id="lblBat">-- % (-- V)</label></td>
|
||
<td><label id="lblTrg"></label></td>
|
||
</tr>
|
||
</table>
|
||
<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, 'tabCharts')">Chart</button>
|
||
<button class="tablinks" onclick="openTab(event, 'tabService')">Service</button>
|
||
</div>
|
||
|
||
<div id="tabConfig" class="tabcontent">
|
||
<table id="tblChkCfg">
|
||
<tr align="left">
|
||
<td><label><input type="checkbox" id="chkCfgClock"/>Показывать время</label></td>
|
||
<td><label><input type="checkbox" id="chkCfgSmiley"/>Отображать смайлики</label></td>
|
||
<td><label><input type="checkbox" id="chkCfgTrg"/>Отображать триггер</label></td>
|
||
</tr>
|
||
</table>
|
||
<table id="tblComfort">
|
||
<tr>
|
||
<td style="width:75px;"><b>Комфорт</b></td>
|
||
<td>Температура от <input size="4" type="text" id="inpTempMin" maxlength="8" title="Минимальная температура">
|
||
до <input size="4" type="text" id="inpTempMax" maxlength="8" title="Максимальная температура"> °C,</td>
|
||
<td>Влажность от <input size="4" type="text" id="inpHumiMin" maxlength="8" title="Минимальная влажность">
|
||
до <input size="4" type="text" id="inpHumiMax" maxlength="8" title="Максимальная влажность"> %RH</td>
|
||
</tr>
|
||
</table>
|
||
<table id="tblTrigger">
|
||
<tr>
|
||
<td style="width:75px;"><b>Триггер</b></td>
|
||
<td>Температура: <input size="4" type="text" id="inpTempThr" maxlength="8" title="Порог температуры"> °C, </td>
|
||
<td>Влажность: <input size="4" type="text" id="inpHumiThr" maxlength="8" title="Порог влажности"> %RH, </td>
|
||
<td>Гистерезис: <input size="4" type="text" id="inpTempHsr" maxlength="8" title="Гистерезис температуры, если =0 - обработка отключена, если отрицательный - выход включен при температуре менее заданной (нагреватель)"> °C</td>
|
||
<td><input size="4" type="text" id="inpHumiHsr" maxlength="8" title="Гистерезис влажности, если =0 - обработка отключена, если отрицательный - выход включен при влажности менее заданной (увлажнитель)"> %RH, </td>
|
||
<td><label><input type="checkbox" id="chkInvOut"/>Инверсия вывода</label></td>
|
||
</tr>
|
||
</table>
|
||
<button type="button"id="btnGetTrg" onclick="getTrgCfg()">Прочитать</button>
|
||
<button type="button"id="btnSetTrg" onclick="setTrgCfg()">Записать</button>
|
||
<hr id="hrPres">
|
||
<button type="button" id="btnGetDevTime" onclick="getDevTime()">Время устройства</button>
|
||
<button type="button" id="btnSetDevTime" onclick="setDevTime()">Установить время</button>
|
||
<hr>
|
||
Параметры связи
|
||
<table>
|
||
<tr>
|
||
<th>Send Notification</th>
|
||
<th>Tx Power</th>
|
||
<th>Conn. Latency</th>
|
||
</tr>
|
||
<tr align="center">
|
||
<td><label><input type="checkbox" id="chkCfgNotify"/>Измерения</label></td>
|
||
<td>
|
||
<select id="selTxPwr">
|
||
<option value="63">+5 дБм</option>
|
||
<option value="31" selected>+0 дБм</option>
|
||
<option value="23">-5 дБм</option>
|
||
<option value="15">-10 дБм</option>
|
||
<option value="7">-15 дБм</option>
|
||
<option value="0">-20 дБм</option>
|
||
</select>
|
||
</td>
|
||
<td><input size="4" type="text" id="inpLat" maxlength="4" title="Connect latency, Итоговый интервал = 30 * (Connect latency + 1) мс"></td>
|
||
</tr>
|
||
</table>
|
||
Интервалы
|
||
<table>
|
||
<tr>
|
||
<th>Реклама</th>
|
||
<th>Измерения</th>
|
||
<th>Усреднение</th>
|
||
<th>Уровень батареи</th>
|
||
</tr>
|
||
<tr align="center">
|
||
<td><!--- надо отдельно, на ходу, при изменении, выводить расчетное значение inpAdvInt*62.5 в ms --->
|
||
<input size="4" type="text" id="inpAdvInt" maxlength="4" title="Интервал BLE рекламы в мс, кратность 62.5 мс"></td>
|
||
<td><!--- надо отдельно выводить расчетное значение inpAdvInt*62.5*inpMeasInt в ms --->
|
||
<input size="4" type="text" id="inpMeasInt" maxlength="4" title="Опрос датчика в интервалах BLE рекламы, минимум 2 интервала рекламы"></td>
|
||
<td><!--- надо отдельно выводить расчетное значение inpAdvInt*62.5*inpMeasInt*inpAverInt в мс, сек --->
|
||
<input size="4" type="text" id="inpAverInt" maxlength="4" title="Запись истории: 0 - отключена, 1...255 * шаг опроса датчика = интервал записи истории"></td>
|
||
<td>
|
||
<input size="4" type="text" id="inpBatInt" maxlength="4" title="В секундах, обрабатывается кратно интервалу BLE рекламы, минимум 2 секунды"></td>
|
||
</tr>
|
||
</table>
|
||
<button type="button"id="btnGetDev" onclick="getDevCfg()">Прочитать</button>
|
||
<button type="button"id="btnSetDev" onclick="setDevCfg()">Записать</button>
|
||
<button type="button"id="btnRstDev" onclick="resetDevCfg()" title="Восстановить стандартные параметры связи">Восстановить</button>
|
||
<hr>
|
||
Параметры сенсора <b><label id="lblSensor">?</label></b>
|
||
<table>
|
||
<tr>
|
||
<th></th>
|
||
<th>Температура</th>
|
||
<th>Влажность</th>
|
||
</tr>
|
||
<tr>
|
||
<th></th>
|
||
</tr>
|
||
<tr>
|
||
<td>Поправочные коэффициенты</td>
|
||
<td align="center"><input size="8" type="text" id="inpTempK" maxlength="8" title="*0.01 = Коэффициент угла наклона (линейная функция) для расчета температуры"></td>
|
||
<td align="center"><input size="8" type="text" id="inpHumK" maxlength="8" title="*0.01 = Коэффициент угла наклона (линейная функция) для расчета влажности"></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Коррекция смещения</td>
|
||
<td><input size="8" type="text" id="inpTempZ" maxlength="8" title="*0.01C = Смещение нуля с поправочным значением для расчета температуры"></td>
|
||
<td><input size="8" type="text" id="inpHumZ" maxlength="8" title="*0.01% = Смещение нуля с поправочным значением для расчета влажности"></td>
|
||
</tr>
|
||
</table>
|
||
<br>
|
||
<button type="button"id="btnGetSens" onclick="getSensCfg()">Прочитать</button>
|
||
<button type="button"id="btnSetSens" onclick="setSensCfg()">Записать</button>
|
||
<button type="button"id="btnRstSens" onclick="resetSensCfg()" title="Восстановить стандартные параметры сенсора">Восстановить</button>
|
||
</div>
|
||
|
||
<div id="tabOTA" class="tabcontent">
|
||
<p>Файл прошивки:
|
||
<!--input type="file" hidden accept=".bin" id="inpFile"-->
|
||
<input type="file" hidden accept=".bin,application/octet-stream" id="inpFile">
|
||
<label id="lblFile">не выбран</label>
|
||
<button type="button" onClick="$('inpFile').click()">Выбрать</button>
|
||
<div hidden>URL: <input type="text" id="inpUrl"></div>
|
||
<p>
|
||
<hr>
|
||
<button type="button" id="btnStartDFU" onclick="startDFU()" disabled="true" >Старт</button>
|
||
</div>
|
||
|
||
<div id="tabCharts" class="tabcontent">
|
||
<div id="divChart"></div>
|
||
<!--div id="divLabels"></div-->
|
||
<p><a href="GraphMemo.html" target="_blank">Memo Chart</a> - График истории замеров</p>
|
||
</div>
|
||
|
||
<div id="tabService" class="tabcontent">
|
||
Чтение и запись памяти:<br>
|
||
<table>
|
||
<tr>
|
||
<td style="width:100px;">Адрес (hex):</td>
|
||
<td><button type="button" id="btnReadAddr" style="width:120px;" onclick="readAddress()">Прочитать</button></td>
|
||
<td>Данные (hex):</td>
|
||
</tr>
|
||
<tr>
|
||
<td><input size="8" type="text" id="inpAddr" value="11000000" maxlength="8"></td>
|
||
<td><button type="button" id="btnWriteAddr" onclick="writeAddress()" style="width:120px;">Записать</button></td>
|
||
<td><input size="32" type="text" id="inpData" value="?" maxlength="32"></td>
|
||
</tr>
|
||
</table>
|
||
<hr>
|
||
<table>
|
||
<tr>
|
||
<td style="width:100px;">Команда:</td>
|
||
<td><button type="button" id="btnSendCommand" onclick="sendCommand()" style="width:120px;">Выполнить</button></td>
|
||
<td><input type="text" id="inpCmdData" value="55" maxlength="16"></td>
|
||
</tr>
|
||
</table>
|
||
<hr>
|
||
<table>
|
||
<tr>
|
||
<td style="width:100px;">MAC Адрес:</td>
|
||
<td><button type="button" id="btnGetMAC" onclick="getDevMAC()">Прочитать</button></td>
|
||
<td><input type="text" id="inpDevMAC" value="?" maxlength="16"></td>
|
||
<td><button type="button" id="btnSetMAC" onclick="setDevMAC()">Записать</button></td>
|
||
</tr>
|
||
</table>
|
||
<hr>
|
||
<table>
|
||
<tr>
|
||
<td style="width:100px;">Имя устройства:</td>
|
||
<td><button type="button" id="btnGetName" onclick="getDevName()">Прочитать</button></td>
|
||
<td><input type="text" id="inpDevName" value="" maxlength="19"></td>
|
||
<td><button type="button" id="btnSetName" onclick="setDevName(true)">Записать</button></td>
|
||
<td><button type="button" id="btnRstName" onclick="setDevName(false)">По умолчанию</button></td>
|
||
</tr>
|
||
</table>
|
||
</div>
|
||
<hr>
|
||
<button type="button" onclick="clearLog()">Очистить лог</button>
|
||
<label id="lblStatus"></label>
|
||
<br>
|
||
<div id="log"></div>
|
||
</body>
|
||
</html>
|