2021-12-11 04:26:38 +00:00
|
|
|
<!DOCTYPE HTML>
|
|
|
|
<html>
|
|
|
|
<head>
|
2021-12-26 21:22:30 +00:00
|
|
|
<script src="https://puck-js.com/puck.js"></script>
|
|
|
|
<script type="text/javascript">
|
2021-12-26 20:33:39 +00:00
|
|
|
|
2021-12-26 21:22:30 +00:00
|
|
|
// Don't forget to enable the Local API on your Awair before using this
|
|
|
|
// https://support.getawair.com/hc/en-us/articles/360049221014-Awair-Local-API-Feature
|
2021-12-26 20:33:39 +00:00
|
|
|
|
2021-12-26 21:22:30 +00:00
|
|
|
const awair_name_1 = "Awair";
|
2021-12-26 20:33:39 +00:00
|
|
|
|
2021-12-26 21:22:30 +00:00
|
|
|
var bt_connection;
|
|
|
|
var is_connected = false;
|
|
|
|
var reconnect_counter = 5;
|
|
|
|
var reconnect_attempt_counter = 1;
|
|
|
|
var is_chart_started = false;
|
2021-12-26 20:33:39 +00:00
|
|
|
|
2021-12-26 21:22:30 +00:00
|
|
|
function initChart() {
|
|
|
|
var chart_co2;
|
|
|
|
var chart_voc;
|
|
|
|
var chart_pm;
|
|
|
|
var chart_temperature;
|
|
|
|
var chart_humidity;
|
|
|
|
var dataPoints_1 = [];
|
|
|
|
var posx = 0;
|
2021-12-26 20:33:39 +00:00
|
|
|
|
2021-12-26 21:22:30 +00:00
|
|
|
var awair_ip_1 = document.getElementById('inputawairip').value;
|
|
|
|
|
|
|
|
$.getJSON("http://"+awair_ip_1+"/air-data/latest", function(data) {
|
|
|
|
$.each(data, function(key, value){
|
|
|
|
if (dataPoints_1[key] === undefined) { dataPoints_1[key] = []; }
|
|
|
|
if (key === "temp" || key === "humid") { dataPoints_1[key].push({x: posx, y: parseFloat(value)}); }
|
|
|
|
else { dataPoints_1[key].push({x: posx, y: parseInt(value)}); }
|
|
|
|
});
|
2021-12-26 20:33:39 +00:00
|
|
|
|
2021-12-26 21:22:30 +00:00
|
|
|
posx++;
|
|
|
|
|
|
|
|
chart_co2 = new CanvasJS.Chart("chartContainer_co2",{
|
|
|
|
title:{ text:"CO2", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
|
|
|
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
|
|
|
axisY:{ minimum: 0, labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
|
|
|
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
|
|
|
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.co2 }]
|
|
|
|
});
|
|
|
|
chart_voc = new CanvasJS.Chart("chartContainer_voc",{
|
|
|
|
title:{ text:"VOC", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
|
|
|
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
|
|
|
axisY:{ minimum: 0, labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
|
|
|
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
|
|
|
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.voc }]
|
|
|
|
});
|
|
|
|
chart_pm = new CanvasJS.Chart("chartContainer_pm",{
|
|
|
|
title:{ text:"PM", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
|
|
|
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
|
|
|
axisY:{ minimum: 0, labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
|
|
|
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
|
|
|
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.pm25 }]
|
|
|
|
});
|
|
|
|
chart_humidity = new CanvasJS.Chart("chartContainer_humidity",{
|
|
|
|
title:{ text:"Humidity", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
|
|
|
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
|
|
|
axisY:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
|
|
|
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
|
|
|
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.humid }]
|
|
|
|
});
|
|
|
|
chart_temperature = new CanvasJS.Chart("chartContainer_temperature",{
|
|
|
|
title:{ text:"Temperature", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
|
|
|
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
|
|
|
axisY:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
|
|
|
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
|
|
|
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.temp }]
|
|
|
|
});
|
2021-12-26 20:33:39 +00:00
|
|
|
|
2021-12-26 21:22:30 +00:00
|
|
|
chart_co2.set("backgroundColor", "#1A202C");
|
|
|
|
chart_voc.set("backgroundColor", "#1A202C");
|
|
|
|
chart_pm.set("backgroundColor", "#1A202C");
|
|
|
|
chart_humidity.set("backgroundColor", "#1A202C");
|
|
|
|
chart_temperature.set("backgroundColor", "#1A202C");
|
2021-12-26 20:33:39 +00:00
|
|
|
|
2021-12-26 21:22:30 +00:00
|
|
|
updateChart();
|
|
|
|
});
|
2021-12-26 20:33:39 +00:00
|
|
|
|
2021-12-26 21:22:30 +00:00
|
|
|
function updateChart() {
|
|
|
|
$.getJSON("http://"+awair_ip_1+"/air-data/latest", function(data) {
|
|
|
|
$.each(data, function(key, value){
|
|
|
|
if (dataPoints_1[key] === undefined) { dataPoints_1[key] = []; }
|
|
|
|
if (key === "temp" || key === "humid") { dataPoints_1[key].push({x: posx, y: parseFloat(value)}); }
|
|
|
|
else { dataPoints_1[key].push({x: posx, y: parseInt(value)}); }
|
|
|
|
});
|
2021-12-26 20:33:39 +00:00
|
|
|
|
2021-12-26 21:22:30 +00:00
|
|
|
posx++;
|
|
|
|
chart_co2.render();
|
|
|
|
chart_voc.render();
|
|
|
|
chart_pm.render();
|
|
|
|
chart_temperature.render();
|
|
|
|
chart_humidity.render();
|
|
|
|
|
|
|
|
chart_co2.title.set("text", "CO2 level (ppm)");
|
|
|
|
chart_voc.title.set("text", "VOC level (ppb)");
|
|
|
|
chart_pm.title.set("text", "PM2.5 level (ug/m³)");
|
|
|
|
chart_humidity.title.set("text", "Humidity level (%)");
|
|
|
|
chart_temperature.title.set("text", "Temperature level (°C)");
|
|
|
|
|
|
|
|
let current_co2 = dataPoints_1['co2'][dataPoints_1['co2'].length-1].y;
|
|
|
|
let current_voc = dataPoints_1['voc'][dataPoints_1['voc'].length-1].y;
|
|
|
|
let current_pm25 = dataPoints_1['pm25'][dataPoints_1['pm25'].length-1].y;
|
|
|
|
let current_humi = dataPoints_1['humid'][dataPoints_1['humid'].length-1].y;
|
|
|
|
let current_temp = dataPoints_1['temp'][dataPoints_1['temp'].length-1].y;
|
|
|
|
let last_update = dataPoints_1['temp'].length-1;
|
2021-12-26 20:33:39 +00:00
|
|
|
|
2021-12-26 21:22:30 +00:00
|
|
|
if (is_connected && bt_connection && bt_connection.isOpen) {
|
|
|
|
bt_connection.write('\x10bt_current_co2='+current_co2+';bt_current_voc='+current_voc+';bt_current_pm25='+current_pm25+';bt_current_humi='+current_humi+';bt_current_temp='+current_temp+';bt_last_update='+last_update+';\n');
|
2021-12-26 20:33:39 +00:00
|
|
|
|
2021-12-26 21:22:30 +00:00
|
|
|
console.log("Sent data through Bluetooth");
|
|
|
|
} else if (is_connected && bt_connection && !bt_connection.isOpen) {
|
|
|
|
console.log("Disconnected - Next attempt to reconnect in " + reconnect_counter);
|
|
|
|
reconnect_counter--;
|
2021-12-26 20:33:39 +00:00
|
|
|
|
2021-12-26 21:22:30 +00:00
|
|
|
if (reconnect_counter <= 0) {
|
|
|
|
reconnect_counter = 10 * reconnect_attempt_counter;
|
|
|
|
reconnect_attempt_counter++;
|
2021-12-26 20:33:39 +00:00
|
|
|
|
2021-12-26 21:22:30 +00:00
|
|
|
console.log("Trying to reconnect");
|
|
|
|
bt_connection.reconnect(function(c) {
|
|
|
|
console.log("Reconnect callback");
|
|
|
|
if (!c) {
|
|
|
|
console.log("Couldn't reconnect");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
bt_connection = c;
|
|
|
|
is_connected = true;
|
|
|
|
reconnect_attempt_counter = 1;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
setTimeout(function(){updateChart()}, 1000);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2021-12-26 20:33:39 +00:00
|
|
|
|
2021-12-26 21:22:30 +00:00
|
|
|
function connectBT() {
|
|
|
|
console.log("Connect BT");
|
|
|
|
Puck.connect(function(c) {
|
|
|
|
console.log("Connect callback");
|
|
|
|
if (!c) {
|
|
|
|
console.log("Couldn't connect");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
bt_connection = c;
|
|
|
|
is_connected = true;
|
|
|
|
reconnect_attempt_counter = 1;
|
|
|
|
if (!is_chart_started) {
|
|
|
|
initChart();
|
|
|
|
is_chart_started = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function disconnectBT() {
|
|
|
|
console.log("Disconnect Bluetooth button pressed. bt_connection value below.")
|
|
|
|
console.log(bt_connection);
|
|
|
|
if (is_connected && bt_connection) {
|
|
|
|
bt_connection.close();
|
|
|
|
is_connected = false;
|
|
|
|
console.log("Closed Bluetooth connection");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
</script>
|
|
|
|
<script type="text/javascript" src="https://canvasjs.com/assets/script/jquery-1.11.1.min.js"></script>
|
|
|
|
<script type="text/javascript" src="https://canvasjs.com/assets/script/canvasjs.min.js"></script>
|
|
|
|
</head>
|
|
|
|
<body style="background-color:#1A202C;">
|
|
|
|
|
|
|
|
<p style="color: #F7FAFC">
|
|
|
|
<b>How to use</b>
|
|
|
|
<br><br>
|
|
|
|
Step 1: Enable the Local API on your Awair: https://support.getawair.com/hc/en-us/articles/360049221014-Awair-Local-API-Feature
|
|
|
|
<br><br>
|
|
|
|
Step 2: Launch the Awair Monitor app on your BangleJS
|
|
|
|
<br><br>
|
|
|
|
Step 3: Input your Awair IP address and click the Connect button:
|
|
|
|
<input type="text" id="inputawairip" value="192.168.2.1">
|
|
|
|
<input type="button" value="Connect BangleJS" onclick="connectBT();">
|
|
|
|
<br><br>
|
|
|
|
Step 4: Optionally, open the web inspector's console (Right click > Inspector > Console) to read the Bluetooth logs
|
|
|
|
<br><br>
|
|
|
|
Step 5: Once you are done, click the Disconnect button to properly close the Blutooth connection
|
|
|
|
<center><button onclick="disconnectBT();">Disconnect BangleJS</button></center>
|
|
|
|
</p>
|
|
|
|
|
|
|
|
<br/><br/>
|
|
|
|
|
|
|
|
<div id="chartContainer_co2" style="height: 300px; max-width: 920px; margin: 0px auto; margin-bottom: 64px;"></div>
|
|
|
|
<div id="chartContainer_voc" style="height: 300px; max-width: 920px; margin: 0px auto; margin-bottom: 64px;"></div>
|
|
|
|
<div id="chartContainer_pm" style="height: 300px; max-width: 920px; margin: 0px auto; margin-bottom: 64px;"></div>
|
|
|
|
<div id="chartContainer_humidity" style="height: 300px; max-width: 920px; margin: 0px auto; margin-bottom: 64px;"></div>
|
|
|
|
<div id="chartContainer_temperature" style="height: 300px; max-width: 920px; margin: 0px auto; margin-bottom: 64px;"></div>
|
|
|
|
</body>
|
|
|
|
</html>(buf));
|
2021-12-26 20:33:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function str2ab(str) {
|
|
|
|
var buf = new ArrayBuffer(str.length);
|
|
|
|
var bufView = new Uint8Array(buf);
|
|
|
|
for (var i=0, strLen=str.length; i<strLen; i++) {
|
|
|
|
bufView[i] = str.charCodeAt(i);
|
|
|
|
}
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleQueue() {
|
|
|
|
if (!queue.length) return;
|
|
|
|
var q = queue.shift();
|
|
|
|
log(3,"Executing "+JSON.stringify(q)+" from queue");
|
|
|
|
if (q.type == "write") puck.write(q.data, q.callback, q.callbackNewline);
|
|
|
|
else log(1,"Unknown queue item "+JSON.stringify(q));
|
|
|
|
}
|
|
|
|
|
|
|
|
function connect(callback) {
|
|
|
|
if (!checkIfSupported()) return;
|
|
|
|
|
|
|
|
var connection = {
|
|
|
|
on : function(evt,cb) { this["on"+evt]=cb; },
|
|
|
|
emit : function(evt,data) { if (this["on"+evt]) this["on"+evt](data); },
|
|
|
|
isOpen : false,
|
|
|
|
isOpening : true,
|
|
|
|
txInProgress : false
|
|
|
|
};
|
|
|
|
var btServer = undefined;
|
|
|
|
var btService;
|
|
|
|
var connectionDisconnectCallback;
|
|
|
|
var txCharacteristic;
|
|
|
|
var rxCharacteristic;
|
|
|
|
var txDataQueue = [];
|
|
|
|
var flowControlXOFF = false;
|
|
|
|
var chunkSize = DEFAULT_CHUNKSIZE;
|
|
|
|
|
|
|
|
connection.close = function() {
|
|
|
|
connection.isOpening = false;
|
|
|
|
if (connection.isOpen) {
|
|
|
|
connection.isOpen = false;
|
|
|
|
connection.emit('close');
|
|
|
|
} else {
|
|
|
|
if (callback) callback(null);
|
|
|
|
}
|
|
|
|
if (btServer) {
|
|
|
|
btServer.disconnect();
|
|
|
|
btServer = undefined;
|
|
|
|
txCharacteristic = undefined;
|
|
|
|
rxCharacteristic = undefined;
|
|
|
|
chunkSize = DEFAULT_CHUNKSIZE;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
connection.write = function(data, callback) {
|
|
|
|
if (data) txDataQueue.push({data:data,callback:callback,maxLength:data.length});
|
|
|
|
if (connection.isOpen && !connection.txInProgress) writeChunk();
|
|
|
|
|
|
|
|
function writeChunk() {
|
|
|
|
if (flowControlXOFF) { // flow control - try again later
|
|
|
|
setTimeout(writeChunk, 50);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var chunk;
|
|
|
|
if (!txDataQueue.length) {
|
|
|
|
puck.writeProgress();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var txItem = txDataQueue[0];
|
|
|
|
puck.writeProgress(txItem.maxLength - txItem.data.length, txItem.maxLength);
|
|
|
|
if (txItem.data.length <= chunkSize) {
|
|
|
|
chunk = txItem.data;
|
|
|
|
txItem.data = undefined;
|
|
|
|
} else {
|
|
|
|
chunk = txItem.data.substr(0,chunkSize);
|
|
|
|
txItem.data = txItem.data.substr(chunkSize);
|
|
|
|
}
|
|
|
|
connection.txInProgress = true;
|
|
|
|
log(2, "Sending "+ JSON.stringify(chunk));
|
|
|
|
txCharacteristic.writeValue(str2ab(chunk)).then(function() {
|
|
|
|
log(3, "Sent");
|
|
|
|
if (!txItem.data) {
|
|
|
|
txDataQueue.shift(); // remove this element
|
|
|
|
if (txItem.callback)
|
|
|
|
txItem.callback();
|
|
|
|
}
|
|
|
|
connection.txInProgress = false;
|
|
|
|
writeChunk();
|
|
|
|
}).catch(function(error) {
|
|
|
|
log(1, 'SEND ERROR: ' + error);
|
|
|
|
txDataQueue = [];
|
|
|
|
connection.close();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
navigator.bluetooth.requestDevice({
|
|
|
|
filters:[
|
|
|
|
{ namePrefix: 'Puck.js' },
|
|
|
|
{ namePrefix: 'Pixl.js' },
|
|
|
|
{ namePrefix: 'MDBT42Q' },
|
|
|
|
{ namePrefix: 'RuuviTag' },
|
|
|
|
{ namePrefix: 'iTracker' },
|
|
|
|
{ namePrefix: 'Thingy' },
|
|
|
|
{ namePrefix: 'Espruino' },
|
|
|
|
{ services: [ NORDIC_SERVICE ] }
|
|
|
|
], optionalServices: [ NORDIC_SERVICE ]}).then(function(device) {
|
|
|
|
log(1, 'Device Name: ' + device.name);
|
|
|
|
log(1, 'Device ID: ' + device.id);
|
|
|
|
// Was deprecated: Should use getPrimaryServices for this in future
|
|
|
|
//log('BT> Device UUIDs: ' + device.uuids.join('\n' + ' '.repeat(21)));
|
|
|
|
device.addEventListener('gattserverdisconnected', function() {
|
|
|
|
log(1, "Disconnected (gattserverdisconnected)");
|
|
|
|
connection.close();
|
|
|
|
});
|
|
|
|
connection.device = device;
|
|
|
|
connection.reconnect(callback);
|
|
|
|
}).catch(function(error) {
|
|
|
|
log(1, 'ERROR: ' + error);
|
|
|
|
connection.close();
|
|
|
|
});
|
|
|
|
|
|
|
|
connection.reconnect = function(callback) {
|
|
|
|
connection.device.gatt.connect().then(function(server) {
|
|
|
|
log(1, "Connected");
|
|
|
|
btServer = server;
|
|
|
|
return server.getPrimaryService(NORDIC_SERVICE);
|
|
|
|
}).then(function(service) {
|
|
|
|
log(2, "Got service");
|
|
|
|
btService = service;
|
|
|
|
return btService.getCharacteristic(NORDIC_RX);
|
|
|
|
}).then(function (characteristic) {
|
|
|
|
rxCharacteristic = characteristic;
|
|
|
|
log(2, "RX characteristic:"+JSON.stringify(rxCharacteristic));
|
|
|
|
rxCharacteristic.addEventListener('characteristicvaluechanged', function(event) {
|
|
|
|
var dataview = event.target.value;
|
|
|
|
var data = ab2str(dataview.buffer);
|
|
|
|
if (data.length > chunkSize) {
|
|
|
|
log(2, "Received packet of length "+data.length+", increasing chunk size");
|
|
|
|
chunkSize = data.length;
|
|
|
|
}
|
|
|
|
if (puck.flowControl) {
|
|
|
|
for (var i=0;i<data.length;i++) {
|
|
|
|
var ch = data.charCodeAt(i);
|
|
|
|
var remove = true;
|
|
|
|
if (ch==19) {// XOFF
|
|
|
|
log(2,"XOFF received => pause upload");
|
|
|
|
flowControlXOFF = true;
|
|
|
|
} else if (ch==17) {// XON
|
|
|
|
log(2,"XON received => resume upload");
|
|
|
|
flowControlXOFF = false;
|
|
|
|
} else
|
|
|
|
remove = false;
|
|
|
|
if (remove) { // remove character
|
|
|
|
data = data.substr(0,i-1)+data.substr(i+1);
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
log(3, "Received "+JSON.stringify(data));
|
|
|
|
connection.emit('data', data);
|
|
|
|
});
|
|
|
|
return rxCharacteristic.startNotifications();
|
|
|
|
}).then(function() {
|
|
|
|
return btService.getCharacteristic(NORDIC_TX);
|
|
|
|
}).then(function (characteristic) {
|
|
|
|
txCharacteristic = characteristic;
|
|
|
|
log(2, "TX characteristic:"+JSON.stringify(txCharacteristic));
|
|
|
|
}).then(function() {
|
|
|
|
connection.txInProgress = false;
|
|
|
|
connection.isOpen = true;
|
|
|
|
connection.isOpening = false;
|
|
|
|
isBusy = false;
|
|
|
|
queue = [];
|
|
|
|
callback(connection);
|
|
|
|
connection.emit('open');
|
|
|
|
// if we had any writes queued, do them now
|
|
|
|
connection.write();
|
|
|
|
}).catch(function(error) {
|
|
|
|
log(1, 'ERROR: ' + error);
|
|
|
|
connection.close();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
return connection;
|
|
|
|
};
|
|
|
|
|
|
|
|
// ----------------------------------------------------------
|
|
|
|
var connection;
|
|
|
|
/* convenience function... Write data, call the callback with data:
|
|
|
|
callbackNewline = false => if no new data received for ~0.2 sec
|
|
|
|
callbackNewline = true => after a newline */
|
|
|
|
function write(data, callback, callbackNewline) {
|
|
|
|
if (!checkIfSupported()) return;
|
|
|
|
|
|
|
|
let result;
|
|
|
|
/// If there wasn't a callback function, then promisify
|
|
|
|
if (typeof callback !== 'function') {
|
|
|
|
callbackNewline = callback;
|
|
|
|
|
|
|
|
result = new Promise((resolve, reject) => callback = (value, err) => {
|
|
|
|
if (err) reject(err);
|
|
|
|
else resolve(value);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isBusy) {
|
|
|
|
log(3, "Busy - adding Puck.write to queue");
|
|
|
|
queue.push({type:"write", data:data, callback:callback, callbackNewline:callbackNewline});
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
var cbTimeout;
|
|
|
|
function onWritten() {
|
|
|
|
if (callbackNewline) {
|
|
|
|
connection.cb = function(d) {
|
|
|
|
var newLineIdx = connection.received.indexOf("\n");
|
|
|
|
if (newLineIdx>=0) {
|
|
|
|
var l = connection.received.substr(0,newLineIdx);
|
|
|
|
connection.received = connection.received.substr(newLineIdx+1);
|
|
|
|
connection.cb = undefined;
|
|
|
|
if (cbTimeout) clearTimeout(cbTimeout);
|
|
|
|
cbTimeout = undefined;
|
|
|
|
if (callback)
|
|
|
|
callback(l);
|
|
|
|
isBusy = false;
|
|
|
|
handleQueue();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
// wait for any received data if we have a callback...
|
|
|
|
var maxTime = 300; // 30 sec - Max time we wait in total, even if getting data
|
|
|
|
var dataWaitTime = callbackNewline ? 100/*10 sec if waiting for newline*/ : 3/*300ms*/;
|
|
|
|
var maxDataTime = dataWaitTime; // max time we wait after having received data
|
|
|
|
cbTimeout = setTimeout(function timeout() {
|
|
|
|
cbTimeout = undefined;
|
|
|
|
if (maxTime) maxTime--;
|
|
|
|
if (maxDataTime) maxDataTime--;
|
|
|
|
if (connection.hadData) maxDataTime=dataWaitTime;
|
|
|
|
if (maxDataTime && maxTime) {
|
|
|
|
cbTimeout = setTimeout(timeout, 100);
|
|
|
|
} else {
|
|
|
|
connection.cb = undefined;
|
|
|
|
if (callback)
|
|
|
|
callback(connection.received);
|
|
|
|
isBusy = false;
|
|
|
|
handleQueue();
|
|
|
|
connection.received = "";
|
|
|
|
}
|
|
|
|
connection.hadData = false;
|
|
|
|
}, 100);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (connection && (connection.isOpen || connection.isOpening)) {
|
|
|
|
if (!connection.txInProgress) connection.received = "";
|
|
|
|
isBusy = true;
|
|
|
|
connection.write(data, onWritten);
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
connection = connect(function(puck) {
|
|
|
|
if (!puck) {
|
|
|
|
connection = undefined;
|
|
|
|
if (callback) callback(null);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
connection.received = "";
|
|
|
|
connection.on('data', function(d) {
|
|
|
|
connection.received += d;
|
|
|
|
connection.hadData = true;
|
|
|
|
if (connection.cb) connection.cb(d);
|
|
|
|
});
|
|
|
|
connection.on('close', function(d) {
|
|
|
|
connection = undefined;
|
|
|
|
});
|
|
|
|
isBusy = true;
|
|
|
|
connection.write(data, onWritten);
|
|
|
|
});
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------
|
|
|
|
|
|
|
|
var puck = {
|
|
|
|
/// Are we writing debug information? 0 is no, 1 is some, 2 is more, 3 is all.
|
|
|
|
debug : 1,
|
|
|
|
/// Should we use flow control? Default is true
|
|
|
|
flowControl : true,
|
|
|
|
/// Used internally to write log information - you can replace this with your own function
|
|
|
|
log : function(level, s) { if (level <= this.debug) console.log("<BLE> "+s)},
|
|
|
|
/// Called with the current send progress or undefined when done - you can replace this with your own function
|
|
|
|
writeProgress : function (charsSent, charsTotal) {
|
|
|
|
//console.log(charsSent + "/" + charsTotal);
|
|
|
|
},
|
|
|
|
/** Connect to a new device - this creates a separate
|
|
|
|
connection to the one `write` and `eval` use. */
|
|
|
|
connect : connect,
|
|
|
|
/// Write to Puck.js and call back when the data is written. Creates a connection if it doesn't exist
|
|
|
|
write : write,
|
|
|
|
/// Evaluate an expression and call cb with the result. Creates a connection if it doesn't exist
|
|
|
|
eval : function(expr, cb) {
|
|
|
|
|
|
|
|
const response = write('\x10Bluetooth.println(JSON.stringify(' + expr + '))\n', true)
|
|
|
|
.then(function (d) {
|
|
|
|
try {
|
|
|
|
return JSON.parse(d);
|
|
|
|
} catch (e) {
|
|
|
|
log(1, "Unable to decode " + JSON.stringify(d) + ", got " + e.toString());
|
|
|
|
return Promise.reject(d);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (cb) {
|
|
|
|
return void response.then(cb, (err) => cb(null, err));
|
|
|
|
} else {
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
|
|
|
/// Write the current time to the Puck
|
|
|
|
setTime : function(cb) {
|
|
|
|
var d = new Date();
|
|
|
|
var cmd = 'setTime('+(d.getTime()/1000)+');';
|
|
|
|
// in 1v93 we have timezones too
|
|
|
|
cmd += 'if (E.setTimeZone) E.setTimeZone('+d.getTimezoneOffset()/-60+');\n';
|
|
|
|
write(cmd, cb);
|
|
|
|
},
|
|
|
|
/// Did `write` and `eval` manage to create a connection?
|
|
|
|
isConnected : function() {
|
|
|
|
return connection!==undefined;
|
|
|
|
},
|
|
|
|
/// get the connection used by `write` and `eval`
|
|
|
|
getConnection : function() {
|
|
|
|
return connection;
|
|
|
|
},
|
|
|
|
/// Close the connection used by `write` and `eval`
|
|
|
|
close : function() {
|
|
|
|
if (connection)
|
|
|
|
connection.close();
|
|
|
|
},
|
|
|
|
/** Utility function to fade out everything on the webpage and display
|
|
|
|
a window saying 'Click to continue'. When clicked it'll disappear and
|
|
|
|
'callback' will be called. This is useful because you can't initialise
|
|
|
|
Web Bluetooth unless you're doing so in response to a user input.*/
|
|
|
|
modal : function(callback) {
|
|
|
|
var e = document.createElement('div');
|
|
|
|
e.style = 'position:absolute;top:0px;left:0px;right:0px;bottom:0px;opacity:0.5;z-index:100;background:black;';
|
|
|
|
e.innerHTML = '<div style="position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);font-family: Sans-Serif;font-size:400%;color:white;">Click to Continue...</div>';
|
|
|
|
e.onclick = function(evt) {
|
|
|
|
callback();
|
|
|
|
evt.preventDefault();
|
|
|
|
document.body.removeChild(e);
|
|
|
|
};
|
|
|
|
document.body.appendChild(e);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
return puck;
|
|
|
|
}));
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
2021-12-11 04:26:38 +00:00
|
|
|
<script type="text/javascript">
|
|
|
|
|
|
|
|
// Don't forget to enable the Local API on your Awair before using this
|
|
|
|
// https://support.getawair.com/hc/en-us/articles/360049221014-Awair-Local-API-Feature
|
|
|
|
|
|
|
|
const awair_name_1 = "Awair";
|
|
|
|
|
|
|
|
var bt_connection;
|
|
|
|
var is_connected = false;
|
|
|
|
var reconnect_counter = 5;
|
|
|
|
var reconnect_attempt_counter = 1;
|
2021-12-26 20:33:39 +00:00
|
|
|
var is_chart_started = false;
|
2021-12-11 04:26:38 +00:00
|
|
|
|
2021-12-26 20:00:22 +00:00
|
|
|
function initChart() {
|
2021-12-11 04:26:38 +00:00
|
|
|
var chart_co2;
|
|
|
|
var chart_voc;
|
|
|
|
var chart_pm;
|
|
|
|
var chart_temperature;
|
|
|
|
var chart_humidity;
|
|
|
|
var dataPoints_1 = [];
|
|
|
|
var posx = 0;
|
2021-12-26 20:00:22 +00:00
|
|
|
|
|
|
|
var awair_ip_1 = document.getElementById('inputawairip').value;
|
2021-12-11 04:26:38 +00:00
|
|
|
|
|
|
|
$.getJSON("http://"+awair_ip_1+"/air-data/latest", function(data) {
|
|
|
|
$.each(data, function(key, value){
|
|
|
|
if (dataPoints_1[key] === undefined) { dataPoints_1[key] = []; }
|
|
|
|
if (key === "temp" || key === "humid") { dataPoints_1[key].push({x: posx, y: parseFloat(value)}); }
|
|
|
|
else { dataPoints_1[key].push({x: posx, y: parseInt(value)}); }
|
|
|
|
});
|
|
|
|
|
|
|
|
posx++;
|
|
|
|
|
|
|
|
chart_co2 = new CanvasJS.Chart("chartContainer_co2",{
|
|
|
|
title:{ text:"CO2", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
|
|
|
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
|
|
|
axisY:{ minimum: 0, labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
|
|
|
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
|
|
|
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.co2 }]
|
|
|
|
});
|
|
|
|
chart_voc = new CanvasJS.Chart("chartContainer_voc",{
|
|
|
|
title:{ text:"VOC", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
|
|
|
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
|
|
|
axisY:{ minimum: 0, labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
|
|
|
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
|
|
|
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.voc }]
|
|
|
|
});
|
|
|
|
chart_pm = new CanvasJS.Chart("chartContainer_pm",{
|
|
|
|
title:{ text:"PM", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
|
|
|
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
|
|
|
axisY:{ minimum: 0, labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
|
|
|
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
|
|
|
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.pm25 }]
|
|
|
|
});
|
|
|
|
chart_humidity = new CanvasJS.Chart("chartContainer_humidity",{
|
|
|
|
title:{ text:"Humidity", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
|
|
|
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
|
|
|
axisY:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
|
|
|
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
|
|
|
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.humid }]
|
|
|
|
});
|
|
|
|
chart_temperature = new CanvasJS.Chart("chartContainer_temperature",{
|
|
|
|
title:{ text:"Temperature", fontFamily: "helvetica", fontColor: "#F7FAFC", fontSize: 16, horizontalAlign: "left", padding: { left: 30 } },
|
|
|
|
axisX:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
|
|
|
axisY:{ labelFontColor: "#F7FAFC", gridColor: "#2D3748", lineColor: "#2D3748", tickColor: "#2D3748" },
|
|
|
|
legend: { fontColor: "#F7FAFC", horizontalAlign: "center", verticalAlign: "bottom" },
|
|
|
|
data: [ { type: "line", lineColor: "#6648FF", showInLegend: true, legendText: awair_name_1, dataPoints : dataPoints_1.temp }]
|
|
|
|
});
|
|
|
|
|
|
|
|
chart_co2.set("backgroundColor", "#1A202C");
|
|
|
|
chart_voc.set("backgroundColor", "#1A202C");
|
|
|
|
chart_pm.set("backgroundColor", "#1A202C");
|
|
|
|
chart_humidity.set("backgroundColor", "#1A202C");
|
|
|
|
chart_temperature.set("backgroundColor", "#1A202C");
|
|
|
|
|
|
|
|
updateChart();
|
|
|
|
});
|
|
|
|
|
|
|
|
function updateChart() {
|
|
|
|
$.getJSON("http://"+awair_ip_1+"/air-data/latest", function(data) {
|
|
|
|
$.each(data, function(key, value){
|
|
|
|
if (dataPoints_1[key] === undefined) { dataPoints_1[key] = []; }
|
|
|
|
if (key === "temp" || key === "humid") { dataPoints_1[key].push({x: posx, y: parseFloat(value)}); }
|
|
|
|
else { dataPoints_1[key].push({x: posx, y: parseInt(value)}); }
|
|
|
|
});
|
|
|
|
|
|
|
|
posx++;
|
|
|
|
chart_co2.render();
|
|
|
|
chart_voc.render();
|
|
|
|
chart_pm.render();
|
|
|
|
chart_temperature.render();
|
|
|
|
chart_humidity.render();
|
|
|
|
|
|
|
|
chart_co2.title.set("text", "CO2 level (ppm)");
|
|
|
|
chart_voc.title.set("text", "VOC level (ppb)");
|
|
|
|
chart_pm.title.set("text", "PM2.5 level (ug/m³)");
|
|
|
|
chart_humidity.title.set("text", "Humidity level (%)");
|
|
|
|
chart_temperature.title.set("text", "Temperature level (°C)");
|
|
|
|
|
|
|
|
let current_co2 = dataPoints_1['co2'][dataPoints_1['co2'].length-1].y;
|
|
|
|
let current_voc = dataPoints_1['voc'][dataPoints_1['voc'].length-1].y;
|
|
|
|
let current_pm25 = dataPoints_1['pm25'][dataPoints_1['pm25'].length-1].y;
|
|
|
|
let current_humi = dataPoints_1['humid'][dataPoints_1['humid'].length-1].y;
|
|
|
|
let current_temp = dataPoints_1['temp'][dataPoints_1['temp'].length-1].y;
|
|
|
|
let last_update = dataPoints_1['temp'].length-1;
|
2021-12-26 20:33:39 +00:00
|
|
|
|
2021-12-26 20:00:22 +00:00
|
|
|
if (is_connected && bt_connection && bt_connection.isOpen) {
|
2021-12-26 21:22:30 +00:00
|
|
|
bt_connection.write('\x10bt_current_co2='+current_co2+';bt_current_voc='+current_voc+';bt_current_pm25='+current_pm25+';bt_current_humi='+current_humi+';bt_current_temp='+current_temp+';bt_
|