homebridge-mi-hygrothermograph
Version:
Homebridge plugin for Xiaomi Mi Bluetooth Temperature and Humidity Sensor
1,413 lines (1,294 loc) • 58.3 kB
JavaScript
const assert = require("assert");
const proxyquire = require("proxyquire").noCallThru();
const sinon = require("sinon");
const { describe, it, beforeEach, afterEach } = require("mocha");
const {
CharacteristicMock,
ServiceMock,
FakeGatoHistoryServiceMock,
nobleMock,
mqttMock,
mockLogger,
} = require("./mocks");
const { Scanner } = proxyquire("../lib/scanner", {
"@stoprocent/noble": nobleMock,
});
describe("accessory", () => {
beforeEach(() => {
this.characteristics = {
BatteryLevel: new CharacteristicMock(),
StatusLowBattery: new CharacteristicMock(),
ChargingState: new CharacteristicMock(),
Manufacturer: new CharacteristicMock(),
FirmwareRevision: new CharacteristicMock(),
Model: new CharacteristicMock(),
SerialNumber: new CharacteristicMock(),
CurrentTemperature: new CharacteristicMock(),
CurrentRelativeHumidity: new CharacteristicMock(),
CurrentAmbientLightLevel: new CharacteristicMock(),
AirQuality: new CharacteristicMock(),
};
this.services = {
BatteryService: ServiceMock,
HumiditySensor: ServiceMock,
TemperatureSensor: ServiceMock,
LightSensor: ServiceMock,
AccessoryInformation: ServiceMock,
AirQualitySensor: ServiceMock,
};
this.homebridgeMock = {
hap: {
Service: this.services,
Characteristic: this.characteristics,
},
user: {
storagePath: () => "/tmp/",
},
};
const { HygrothermographAccessory, AccessoryType } = proxyquire(
"../lib/accessory",
{
"./scanner": {
Scanner,
},
"fakegato-history": () => FakeGatoHistoryServiceMock,
mqtt: mqttMock,
}
)(this.homebridgeMock);
this.AccessoryType = AccessoryType;
this.HygrothermographAccessory = HygrothermographAccessory;
});
afterEach(() => {
nobleMock.removeAllListeners();
sinon.restore();
});
it("should initialize without config param", () => {
const accessory = new this.HygrothermographAccessory(mockLogger);
assert(accessory.config);
});
it("should initialize Hygrothermograph type without config param", () => {
const accessory = new this.HygrothermographAccessory(mockLogger);
assert.strictEqual(accessory.type, this.AccessoryType.Hygrotermograph);
});
it("should throw on unknown accessory type", () => {
assert.throws(() => {
new this.HygrothermographAccessory(mockLogger, {
type: "foo-bar",
});
}, /Unsupported accessory type "foo-bar"/);
});
it("should update current temperature", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {});
const characteristic = this.characteristics.CurrentTemperature;
const updateValueSpy = sinon.spy(characteristic, "updateValue");
accessory.scanner.emit("temperatureChange", 20.5, {
address: "123",
id: "123",
});
assert.strictEqual(accessory.latestTemperature, 20.5);
accessory.scanner.emit("temperatureChange", 25.5, {
address: "123",
id: "123",
});
assert.strictEqual(accessory.latestTemperature, 25.5);
assert(updateValueSpy.called);
accessory.scanner.emit("temperatureChange", 25.5, {
id: "123",
});
});
it("should not update temperature characteristic when using update interval", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {
updateInterval: 60,
});
const characteristic = this.characteristics.CurrentTemperature;
const updateValueSpy = sinon.spy(characteristic, "updateValue");
accessory.scanner.emit("temperatureChange", 25.5, {
address: "123",
id: "123",
});
assert(updateValueSpy.called === false);
});
it("should update current humidity", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {});
const characteristic = this.characteristics.CurrentRelativeHumidity;
const updateValueSpy = sinon.spy(characteristic, "updateValue");
accessory.scanner.emit("humidityChange", 30.5, {
address: "123",
id: "123",
});
assert.strictEqual(accessory.latestHumidity, 30.5);
accessory.scanner.emit("humidityChange", 35.5, {
address: "123",
id: "123",
});
assert.strictEqual(accessory.latestHumidity, 35.5);
assert(updateValueSpy.called);
accessory.scanner.emit("humidityChange", 35.5, {
id: "123",
});
});
it("should update current fertility", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {
type: this.AccessoryType.MiFlora,
});
accessory.scanner.emit("fertilityChange", 500, {
address: "123",
id: "123",
});
assert.strictEqual(accessory.latestFertility, 500);
accessory.scanner.emit("fertilityChange", 600, {
address: "123",
id: "123",
});
assert.strictEqual(accessory.latestFertility, 600);
accessory.scanner.emit("fertilityChange", 600, {
id: "123",
});
});
it("should update current moisture", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {
type: this.AccessoryType.MiFlora,
});
const characteristic = this.characteristics.CurrentRelativeHumidity;
const updateValueSpy = sinon.spy(characteristic, "updateValue");
accessory.scanner.emit("moistureChange", 50.7, {
address: "123",
id: "123",
});
assert.strictEqual(accessory.latestMoisture, 50.7);
accessory.scanner.emit("moistureChange", 51.7, {
address: "123",
id: "123",
});
assert.strictEqual(accessory.latestMoisture, 51.7);
assert(updateValueSpy.called);
accessory.scanner.emit("moistureChange", 51.7, {
id: "123",
});
});
it("should update current illuminance", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {
type: this.AccessoryType.MiFlora,
});
const characteristic = this.characteristics.CurrentAmbientLightLevel;
const updateValueSpy = sinon.spy(characteristic, "updateValue");
accessory.scanner.emit("illuminanceChange", 3000, {
address: "123",
id: "123",
});
assert.strictEqual(accessory.latestIlluminance, 3000);
accessory.scanner.emit("illuminanceChange", 3500, {
address: "123",
id: "123",
});
assert.strictEqual(accessory.latestIlluminance, 3500);
assert(updateValueSpy.called);
accessory.scanner.emit("illuminanceChange", 3500, {
id: "123",
});
});
it("should not update current fertility on non-MiFlora", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {});
const characteristic = this.characteristics.CurrentRelativeHumidity;
const updateValueSpy = sinon.spy(characteristic, "updateValue");
accessory.scanner.emit("fertilityChange", 500, {
address: "123",
id: "123",
});
assert.strictEqual(accessory.latestFertility, undefined);
assert(!updateValueSpy.called);
});
it("should not update humidity characteristic when using update interval", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {
updateInterval: 60,
});
const characteristic = this.characteristics.CurrentRelativeHumidity;
const updateValueSpy = sinon.spy(characteristic, "updateValue");
accessory.scanner.emit("humidityChange", 25.0, {
address: "123",
id: "123",
});
assert(updateValueSpy.called === false);
});
it("should update current battery level", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {});
const characteristic = this.characteristics.BatteryLevel;
const updateValueSpy = sinon.spy(characteristic, "updateValue");
accessory.scanner.emit("batteryChange", 90, { address: "123", id: "123" });
assert.strictEqual(accessory.latestBatteryLevel, 90);
accessory.scanner.emit("batteryChange", 9, { address: "123", id: "123" });
assert.strictEqual(accessory.latestBatteryLevel, 9);
assert(updateValueSpy.called);
accessory.scanner.emit("batteryChange", 9, { id: "123" });
});
it("should not update battery characteristic when using update interval", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {
updateInterval: 60,
});
const characteristic = this.characteristics.BatteryLevel;
const updateValueSpy = sinon.spy(characteristic, "updateValue");
accessory.scanner.emit("batteryChange", 90, { address: "123", id: "123" });
assert(updateValueSpy.called === false);
});
it("should receive error", () => {
const spyLogger = sinon.spy(mockLogger, "error");
const accessory = new this.HygrothermographAccessory(mockLogger, {});
accessory.scanner.emit("error", new Error("error"));
assert(spyLogger.called);
});
it("should answer temperature characteristic get value", () => {
const temperatureSpy = sinon.spy();
const accessory = new this.HygrothermographAccessory(mockLogger, {});
const characteristic = this.characteristics.CurrentTemperature;
accessory.setTemperature(23);
characteristic.emit("get", temperatureSpy);
assert(temperatureSpy.calledWith(null, 23));
});
it("should error on undefined temperature characteristic get value ", () => {
const temperatureSpy = sinon.spy();
const accessory = new this.HygrothermographAccessory(mockLogger, {});
const characteristic = this.characteristics.CurrentTemperature;
assert.strictEqual(accessory.temperature, undefined);
characteristic.emit("get", temperatureSpy);
assert(temperatureSpy.calledWith(sinon.match.instanceOf(Error)));
});
it("should error on timeout temperature characteristic get value", () => {
const temperatureSpy = sinon.spy();
const accessory = new this.HygrothermographAccessory(mockLogger, {});
const characteristic = this.characteristics.CurrentTemperature;
assert.strictEqual(accessory.lastUpdatedAt, undefined);
accessory.setTemperature(25);
assert.notStrictEqual(accessory.lastUpdatedAt, undefined);
sinon.useFakeTimers(Date.now() + 1000 * 60 * accessory.timeout);
characteristic.emit("get", temperatureSpy);
assert(temperatureSpy.calledWith(sinon.match.instanceOf(Error)));
});
it("should answer humidity characteristic get value", () => {
const humiditySpy = sinon.spy();
const accessory = new this.HygrothermographAccessory(mockLogger, {});
const characteristic = this.characteristics.CurrentRelativeHumidity;
accessory.setHumidity(30);
characteristic.emit("get", humiditySpy);
assert(humiditySpy.calledWith(null, 30));
});
it("should handle setHumidity for MiFlora devices", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {
type: this.AccessoryType.MiFlora,
});
accessory.setHumidity(30);
assert.strictEqual(accessory.latestHumidity, undefined);
});
it("should error on undefined humidity characteristic get value ", () => {
const humiditySpy = sinon.spy();
const accessory = new this.HygrothermographAccessory(mockLogger, {});
const characteristic = this.characteristics.CurrentRelativeHumidity;
assert.strictEqual(accessory.humidity, undefined);
characteristic.emit("get", humiditySpy);
assert(humiditySpy.calledWith(sinon.match.instanceOf(Error)));
});
it("should error on timeout humidity characteristic get value", () => {
const humiditySpy = sinon.spy();
const accessory = new this.HygrothermographAccessory(mockLogger, {});
const characteristic = this.characteristics.CurrentRelativeHumidity;
assert.strictEqual(accessory.lastUpdatedAt, undefined);
accessory.setHumidity(30);
assert.notStrictEqual(accessory.lastUpdatedAt, undefined);
sinon.useFakeTimers(Date.now() + 1000 * 60 * accessory.timeout);
characteristic.emit("get", humiditySpy);
assert(humiditySpy.calledWith(sinon.match.instanceOf(Error)));
});
it("should answer low battery characteristic get value", () => {
const lowBatterySpy = sinon.spy();
const accessory = new this.HygrothermographAccessory(mockLogger);
const characteristic = this.characteristics.StatusLowBattery;
// Low battery
accessory.setBatteryLevel(9);
characteristic.emit("get", lowBatterySpy);
assert(lowBatterySpy.calledWith(null, characteristic.BATTERY_LEVEL_LOW));
// Normal battery
accessory.setBatteryLevel(15);
characteristic.emit("get", lowBatterySpy);
assert(lowBatterySpy.calledWith(null, characteristic.BATTERY_LEVEL_NORMAL));
});
it("should answer used configured low battery threshold", () => {
const lowBatterySpy = sinon.spy();
const accessory = new this.HygrothermographAccessory(mockLogger, {
lowBattery: 20,
});
const characteristic = this.characteristics.StatusLowBattery;
// Low battery
accessory.setBatteryLevel(9);
characteristic.emit("get", lowBatterySpy);
assert(lowBatterySpy.calledWith(null, characteristic.BATTERY_LEVEL_LOW));
accessory.setBatteryLevel(15);
characteristic.emit("get", lowBatterySpy);
assert(lowBatterySpy.calledWith(null, characteristic.BATTERY_LEVEL_LOW));
// Normal battery
accessory.setBatteryLevel(21);
characteristic.emit("get", lowBatterySpy);
assert(lowBatterySpy.calledWith(null, characteristic.BATTERY_LEVEL_NORMAL));
});
it("should error on undefined low battery characteristic get value", () => {
const lowBatterySpy = sinon.spy();
const accessory = new this.HygrothermographAccessory(mockLogger, {});
const characteristic = this.characteristics.StatusLowBattery;
assert.strictEqual(accessory.batteryLevel, undefined);
characteristic.emit("get", lowBatterySpy);
assert(lowBatterySpy.calledWith(sinon.match.instanceOf(Error)));
});
it("should answer battery level characteristic get value", () => {
const batterySpy = sinon.spy();
const accessory = new this.HygrothermographAccessory(mockLogger, {});
const characteristic = this.characteristics.BatteryLevel;
accessory.setBatteryLevel(99);
characteristic.emit("get", batterySpy);
assert(batterySpy.calledWith(null, 99));
});
it("should error on undefined battery level characteristic get value ", () => {
const batterySpy = sinon.spy();
const accessory = new this.HygrothermographAccessory(mockLogger, {});
const characteristic = this.characteristics.BatteryLevel;
assert.strictEqual(accessory.batteryLevel, undefined);
characteristic.emit("get", batterySpy);
assert(batterySpy.calledWith(sinon.match.instanceOf(Error)));
});
it("should error on timeout battery level characteristic get value", () => {
const batterySpy = sinon.spy();
const accessory = new this.HygrothermographAccessory(mockLogger, {});
const characteristic = this.characteristics.BatteryLevel;
assert.strictEqual(accessory.lastUpdatedAt, undefined);
accessory.setBatteryLevel(99);
assert.notStrictEqual(accessory.lastUpdatedAt, undefined);
sinon.useFakeTimers(Date.now() + 1000 * 60 * accessory.timeout);
characteristic.emit("get", batterySpy);
assert(batterySpy.calledWith(sinon.match.instanceOf(Error)));
});
it("should answer moisture characteristic get value", () => {
const moistureSpy = sinon.spy();
const accessory = new this.HygrothermographAccessory(mockLogger, {
type: this.AccessoryType.MiFlora,
});
const characteristic = this.characteristics.CurrentRelativeHumidity;
accessory.setMoisture(23.5);
characteristic.emit("get", moistureSpy);
assert(moistureSpy.calledWith(null, 23.5));
});
it("should handle setMoisture for non MiFlora devices", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {});
accessory.setMoisture(23.5);
assert.strictEqual(accessory.latestMoisture, undefined);
});
it("should error on undefined moisture characteristic get value ", () => {
const moistureSpy = sinon.spy();
const accessory = new this.HygrothermographAccessory(mockLogger, {
type: this.AccessoryType.MiFlora,
});
const characteristic = this.characteristics.CurrentRelativeHumidity;
assert.strictEqual(accessory.moisture, undefined);
characteristic.emit("get", moistureSpy);
assert(moistureSpy.calledWith(sinon.match.instanceOf(Error)));
});
it("should error on timeout moisture characteristic get value", () => {
const moistureSpy = sinon.spy();
const accessory = new this.HygrothermographAccessory(mockLogger, {
type: this.AccessoryType.MiFlora,
});
const characteristic = this.characteristics.CurrentRelativeHumidity;
assert.strictEqual(accessory.lastUpdatedAt, undefined);
accessory.setMoisture(23.5);
assert.notStrictEqual(accessory.lastUpdatedAt, undefined);
sinon.useFakeTimers(Date.now() + 1000 * 60 * accessory.timeout);
characteristic.emit("get", moistureSpy);
assert(moistureSpy.calledWith(sinon.match.instanceOf(Error)));
});
it("should answer fertility characteristic get value for MiFlora", () => {
const fertilitySpy = sinon.spy();
const accessory = new this.HygrothermographAccessory(mockLogger, {
type: this.AccessoryType.MiFlora,
});
const characteristic = this.characteristics.CurrentAmbientLightLevel;
accessory.setFertility(500);
characteristic.emit("get", fertilitySpy);
assert(fertilitySpy.calledWith(null, 500));
});
it("should handle setFertility for non MiFlora devices", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {});
accessory.setFertility(500);
assert.strictEqual(accessory.latestFertility, undefined);
});
it("should error on undefined fertility characteristic get value ", () => {
const fertilitySpy = sinon.spy();
const accessory = new this.HygrothermographAccessory(mockLogger, {
type: this.AccessoryType.MiFlora,
});
const characteristic = this.characteristics.CurrentAmbientLightLevel;
assert.strictEqual(accessory.fertility, undefined);
characteristic.emit("get", fertilitySpy);
assert(fertilitySpy.calledWith(sinon.match.instanceOf(Error)));
});
it("should error on timeout fertility characteristic get value", () => {
const fertilitySpy = sinon.spy();
const accessory = new this.HygrothermographAccessory(mockLogger, {
type: this.AccessoryType.MiFlora,
});
const characteristic = this.characteristics.CurrentAmbientLightLevel;
assert.strictEqual(accessory.lastUpdatedAt, undefined);
accessory.setFertility(500);
assert.notStrictEqual(accessory.lastUpdatedAt, undefined);
sinon.useFakeTimers(Date.now() + 1000 * 60 * accessory.timeout);
characteristic.emit("get", fertilitySpy);
assert(fertilitySpy.calledWith(sinon.match.instanceOf(Error)));
});
it("should answer illuminance characteristic get value for MiFlora", () => {
const illuminanceSpy = sinon.spy();
const accessory = new this.HygrothermographAccessory(mockLogger, {
type: this.AccessoryType.MiFlora,
});
const characteristic = this.characteristics.CurrentAmbientLightLevel;
accessory.setIlluminance(5000);
characteristic.emit("get", illuminanceSpy);
assert(illuminanceSpy.calledWith(null, 5000));
});
it("should handle setIlluminance for non MiFlora devices", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {});
accessory.setIlluminance(5000);
assert.strictEqual(accessory.latestIlluminance, undefined);
});
it("should answer min bound illuminance characteristic get value for MiFlora", () => {
const illuminanceSpy = sinon.spy();
const accessory = new this.HygrothermographAccessory(mockLogger, {
type: this.AccessoryType.MiFlora,
});
const characteristic = this.characteristics.CurrentAmbientLightLevel;
accessory.setIlluminance(0);
characteristic.emit("get", illuminanceSpy);
assert(illuminanceSpy.calledWith(null, 0.0001));
});
it("should answer max bound illuminance characteristic get value for MiFlora", () => {
const illuminanceSpy = sinon.spy();
const accessory = new this.HygrothermographAccessory(mockLogger, {
type: this.AccessoryType.MiFlora,
});
const characteristic = this.characteristics.CurrentAmbientLightLevel;
accessory.setIlluminance(99999999);
characteristic.emit("get", illuminanceSpy);
assert(illuminanceSpy.calledWith(null, 100000));
});
it("should error on undefined illuminance characteristic get value ", () => {
const illuminanceSpy = sinon.spy();
const accessory = new this.HygrothermographAccessory(mockLogger, {
type: this.AccessoryType.MiFlora,
});
const characteristic = this.characteristics.CurrentAmbientLightLevel;
assert.strictEqual(accessory.illuminance, undefined);
characteristic.emit("get", illuminanceSpy);
assert(illuminanceSpy.calledWith(sinon.match.instanceOf(Error)));
});
it("should error on timeout illuminance characteristic get value", () => {
const illuminanceSpy = sinon.spy();
const accessory = new this.HygrothermographAccessory(mockLogger, {
type: this.AccessoryType.MiFlora,
});
const characteristic = this.characteristics.CurrentAmbientLightLevel;
assert.strictEqual(accessory.lastUpdatedAt, undefined);
accessory.setIlluminance(5000);
assert.notStrictEqual(accessory.lastUpdatedAt, undefined);
sinon.useFakeTimers(Date.now() + 1000 * 60 * accessory.timeout);
characteristic.emit("get", illuminanceSpy);
assert(illuminanceSpy.calledWith(sinon.match.instanceOf(Error)));
});
it("should return all services for Hygrothermograph", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {});
const services = accessory.getServices();
assert.strictEqual(services.length, 4);
});
it("should return all services for MiFlora", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {
type: this.AccessoryType.MiFlora,
});
const services = accessory.getServices();
assert.strictEqual(services.length, 6);
});
it("should set address config", () => {
const config = { address: "deadbeef" };
const accessory = new this.HygrothermographAccessory(mockLogger, config);
assert.deepStrictEqual(config, accessory.config);
});
it("should set timeout config", () => {
const config = { timeout: 25 };
const accessory = new this.HygrothermographAccessory(mockLogger, config);
assert.deepStrictEqual(config, accessory.config);
assert.strictEqual(config.timeout, accessory.timeout);
});
it("should return timed out true when timed out", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {});
accessory.lastUpdatedAt = Date.now();
sinon.useFakeTimers(Date.now() + 1000 * 60 * (accessory.timeout + 15));
assert(accessory.hasTimedOut());
});
it("should return timed out false when not timed out", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {});
accessory.lastUpdatedAt = Date.now();
sinon.useFakeTimers(Date.now() + 1000 * 60 * (accessory.timeout - 5));
assert(!accessory.hasTimedOut());
});
it("should return timed out false when set as 0", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {
timeout: 0,
});
accessory.lastUpdatedAt = Date.now();
sinon.useFakeTimers(Date.now() + 1000 * 60 * (accessory.timeout + 15));
assert(!accessory.hasTimedOut());
});
it("should return timed out false with undefined timestamp", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {});
sinon.useFakeTimers(Date.now() + 1000 * 60 * (accessory.timeout + 15));
assert(!accessory.hasTimedOut());
});
it("should have custom temperature name when configured", () => {
const temperatureName = "CustomTemperatureName";
const accessory = new this.HygrothermographAccessory(mockLogger, {
temperatureName,
});
assert.strictEqual(accessory.temperatureName, temperatureName);
});
it("should have default temperature name when not configured", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {});
assert.strictEqual(accessory.temperatureName, "Temperature");
});
it("should have custom humidity name when configured", () => {
const humidityName = "CustomHumidityName";
const accessory = new this.HygrothermographAccessory(mockLogger, {
humidityName,
});
assert.strictEqual(accessory.humidityName, humidityName);
});
it("should have default humidity name when not configured", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {});
assert.strictEqual(accessory.humidityName, "Humidity");
});
it("should get serial number from configured address", () => {
const address = "de:ad:be:ef";
const accessory = new this.HygrothermographAccessory(mockLogger, {
address,
});
assert.strictEqual(accessory.serialNumber, "deadbeef");
});
it("should have undefined serial number of no configured address", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {});
assert.strictEqual(accessory.serialNumber, undefined);
});
it("should setup fakeGatoHistoryService when configured", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {
fakeGatoEnabled: true,
});
assert(accessory.fakeGatoHistoryService !== undefined);
assert.strictEqual(accessory.fakeGatoHistoryService.accessoryType, "room");
assert.strictEqual(accessory.fakeGatoHistoryService.accessory, accessory);
assert.strictEqual(
accessory.fakeGatoHistoryService.optionalParams.path,
"/tmp/"
);
assert.strictEqual(
accessory.fakeGatoHistoryService.optionalParams.storage,
"fs"
);
assert.strictEqual(
accessory.fakeGatoHistoryService.optionalParams.filename,
"fakegato-history_HygrothermographAccessory.json"
);
});
it("should setup fakeGatoHistoryService when configured with options", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {
fakeGatoEnabled: true,
fakeGatoOptions: { disableRepeatLastData: true },
});
assert(accessory.fakeGatoHistoryService !== undefined);
assert.strictEqual(
accessory.fakeGatoHistoryService.optionalParams.disableRepeatLastData,
true
);
});
it("should not setup fakeGatoHistoryService when not configured", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {
fakeGatoEnabled: false,
});
assert.strictEqual(accessory.fakeGatoHistoryService, undefined);
});
it("should add fakegato to getServices when configured", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {
fakeGatoEnabled: true,
});
const services = accessory.getServices();
assert.strictEqual(services.length, 6);
});
it("should use custom fakegato storage path when configured", () => {
const path = "/home/bridge/";
const accessory = new this.HygrothermographAccessory(mockLogger, {
fakeGatoEnabled: true,
fakeGatoStoragePath: path,
});
assert.strictEqual(accessory.fakeGatoStoragePath, path);
});
it("should use homebridge storage path for fakegato storage when not configured", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {
fakeGatoEnabled: true,
});
assert.strictEqual(
accessory.fakeGatoStoragePath,
this.homebridgeMock.user.storagePath()
);
});
it("should add temperature fakegato entry", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {
fakeGatoEnabled: true,
});
const spy = sinon.spy(accessory.fakeGatoHistoryService, "addEntry");
accessory.latestHumidity = 34.0;
accessory.setTemperature(28.0);
assert(spy.called);
assert.strictEqual(spy.args[0][0].temp, 28.0);
assert.strictEqual(spy.args[0][0].humidity, 34.0);
});
it("should add humidity fakegato entry", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {
fakeGatoEnabled: true,
});
const spy = sinon.spy(accessory.fakeGatoHistoryService, "addEntry");
accessory.latestTemperature = 28.0;
accessory.setHumidity(34.0);
assert(spy.called);
assert.strictEqual(spy.args[0][0].humidity, 34.0);
assert.strictEqual(spy.args[0][0].temp, 28.0);
});
it("should add humidity fakegato entry for moisture", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {
fakeGatoEnabled: true,
type: this.AccessoryType.MiFlora,
});
const spy = sinon.spy(accessory.fakeGatoHistoryService, "addEntry");
accessory.latestTemperature = 27.0;
accessory.setMoisture(44.0);
assert(spy.called);
assert.strictEqual(spy.args[0][0].humidity, 44.0);
assert.strictEqual(spy.args[0][0].temp, 27.0);
});
it("should publish temperature to mqtt", () => {
const topic = "sensors/temperature";
const value = 19.0;
const accessory = new this.HygrothermographAccessory(mockLogger, {
mqtt: {
url: "mqtt://127.0.0.1",
temperatureTopic: topic,
},
});
assert(accessory.mqttClient != null);
const publishSpy = sinon.spy(accessory.mqttClient, "publish");
accessory.setTemperature(value);
accessory.setBatteryLevel(10);
assert(publishSpy.calledOnce);
assert.strictEqual(publishSpy.args[0][0], topic);
assert.strictEqual(publishSpy.args[0][1], String(value));
});
it("should publish temperature to mqtt", () => {
const topic = "sensors/temperature";
const value = 19.0;
const accessory = new this.HygrothermographAccessory(mockLogger, {
type: this.AccessoryType.MiFlora,
mqtt: {
url: "mqtt://127.0.0.1",
temperatureTopic: topic,
},
});
assert(accessory.mqttClient != null);
const publishSpy = sinon.spy(accessory.mqttClient, "publish");
accessory.setTemperature(value);
accessory.setBatteryLevel(10);
assert(publishSpy.calledOnce);
assert.strictEqual(publishSpy.args[0][0], topic);
assert.strictEqual(publishSpy.args[0][1], String(value));
});
it("should publish humidity to mqtt", () => {
const topic = "sensors/humidity";
const value = 25.5;
const accessory = new this.HygrothermographAccessory(mockLogger, {
mqtt: {
url: "mqtt://127.0.0.1",
humidityTopic: topic,
},
});
assert(accessory.mqttClient != null);
const publishSpy = sinon.spy(accessory.mqttClient, "publish");
accessory.setHumidity(value);
accessory.setTemperature(23);
assert(publishSpy.calledOnce);
assert.strictEqual(publishSpy.args[0][0], topic);
assert.strictEqual(publishSpy.args[0][1], String(value));
});
it("should publish moisture to mqtt on MiFlora", () => {
const topic = "sensors/moisture";
const value = 25.5;
const accessory = new this.HygrothermographAccessory(mockLogger, {
type: this.AccessoryType.MiFlora,
mqtt: {
url: "mqtt://127.0.0.1",
moistureTopic: topic,
},
});
assert(accessory.mqttClient != null);
const publishSpy = sinon.spy(accessory.mqttClient, "publish");
accessory.setMoisture(value);
accessory.setTemperature(23);
assert(publishSpy.calledOnce);
assert.strictEqual(publishSpy.args[0][0], topic);
assert.strictEqual(publishSpy.args[0][1], String(value));
});
it("should publish fertility to mqtt on MiFlora", () => {
const topic = "sensors/fertility";
const value = 500;
const accessory = new this.HygrothermographAccessory(mockLogger, {
type: this.AccessoryType.MiFlora,
mqtt: {
url: "mqtt://127.0.0.1",
fertilityTopic: topic,
},
});
assert(accessory.mqttClient != null);
const publishSpy = sinon.spy(accessory.mqttClient, "publish");
accessory.setFertility(value);
accessory.setTemperature(23);
assert(publishSpy.calledOnce);
assert.strictEqual(publishSpy.args[0][0], topic);
assert.strictEqual(publishSpy.args[0][1], String(value));
});
it("should publish illuminance to mqtt on MiFlora", () => {
const topic = "sensors/illuminance";
const value = 2500;
const accessory = new this.HygrothermographAccessory(mockLogger, {
type: this.AccessoryType.MiFlora,
mqtt: {
url: "mqtt://127.0.0.1",
illuminanceTopic: topic,
},
});
assert(accessory.mqttClient != null);
const publishSpy = sinon.spy(accessory.mqttClient, "publish");
accessory.setIlluminance(value);
accessory.setTemperature(23);
assert(publishSpy.calledOnce);
assert.strictEqual(publishSpy.args[0][0], topic);
assert.strictEqual(publishSpy.args[0][1], String(value));
});
it("should publish battery to mqtt", () => {
const topic = "sensors/battery";
const value = 93;
const accessory = new this.HygrothermographAccessory(mockLogger, {
mqtt: {
url: "mqtt://127.0.0.1",
batteryTopic: topic,
},
});
assert(accessory.mqttClient != null);
const publishSpy = sinon.spy(accessory.mqttClient, "publish");
accessory.setBatteryLevel(value);
accessory.setTemperature(23);
assert(publishSpy.calledOnce);
assert.strictEqual(publishSpy.args[0][0], topic);
assert.strictEqual(publishSpy.args[0][1], String(value));
});
it("should not configure mqtt client when not configured", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {});
assert(accessory.mqttClient == null);
});
it("should log on mqtt events", () => {
const spyErrorLogger = sinon.spy(mockLogger, "error");
const spyInfoLogger = sinon.spy(mockLogger, "info");
const spyDebugLogger = sinon.spy(mockLogger, "debug");
const accessory = new this.HygrothermographAccessory(mockLogger, {
mqtt: {
url: "mqtt://127.0.0.1",
batteryTopic: "battery/",
},
});
assert.strictEqual(spyDebugLogger.callCount, 1);
accessory.mqttClient.emit("error", new Error("error"));
assert(spyErrorLogger.calledOnce);
accessory.mqttClient.emit("connect");
assert(spyInfoLogger.calledOnce);
accessory.mqttClient.emit("close");
assert.strictEqual(spyDebugLogger.callCount, 2);
accessory.mqttClient.emit("reconnect");
assert.strictEqual(spyDebugLogger.callCount, 3);
});
it("should set forceDiscovering to true when not set", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {});
assert.strictEqual(accessory.scanner.forceDiscovering, true);
});
it("should pass forceDiscovering to scanner", () => {
const accessoryForcedDiscovery = new this.HygrothermographAccessory(
mockLogger,
{
forceDiscovering: true,
}
);
assert.strictEqual(accessoryForcedDiscovery.scanner.forceDiscovering, true);
const accessoryNotForcedDiscovery = new this.HygrothermographAccessory(
mockLogger,
{
forceDiscovering: false,
}
);
assert.strictEqual(
accessoryNotForcedDiscovery.scanner.forceDiscovering,
false
);
});
it("should set scanner.restartDelay to default when not set", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {});
assert.strictEqual(accessory.scanner.restartDelay, 2500);
});
it("should set scanner.restartDelay when forceDiscoveringDelay is configured", () => {
const forceDiscoveringDelay = 2500;
const accessory = new this.HygrothermographAccessory(mockLogger, {
forceDiscoveringDelay,
});
assert.strictEqual(accessory.scanner.restartDelay, forceDiscoveringDelay);
});
it("should batch update on change when configured with updateInterval", () => {
const accessory = new this.HygrothermographAccessory(mockLogger);
accessory.scanner.emit("temperatureChange", 25.5, {
address: "123",
id: "123",
});
accessory.scanner.emit("humidityChange", 35.5, {
address: "123",
id: "123",
});
accessory.scanner.emit("batteryChange", 99, {
address: "123",
id: "123",
});
const updateTemperatureValueSpy = sinon.spy(
this.characteristics.CurrentTemperature,
"updateValue"
);
const updateHumidityValueSpy = sinon.spy(
this.characteristics.CurrentRelativeHumidity,
"updateValue"
);
const updateBatteryValueSpy = sinon.spy(
this.characteristics.BatteryLevel,
"updateValue"
);
accessory.scanner.emit("change", {
address: "123",
id: "123",
});
assert(updateTemperatureValueSpy.called === false);
assert(updateHumidityValueSpy.called === false);
assert(updateBatteryValueSpy.called === false);
});
it("should batch update on change when configured with updateInterval on MiFlora", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {
type: this.AccessoryType.MiFlora,
});
accessory.scanner.emit("temperatureChange", 25.5, {
address: "123",
id: "123",
});
accessory.scanner.emit("moistureChange", 35.5, {
address: "123",
id: "123",
});
accessory.scanner.emit("fertilityChange", 500, {
address: "123",
id: "123",
});
accessory.scanner.emit("illuminanceChange", 400, {
address: "123",
id: "123",
});
const updateTemperatureValueSpy = sinon.spy(
this.characteristics.CurrentTemperature,
"updateValue"
);
const updateMoistureValueSpy = sinon.spy(
this.characteristics.CurrentRelativeHumidity,
"updateValue"
);
const updateIlluminanceValueSpy = sinon.spy(
this.characteristics.CurrentAmbientLightLevel,
"updateValue"
);
accessory.scanner.emit("change", {
address: "123",
id: "123",
});
assert(updateTemperatureValueSpy.called === false);
assert(updateMoistureValueSpy.called === false);
assert(updateIlluminanceValueSpy.called === false);
});
it("should not batch update on change when configured with updateInterval", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {
updateInterval: 60,
});
const updateTemperatureValueSpy = sinon.spy(
this.characteristics.CurrentTemperature,
"updateValue"
);
const updateHumidityValueSpy = sinon.spy(
this.characteristics.CurrentRelativeHumidity,
"updateValue"
);
const updateBatteryValueSpy = sinon.spy(
this.characteristics.BatteryLevel,
"updateValue"
);
accessory.scanner.emit("temperatureChange", 25.5, {
address: "123",
id: "123",
});
assert(updateTemperatureValueSpy.called === false);
accessory.scanner.emit("humidityChange", 35.5, {
address: "123",
id: "123",
});
assert(updateHumidityValueSpy.called === false);
accessory.scanner.emit("batteryChange", 99, {
address: "123",
id: "123",
});
assert(updateBatteryValueSpy.called === false);
accessory.scanner.emit("change", {
address: "123",
id: "123",
});
assert(updateTemperatureValueSpy.called);
assert(updateHumidityValueSpy.called);
assert(updateBatteryValueSpy.called);
});
it("should not batch update on change when configured with updateInterval on MiFlora", () => {
const accessory = new this.HygrothermographAccessory(mockLogger, {
updateInterval: 60,
type: this.AccessoryType.MiFlora,
});
const updateTemperatureValueSpy = sinon.spy(
this.characteristics.CurrentTemperature,
"updateValue"
);
const updateMoistureValueSpy = sinon.spy(
this.characteristics.CurrentRelativeHumidity,
"updateValue"
);
const updateIlluminanceValueSpy = sinon.spy(
this.characteristics.CurrentAmbientLightLevel,
"updateValue"
);
accessory.scanner.emit("temperatureChange", 25.5, {
address: "123",
id: "123",
});
assert(updateTemperatureValueSpy.called === false);
accessory.scanner.emit("moistureChange", 35.5, {
address: "123",
id: "123",
});
assert(updateMoistureValueSpy.called === false);
accessory.scanner.emit("fertilityChange", 500, {
address: "123",
id: "123",
});
accessory.scanner.emit("illuminanceChange", 400, {
address: "123",
id: "123",
});
assert(updateIlluminanceValueSpy.called === false);
accessory.scanner.emit("change", {
address: "123",
id: "123",
});
assert(updateTemperatureValueSpy.called);
assert(updateMoistureValueSpy.called);
assert(updateIlluminanceValueSpy.called);
});
it("should batch update with missing values", () => {
const updateInterval = 60;
const accessory = new this.HygrothermographAccessory(mockLogger, {
updateInterval: updateInterval,
});
const updateTemperatureValueSpy = sinon.spy(
this.characteristics.CurrentTemperature,
"updateValue"
);
const updateHumidityValueSpy = sinon.spy(
this.characteristics.CurrentRelativeHumidity,
"updateValue"
);
const updateBatteryValueSpy = sinon.spy(
this.characteristics.BatteryLevel,
"updateValue"
);
accessory.lastBatchUpdatedAt = Date.now();
accessory.scanner.emit("temperatureChange", 25.5, {
address: "123",
id: "123",
});
accessory.scanner.emit("change", {
address: "123",
id: "123",
});
assert(updateTemperatureValueSpy.called === false);
assert(updateHumidityValueSpy.called === false);
assert(updateBatteryValueSpy.called === false);
sinon.useFakeTimers(Date.now() + 1000 * updateInterval);
accessory.scanner.emit("temperatureChange", 26.5, {
address: "123",
id: "123",
});
accessory.scanner.emit("change", {
address: "123",
id: "123",
});
assert(updateTemperatureValueSpy.called);
assert(updateHumidityValueSpy.called === false);
assert(updateBatteryValueSpy.called === false);
sinon.useFakeTimers(Date.now() + 1000 * updateInterval);
accessory.scanner.emit("humidityChange", 35.5, {
address: "123",
id: "123",
});
accessory.scanner.emit("change", {
address: "123",
id: "123",
});
assert(updateTemperatureValueSpy.called);
assert(updateHumidityValueSpy.called);
assert(updateBatteryValueSpy.called === false);
sinon.useFakeTimers(Date.now() + 1000 * updateInterval);
accessory.scanner.emit("batteryChange", 99, {
address: "123",
id: "123",
});
accessory.scanner.emit("change", {
address: "123",
id: "123",
});
assert(updateTemperatureValueSpy.called);
assert(updateHumidityValueSpy.called);
assert(updateBatteryValueSpy.called);
// Reset temperature
accessory.latestTemperature = null;
updateTemperatureValueSpy.resetHistory();
sinon.useFakeTimers(Date.now() + 1000 * updateInterval);
accessory.scanner.emit("batteryChange", 99, {
address: "123",
id: "123",
});
accessory.scanner.emit("change", {
address: "123",
id: "123",
});
assert(updateTemperatureValueSpy.called === false);
assert(updateHumidityValueSpy.called);
assert(updateBatteryValueSpy.called);
});
it("should batch update with missing values on MiFlora [1]", () => {
const updateInterval = 60;
const accessory = new this.HygrothermographAccessory(mockLogger, {
updateInterval: updateInterval,
type: this.AccessoryType.MiFlora,
});
const updateTemperatureValueSpy = sinon.spy(
this.characteristics.CurrentTemperature,
"updateValue"
);
const updateMoistureValueSpy = sinon.spy(
this.characteristics.CurrentRelativeHumidity,
"updateValue"
);
const updateIlluminanceValueSpy = sinon.spy(
this.characteristics.CurrentAmbientLightLevel,
"updateValue"
);
accessory.lastBatchUpdatedAt = Date.now();
accessory.scanner.emit("temperatureChange", 25.5, {
address: "123",
id: "123",
});
accessory.scanner.emit("change", {
address: "123",
id: "123",
});
assert(updateTemperatureValueSpy.called === false);
assert(updateMoistureValueSpy.called === false);
assert(updateIlluminanceValueSpy.called === false);
sinon.useFakeTimers(Date.now() + 1000 * updateInterval);
accessory.scanner.emit("temperatureChange", 26.5, {
address: "123",
id: "123",
});
accessory.scanner.emit("change", {
address: "123",
id: "123",
});
assert(updateTemperatureValueSpy.called);
assert(updateMoistureValueSpy.called === false);
assert(updateIlluminanceValueSpy.called === false);
sinon.useFakeTimers(Date.now() + 1000 * updateInterval);
accessory.scanner.emit("moistureChange", 35.5, {
address: "123",
id: "123",
});
accessory.scanner.emit("change", {
address: "123",
id: "123",
});
assert(updateTemperatureValueSpy.called);
assert(updateMoistureValueSpy.called);
assert(updateIlluminanceValueSpy.called === false);
sinon.useFakeTimers(Date.now() + 1000 * updateInterval);
accessory.scanner.emit("illuminanceChange", 99, {
address: "123",
id: "123",
});
accessory.scanner.emit("change", {
address: "123",
id: "123",
});
assert(updateTemperatureValueSpy.called);
assert(updateMoistureValueSpy.called);
assert(updateIlluminanceValueSpy.called);
// Reset temperature
accessory.latestTemperature = null;
updateTemperatureValueSpy.resetHistory();
sinon.useFakeTimers(Date.now() + 1000 * updateInterval);
accessory.scanner.emit("illuminanceChange", 99, {
address: "123",
id: "123",
});
accessory.scanner.emit("change", {
address: "123",
id: "123",
});
assert(updateTemperatureValueSpy.called === false);
assert(updateMoistureValueSpy.called);
assert(updateIlluminanceValueSpy.called);
});
it("should batch update with missing values on MiFlora", () => {
const updateInterval = 60;
const accessory = new this.HygrothermographAccessory(mockLogger, {
updateInterval: updateInterval,
type: this.AccessoryType.MiFlora,
});
const updateTemperatureValueSpy = sinon.spy(
this.characteristics.CurrentTemperature,
"updateValue"
);
const updateMoistureValueSpy = sinon.spy(
this.characteristics.CurrentRelativeHumidity,
"updateValue"
);
const updateFertilityValueSpy = sinon.spy(
this.characteristics.CurrentAmbientLightLevel,
"updateValue"
);
accessory.lastBatchUpdatedAt = Date.now();
accessory.scanner.emit("temperatureChange", 25.5, {
address: "123",
id: "123",
});
accessory.scanner.emit("change", {
address: "123",
id: "123",
});
assert(updateTemperatureValueSpy.called === false);
assert(updateMoistureValueSpy.called === false);
assert(updateFertilityValueSpy.called === false);
sinon.useFakeTimers(Date.now() + 1000 * updateInterval);
accessory.scanner.emit("temperatureChange", 26.5, {
address: "123",
id: "123",
});
accessory.scanner.emit("change", {
address: "123",
id: "123",
});
assert(updateTemperatureValueSpy.called);
assert(updateMoistureValueSpy.called === false);
assert(updateFertilityValueSpy.called === false);
sinon.useFakeTimers(Date.now() + 1000 * updateInterval);
accessory.scanner.emit("moistureChange", 35.5, {
address: "123",
id: "123",
});
accessory.scanner.emit("change", {
address: "123",
id: "123",
});
assert(updateTemperatureValueSpy.called);
assert(updateMoistureValueSpy.called);
assert(updateFertilityValueSpy.called === false);
sinon.useFakeTimers(Date.now() + 1000 * updateInterval);
accessory.scanner.emit("fertilityChange", 99, {
address: "123",
id: "123",
});
accessory.scanner.emit("change", {
address: "123",
id: "123",
});
assert(updateTemperatureValueSpy.called);
assert(updateMoistureValueSpy.called);
assert(updateFertilityValueSpy.called);
// Reset temperature
accessory.latestTemperature = null;
updateTemperatureValueSpy.resetHistory();
sinon.useFakeTimers(Date.now() + 1000 * updateInterval);
accessory.scanner.emit("fertilityChange", 99, {
address: "123",
id: "123",
});
accessory.scanner.emit("change", {
address: "123",
id: "123",
});
assert(updateTemperatureValueSpy.called === false);
assert(updateMoistureValueSpy.called);
assert(updateFertilityValueSpy.called);
});
it("should not batch update until interval delta is reached", () => {
const updateInterval = 60;
const accessory = new this.HygrothermographAccessory(mockLogger, {
updateInterval: updateInterval,
});
accessory.scanner.emit("temperatureChange", 25.5, {
address: "123",
id: "123",
});
accessory.scanner.emit("humidityChange", 35.5, {
address: "123",
id: "123",
});
accessory.scanner.emit("batteryChange", 99, {
address: "123",
id: "123",
});
const updateTemperatureValueSpy = sinon.spy(
this.characteristics.CurrentTemperature,
"updateValue"
);
const updateHumidityValueSpy = sinon.spy(
this.characteristics.CurrentRelativeHumidity,
"updateValue"
);
const updateBatteryValueSpy = sinon.spy(
this.characteristics.BatteryLevel,
"updateValue"
);
accessory.scanner.emit("change", {
address: "123",
id: "123",
});
assert(updateTemperatureValueSpy.called);
assert(updateHumidityValueSpy.called);
assert(