Skip to content

Commit

Permalink
1.0.10
Browse files Browse the repository at this point in the history
  • Loading branch information
asednev committed Mar 11, 2021
1 parent 9c46b04 commit 530a556
Show file tree
Hide file tree
Showing 30 changed files with 486 additions and 3 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ out

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
Expand Down
31 changes: 31 additions & 0 deletions dist/decode.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export declare const decodeH5074Values: (streamUpdate: string) => {
battery: number;
humidity: number;
tempInC: number;
tempInF: number;
};
export declare const decodeH5075Values: (streamUpdate: string) => {
battery: number;
humidity: number;
tempInC: number;
tempInF: number;
};
export declare const decodeH5101Values: (streamUpdate: string) => {
battery: number;
humidity: number;
tempInC: number;
tempInF: number;
};
export declare const decodeH5179Values: (streamUpdate: string) => {
battery: number;
humidity: number;
tempInC: number;
tempInF: number;
};
export declare const decodeAny: (streamUpdate: string) => {
battery: number;
humidity: number;
tempInC: number;
tempInF: number;
};
//# sourceMappingURL=decode.d.ts.map
1 change: 1 addition & 0 deletions dist/decode.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

92 changes: 92 additions & 0 deletions dist/decode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.decodeAny = exports.decodeH5179Values = exports.decodeH5101Values = exports.decodeH5075Values = exports.decodeH5074Values = void 0;
const validation_1 = require("./validation");
exports.decodeH5074Values = (streamUpdate) => {
// inspired by https://github.com/Home-Is-Where-You-Hang-Your-Hack/sensor.goveetemp_bt_hci/blob/master/custom_components/govee_ble_hci/govee_advertisement.py#L116
const temp_lsb = streamUpdate
.substring(8, 10)
.concat(streamUpdate.substring(6, 8));
const hum_lsb = streamUpdate
.substring(12, 14)
.concat(streamUpdate.substring(10, 12));
const tempInC = twos_complement(parseInt(temp_lsb, 16)) / 100;
const tempInF = (tempInC * 9) / 5 + 32;
const humidity = parseInt(hum_lsb, 16) / 100;
const battery = parseInt(streamUpdate.substring(14, 16), 16);
return {
battery,
humidity,
tempInC,
tempInF,
};
};
exports.decodeH5075Values = (streamUpdate) => {
// TODO would be great to find a way to validate
const encodedData = parseInt(streamUpdate.substring(6, 12), 16);
const battery = parseInt(streamUpdate.substring(12, 14), 16);
const tempInC = encodedData / 10000;
const tempInF = (tempInC * 9) / 5 + 32;
const humidity = (encodedData % 1000) / 10;
return {
battery,
humidity,
tempInC,
tempInF,
};
};
exports.decodeH5101Values = (streamUpdate) => {
// TODO would be great to find a way to validate
const encodedData = parseInt(streamUpdate.substring(8, 14), 16);
const battery = parseInt(streamUpdate.substring(14, 16), 16);
const tempInC = encodedData / 10000;
const tempInF = (tempInC * 9) / 5 + 32;
const humidity = (encodedData % 1000) / 10;
return {
battery,
humidity,
tempInC,
tempInF,
};
};
exports.decodeH5179Values = (streamUpdate) => {
// TODO would be great to find a way to validate
const temp_lsb = streamUpdate
.substring(14, 16)
.concat(streamUpdate.substring(16, 18));
const hum_lsb = streamUpdate
.substring(18, 20)
.concat(streamUpdate.substring(16, 18));
const tempInC = twos_complement(parseInt(temp_lsb, 16)) / 100;
const tempInF = (tempInC * 9) / 5 + 32;
const humidity = parseInt(hum_lsb, 16) / 100;
const battery = parseInt(streamUpdate.substring(20, 22), 16);
return {
battery,
humidity,
tempInC,
tempInF,
};
};
exports.decodeAny = (streamUpdate) => {
if (validation_1.isHt5074(streamUpdate)) {
return exports.decodeH5074Values(streamUpdate);
}
if (validation_1.isHt5075(streamUpdate)) {
return exports.decodeH5075Values(streamUpdate);
}
if (validation_1.isHt5101(streamUpdate)) {
return exports.decodeH5101Values(streamUpdate);
}
if (validation_1.isHt5179(streamUpdate)) {
return exports.decodeH5179Values(streamUpdate);
}
throw new Error("Unsupported stream update: " + streamUpdate);
};
function twos_complement(n, w = 16) {
// Adapted from: https://stackoverflow.com/a/33716541.
if (n & (1 << (w - 1))) {
n = n - (1 << w);
}
return n;
}
2 changes: 2 additions & 0 deletions dist/decode.spec.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=decode.spec.d.ts.map
1 change: 1 addition & 0 deletions dist/decode.spec.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

