-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathreadSerial.js
167 lines (153 loc) · 4.44 KB
/
readSerial.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// acquire weather data from 868 weather sensors using the IT+ protocol via a JeeLink and store it in CouchDB
// log once every five minutes
const timeInterval = 5000 * 60 * 1;
// expire unnamed sensors we haven't seen for an hour
const expireInterval = 60000 * 60 * 1;
// the radio sometimes picks up garbage, when falls a change out of range
const maxTempDistance = 5;
const maxHumidDistance = 5;
import { openDB } from "./lib/database.js";
const weatherdb = openDB();
import { SerialPort } from "serialport";
import { ReadlineParser } from "@serialport/parser-readline";
const path = "/dev/ttyUSB0";
const baudRate = 57600;
let sensors = {};
const settings = {};
function readSensorsFromDb(init) {
// console.log('before update:', JSON.stringify(sensors))
weatherdb.get("config/sensorIDs", (err, body) => {
if (!err) {
const nowTime = new Date().getTime();
if (settings._rev !== body._rev) {
// we got an update from the DB
sensors = body.sensorIDs;
settings._rev = body._rev;
console.log(settings, sensors);
} else {
// no new data in sensor table, check if we need to expire unknown sensors
for (const id in sensors) {
if (
typeof sensors[id].lastSeen !== "undefined" &&
nowTime - sensors[id].lastSeen > expireInterval
) {
sensors[id] = undefined;
}
}
}
settings.nextTime = nowTime + timeInterval;
// we got valid data back, start the show
if (init) {
startSerial();
}
}
// console.log('after update:', JSON.stringify(sensors))
});
}
// helper function to sum an Array
Array.prototype.sum = function () {
return this.reduce((a, b) => a + b);
};
// filter outliers from sensor data
function validData(data, item, maxDistance) {
// we need at least 3 values to determine an average
if (data.length < 3) {
return false;
}
let avg = data.sum() / data.length;
// define what an outlier is
function isNoOutlier(value) {
return Math.abs(value - avg) < maxDistance;
}
// filter any outliers from the dataset
const result = data.filter(isNoOutlier);
// check if the item was an outlier itself
avg = result.sum() / result.length;
if (!isNoOutlier(item)) {
return false;
}
// we got a valid item
return true;
}
// check both temp and humidity
function valuesOk(sensor, temp, humid) {
return (
validData(sensor.temps, temp, maxTempDistance) &&
validData(sensor.humids, humid, maxHumidDistance)
);
}
// store data in database
function saveData(record) {
console.log(record.date, record.msg);
weatherdb.insert(record, record.date, (err, body, header) => {
if (err) {
console.log("[weatherdb.insert] ", err.message);
return;
}
});
}
function idFromSensorId(sensorid) {
if (typeof sensors[sensorid] === "undefined") {
sensors[sensorid] = { name: sensorid };
}
const sensor = sensors[sensorid];
if (typeof sensor.nextTime === "undefined") {
// setup the sensor data structure
sensor.nextTime = 0;
sensor.temps = [];
sensor.humids = [];
sensor.lastSeen = 0;
}
return sensor.name;
}
// process the message received from the serialPort
function processMsg(msg) {
const now = new Date();
const nowTime = now.getTime();
const datestr = now.toJSON();
// console.log(datestr,msg)
// IT+ ID: F0 Temp: 14.8 Humidity: 84 RawData: 9F 05 48
const data = msg.split(" ");
if (data[0] === "IT+") {
const record = {
date: datestr,
id: idFromSensorId(data[2]),
sensorid: data[2],
temp: Number(data[4]),
humid: Number(data[6]),
batt: data[7] === "L" ? data[7] : "ok",
msg: msg,
};
const sensor = sensors[record.sensorid];
// retain the values in cache to look for outliers
sensor.temps.push(record.temp);
sensor.humids.push(record.humid);
sensor.lastSeen = nowTime;
// has the interval passed ?
if (nowTime >= sensor.nextTime) {
// save record if the last values made sense
if (valuesOk(sensor, record.temp, record.humid)) {
saveData(record);
// and update the interval
sensor.nextTime = nowTime + timeInterval;
// and reset the outlier cache
sensor.temps = [];
sensor.humids = [];
}
}
}
// try to update the sensor ID's in the same interval
if (nowTime >= settings.nextTime) {
readSensorsFromDb(false);
}
}
// start listening to the serialPort
function startSerial() {
const port = new SerialPort({ path, baudRate });
const parser = port.pipe(new ReadlineParser({ delimiter: "\r\n" }));
parser.on("data", processMsg);
}
export function startSerialReader() {
// this is where it all starts
readSensorsFromDb(true);
}