89 changes: 89 additions & 0 deletions dist/decode.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const decode_1 = require("./decode");
const validationMatrix_1 = require("./validationMatrix");
it("should decode H5074 values", () => {
// sources: https://github.com/wcbonner/GoveeBTTempLogger/blob/master/goveebttemplogger.cpp#L206
// https://github.com/neilsheps/GoveeTemperatureAndHumidity
// 88EC00 0902 CD15 64 02 (Temp) 41.378°F (Humidity) 55.81% (Battery) 100%
// 5.21 C
const hex = "88EC000902CD156402";
const expectedReading = {
battery: 100,
humidity: 55.81,
tempInC: 5.21,
tempInF: 41.378,
};
const reading = decode_1.decodeH5074Values(hex);
expect(reading).toMatchObject(expectedReading);
});
it("should decode H5075 values", () => {
const hex = "88ec000368d15800";
const expectedReading = {
battery: 88,
humidity: 44.1,
tempInC: 22.3441,
tempInF: 72.21938,
};
const reading = decode_1.decodeH5075Values(hex);
expect(reading).toMatchObject(expectedReading);
});
it("should decode H5101 values", () => {
const hex = "0100010103165564";
const expectedReading = {
battery: 100,
humidity: 32.5,
tempInC: 20.2325,
tempInF: 68.41850000000001,
};
const reading = decode_1.decodeH5101Values(hex);
expect(reading).toMatchObject(expectedReading);
});
it("should decode H5179 values", () => {
const hex = "0188ec0001012e09740e64";
const expectedReading = {
battery: 100,
humidity: 37,
tempInC: 24.2,
tempInF: 75.56,
};
const reading = decode_1.decodeH5179Values(hex);
expect(reading).toMatchObject(expectedReading);
});
describe("test matrix", () => {
test("decode from any supported device", () => {
validationMatrix_1.validationMatrix.forEach((x) => {
const decodedResult = decode_1.decodeAny(x.mfgData);
expect(decodedResult).toMatchSnapshot();
});
});
describe("pass heuristics", () => {
let decodedResults = {};
beforeAll(() => {
validationMatrix_1.validationMatrix.forEach((x) => {
decodedResults[x.mfgData] = decode_1.decodeAny(x.mfgData);
});
});
test("battery between 0 and 100", () => {
validationMatrix_1.validationMatrix.forEach((x) => {
const battery = decodedResults[x.mfgData].battery;
expect(battery).toBeGreaterThanOrEqual(0);
expect(battery).toBeLessThanOrEqual(100);
});
});
test("humidity between 0 and 100", () => {
validationMatrix_1.validationMatrix.forEach((x) => {
const humidity = decodedResults[x.mfgData].humidity;
expect(humidity).toBeGreaterThanOrEqual(0);
expect(humidity).toBeLessThanOrEqual(100);
});
});
test("temperature between -20F and 140F", () => {
validationMatrix_1.validationMatrix.forEach((x) => {
const tempInF = decodedResults[x.mfgData].tempInF;
expect(tempInF).toBeGreaterThanOrEqual(-20);
expect(tempInF).toBeLessThanOrEqual(140);
});
});
});
});
2 changes: 2 additions & 0 deletions dist/example.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=example.d.ts.map
1 change: 1 addition & 0 deletions dist/example.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions dist/example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const index_1 = require("./index");
index_1.debug(true);
console.log("=== start discovery");
index_1.startDiscovery((reading) => {
console.log(reading);
});
setTimeout(async () => {
await index_1.stopDiscovery();
console.log("=== stop discovery");
}, 60000);
11 changes: 11 additions & 0 deletions dist/goveeReading.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export declare type GoveeReading = {
uuid: string;
address: string;
model: string;
tempInC: number;
tempInF: number;
humidity: number;
battery: number;
rssi: number;
};
//# sourceMappingURL=goveeReading.d.ts.map
1 change: 1 addition & 0 deletions dist/goveeReading.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions dist/goveeReading.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
8 changes: 8 additions & 0 deletions dist/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { GoveeReading } from "./goveeReading";
export declare const debug: (on: boolean) => void;
export declare const startDiscovery: (callback: (reading: GoveeReading) => void) => Promise<void>;
export declare const stopDiscovery: () => Promise<void>;
export declare const registerScanStart: (callback: Function) => void;
export declare const registerScanStop: (callback: Function) => void;
export * from "./goveeReading";
//# sourceMappingURL=index.d.ts.map
1 change: 1 addition & 0 deletions dist/index.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

97 changes: 97 additions & 0 deletions dist/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.registerScanStop = exports.registerScanStart = exports.stopDiscovery = exports.startDiscovery = exports.debug = void 0;
const noble_1 = __importDefault(require("@abandonware/noble"));
const validation_1 = require("./validation");
const decode_1 = require("./decode");
process.env.NOBLE_REPORT_ALL_HCI_EVENTS = "1"; // needed on Linux including Raspberry Pi
const h5075_uuid = "ec88";
const h5101_uuid = "0001";
let DEBUG = false;
let discoverCallback;
let scanStartCallback;
let scanStopCallback;
noble_1.default.on("discover", async (peripheral) => {
const { id, uuid, address, state, rssi, advertisement } = peripheral;
if (DEBUG) {
console.log("discovered", id, uuid, address, state, rssi);
}
if (!validation_1.isValidPeripheral(peripheral)) {
if (DEBUG) {
let mfgData;
if (advertisement.manufacturerData) {
mfgData = advertisement.manufacturerData.toString("hex");
}
console.log(`invalid peripheral, manufacturerData=[${mfgData}]`);
}
return;
}
const { localName, manufacturerData } = advertisement;
const streamUpdate = manufacturerData.toString("hex");
if (DEBUG) {
console.log(`${id}: ${streamUpdate}`);
}
const decodedValues = decode_1.decodeAny(streamUpdate);
const current = {
uuid,
address,
model: localName,
battery: decodedValues.battery,
humidity: decodedValues.humidity,
tempInC: decodedValues.tempInC,
tempInF: decodedValues.tempInF,
rssi,
};
if (discoverCallback) {
discoverCallback(current);
}
});
noble_1.default.on("scanStart", () => {
if (DEBUG) {
console.log("scanStart");
}
if (scanStartCallback) {
scanStartCallback();
}
});
noble_1.default.on("scanStop", () => {
if (DEBUG) {
console.log("scanStop");
}
if (scanStopCallback) {
scanStopCallback();
}
});
exports.debug = (on) => {
DEBUG = on;
};
exports.startDiscovery = async (callback) => {
discoverCallback = callback;
await noble_1.default.startScanningAsync([h5075_uuid, h5101_uuid], true);
};
exports.stopDiscovery = async () => {
await noble_1.default.stopScanningAsync();
discoverCallback = undefined;
scanStartCallback = undefined;
scanStopCallback = undefined;
};
exports.registerScanStart = (callback) => {
scanStartCallback = callback;
};
exports.registerScanStop = (callback) => {
scanStopCallback = callback;
};
__exportStar(require("./goveeReading"), exports);
7 changes: 7 additions & 0 deletions dist/streamReading.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export declare type StreamReading = {
battery: number;
humidity: number;
tempInC: number;
tempInF: number;
};
//# sourceMappingURL=streamReading.d.ts.map
1 change: 1 addition & 0 deletions dist/streamReading.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions dist/streamReading.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
Loading

0 comments on commit 530a556

Please sign in to comment.