homebridge-mqtt-ceiling-fan-remote
Version:
A remote using http commands to control a hunter ceiling fan via a tasmotized sonoff rfbridge for homebridge written in Typescript
377 lines • 16.1 kB
JavaScript
import EventEmitter from 'node:events';
const BrightnessLevels = 8;
const FanSpeeds = 3;
const FanStateActive = 1;
const FanStateInactive = 0;
const LightLevels = [12, 25, 37, 50, 62, 75, 87, 100];
/**
* Platform Accessory
* An instance of this class is created for each accessory your platform registers
* Each accessory may expose multiple services of different service types.
*/
export class CeilingFanRemote extends EventEmitter {
platform;
accessory;
name;
serial;
lightService;
fanService;
accessoryState = {
LightOn: false,
Brightness: 100,
FanOn: 0,
Speed: 100,
};
constructor(platform, accessory) {
super();
this.platform = platform;
this.accessory = accessory;
//this.platform.log.debug(Date.now()+" "+'Constructing ceiling fan remote with context:', this.accessory.context);
this.name = this.accessory.context.config.name;
this.serial = this.accessory.context.config._id.toString();
// set accessory information
this.accessory.getService(this.platform.Service.AccessoryInformation)
.setCharacteristic(this.platform.Characteristic.Name, this.name)
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'Andrew Parnell')
.setCharacteristic(this.platform.Characteristic.Model, 'Ceiling fan controls')
.setCharacteristic(this.platform.Characteristic.SerialNumber, this.serial);
// INITIALIZE LIGHTBULB SERVICE
// get the LightBulb service if it exists, otherwise create a new LightBulb service
this.lightService = this.accessory.getService(this.platform.Service.Lightbulb) || this.accessory.addService(this.platform.Service.Lightbulb);
// set the service name, this is what is displayed as the default name on the Home app
this.lightService.setCharacteristic(this.platform.Characteristic.Name, `${this.name} Light`);
// each service must implement at-minimum the "required characteristics" for the given service type
// see https://developers.homebridge.io/#/service/Lightbulb
// register handlers for the On/Off Characteristic
this.lightService.getCharacteristic(this.platform.Characteristic.On)
.onSet(this.setLightOn.bind(this)) // SET - bind to the `setLightOn` method below
.onGet(this.getLightOn.bind(this)); // GET - bind to the `getLightOn` method below
// register handlers for the Brightness Characteristic
this.lightService.getCharacteristic(this.platform.Characteristic.Brightness)
.setProps({
minValue: 0,
maxValue: 100,
minStep: 100 / BrightnessLevels
})
.onSet(this.setLightBrightness.bind(this)) // SET - bind to the 'setLightBrightness` method below
.onGet(this.getLightBrightness.bind(this)); // SET - bind to the 'getLightBrightness` method below
// INITIALIZE FAN SERVICE
// get the Fan service if it exists, otherwise create a new Fan service
this.fanService = this.accessory.getService(this.platform.Service.Fanv2) || this.accessory.addService(this.platform.Service.Fanv2);
// set the service name, this is what is displayed as the default name on the Home app
this.fanService.setCharacteristic(this.platform.Characteristic.Name, `${this.name} Fan`);
// register handlers for the Active Characteristic
this.fanService.getCharacteristic(this.platform.Characteristic.Active)
.onSet(this.setFanActive.bind(this)) // SET - bind to the `setFanActive` method below
.onGet(this.getFanActive.bind(this)); // GET - bind to the `getFanActive` method below
this.fanService.getCharacteristic(this.platform.Characteristic.RotationSpeed)
.setProps({
minValue: 0,
maxValue: 100,
minStep: 100 / FanSpeeds,
validValues: null //[0, 100/FanSpeeds, 200/FanSpeeds, 100]
})
.onSet(this.setFanSpeed.bind(this))
.onGet(this.getFanSpeed.bind(this));
//Initialize the state from context
if (this.accessory.context.state) {
Object.keys(this.accessoryState).forEach(stateKey => {
if (stateKey in this.accessory.context.state) {
this.accessoryState[stateKey] = this.accessory.context.state[stateKey];
}
});
// set characteristic to trigger the commands and make sure the digital model of these fans matches the reality.
this.lightService.setCharacteristic(this.platform.Characteristic.Brightness, this.accessoryState.Brightness);
this.lightService.setCharacteristic(this.platform.Characteristic.On, this.accessoryState.LightOn);
this.fanService.setCharacteristic(this.platform.Characteristic.RotationSpeed, this.accessoryState.Speed);
this.fanService.setCharacteristic(this.platform.Characteristic.Active, this.accessoryState.FanOn);
}
else {
this.accessory.context.state = this.accessoryState;
this.platform.api.updatePlatformAccessories([this.accessory]);
}
}
get config() {
return this.accessory.context.config;
}
async getLightOn() {
this.platform.log.debug(Date.now() + ` ${this.name}.getLightOn() -> return: ${this.accessoryState.LightOn}`);
return this.accessoryState.LightOn;
}
async getLightBrightness() {
this.platform.log.debug(Date.now() + ` ${this.name}.getLightBrightness() -> return: ${this.accessoryState.Brightness}`);
return this.accessoryState.Brightness;
}
async getFanActive() {
this.platform.log.debug(Date.now() + ` ${this.name}.getFanActive() -> return: ${this.accessoryState.FanOn}`);
return this.accessoryState.FanOn;
}
async getFanSpeed() {
this.platform.log.debug(Date.now() + ` ${this.name}.getFanSpeed() -> return: ${this.accessoryState.Speed}`);
return this.accessoryState.Speed;
}
async setLightOn(value) {
this.platform.log.debug(Date.now() + ` ${this.name}.setLightOn(${value})`);
this.accessoryState.LightOn = value;
//Update the cache
this.saveState();
//Send command
this.emit('update', { remote: this.accessory.context.config.remote_ids[0], parameter: 'Brightness', value: value ? this.accessoryState.Brightness : 0 });
}
async setLightBrightness(value) {
this.platform.log.debug(Date.now() + ` ${this.name}.setLightBrightness(${value})`);
const newBrightness = value;
if (newBrightness > 0) {
this.accessoryState.Brightness = newBrightness;
}
//Update the cache
this.saveState();
//Send command
this.emit('update', { remote: this.accessory.context.config.remote_ids[0], parameter: 'Brightness', value: newBrightness });
}
async setFanActive(value) {
this.platform.log.debug(Date.now() + ` ${this.name}.setFanActive(${value})`);
this.accessoryState.FanOn = value;
//Update the cache
this.saveState();
//Send command
this.emit('update', { remote: this.accessory.context.config.remote_ids[0], parameter: 'Speed', value: value === FanStateActive ? this.accessoryState.Speed / (100 / 3) : 0 });
}
async setFanSpeed(value) {
this.platform.log.debug(Date.now() + ` ${this.name}.setFanSpeed(${value})`);
const newSpeed = value;
if (newSpeed > 0) {
this.accessoryState.Speed = newSpeed;
}
//Update the cache
this.saveState();
//Send command
this.emit('update', { remote: this.accessory.context.config.remote_ids[0], parameter: 'Speed', value: Math.round(newSpeed / (100 / 3)) });
}
get commands() {
return [
{
label: 'Fan Off',
command: 98,
update: () => {
this.accessoryState.FanOn = FanStateInactive;
}
},
{
label: 'Fan Toggle On/Off',
command: 35,
update: () => {
const newState = this.accessoryState.FanOn === FanStateActive ? FanStateInactive : FanStateActive;
this.accessoryState.FanOn = newState;
}
},
{
label: 'Fan Speed 1',
command: 4,
update: () => {
this.accessoryState.FanOn = FanStateActive;
this.accessoryState.Speed = 100 / 3;
}
},
{
label: 'Fan Speed 2',
command: 32,
update: () => {
this.accessoryState.FanOn = FanStateActive;
this.accessoryState.Speed = 200 / 3;
}
},
{
label: 'Fan Speed 3',
command: 64,
update: () => {
this.accessoryState.FanOn = FanStateActive;
this.accessoryState.Speed = 100;
}
},
{
label: 'fanMin',
command: 2,
update: () => {
this.accessoryState.FanOn = FanStateActive;
this.accessoryState.Speed = 100 / 3;
}
},
{
label: 'fanMax',
command: 66,
update: () => {
this.accessoryState.FanOn = FanStateActive;
this.accessoryState.Speed = 100;
}
},
{
label: 'fanUp',
command: 513,
update: () => {
this.accessoryState.FanOn = FanStateActive;
const newSpeed = Math.min(this.accessoryState.Speed + (100 / 3), 100);
this.accessoryState.Speed = newSpeed;
}
},
{
label: 'fanDown',
command: 514,
update: () => {
this.accessoryState.FanOn = 1;
const newSpeed = Math.min(this.accessoryState.Speed - (100 / 3), 100 / 3);
this.accessoryState.Speed = newSpeed;
}
},
{
label: 'Light On',
command: 138,
update: () => {
this.accessoryState.LightOn = true;
}
},
{
label: 'Light Off',
command: 266,
update: () => {
this.accessoryState.LightOn = false;
}
},
{
label: 'Light Toggle On/Off',
command: 768,
update: () => {
this.accessoryState.LightOn = !this.accessoryState.LightOn;
}
},
{
label: 'Light 12.5% aka level 1',
command: 10,
update: () => {
this.accessoryState.LightOn = true;
this.accessoryState.Brightness = 12;
}
},
{
label: 'Light 25.0% aka level 2',
command: 11,
update: () => {
this.accessoryState.LightOn = true;
this.accessoryState.Brightness = 25;
}
},
{
label: 'Light 37.5% aka level 3',
command: 12,
update: () => {
this.accessoryState.LightOn = true;
this.accessoryState.Brightness = 37;
}
},
{
label: 'Light 50.0% aka level 4',
command: 13,
update: () => {
this.accessoryState.LightOn = true;
this.accessoryState.Brightness = 50;
}
},
{
label: 'Light 62.5% aka level 5',
command: 14,
update: () => {
this.accessoryState.LightOn = true;
this.accessoryState.Brightness = 62;
}
},
{
label: 'Light 75.0% aka level 6',
command: 15,
update: () => {
this.accessoryState.LightOn = true;
this.accessoryState.Brightness = 75;
}
},
{
label: 'Light 87.5% aka level 7',
command: 72,
update: () => {
this.accessoryState.LightOn = true;
this.accessoryState.Brightness = 87;
}
},
{
label: 'Light 100.0% aka level 8',
command: 73,
update: () => {
this.accessoryState.LightOn = true;
this.accessoryState.Brightness = 100;
}
},
{
label: 'lightMin',
command: 9,
update: () => {
this.accessoryState.LightOn = true;
this.accessoryState.Brightness = 12;
}
},
{
label: 'lightMax',
command: 74,
update: () => {
this.accessoryState.LightOn = true;
this.accessoryState.Brightness = 100;
}
},
{
label: 'lightUp',
command: 137,
update: () => {
this.accessoryState.LightOn = true;
const currentLevelIndex = LightLevels.indexOf(this.accessoryState.Brightness);
const nextLevelIndex = Math.min(LightLevels.length, currentLevelIndex + 1);
this.accessoryState.Brightness = LightLevels[nextLevelIndex];
}
},
{
label: 'lightDown',
command: 265,
update: () => {
this.accessoryState.LightOn = true;
const currentLevelIndex = LightLevels.indexOf(this.accessoryState.Brightness);
const prevLevelIndex = Math.max(0, currentLevelIndex - 1);
this.accessoryState.Brightness = LightLevels[prevLevelIndex];
}
},
{
label: 'toggleDimming',
command: 5,
update: () => { }
},
{
label: 'pair',
command: 65,
update: () => { }
},
];
}
update(command) {
this.platform.log.debug(Date.now() + ` ${this.name}.command(${command})`);
const _command = this.commands.find(c => c.command === command);
if (_command) {
this.platform.log.debug(Date.now() + ` ${this.name} - Run Command ${_command.label}`);
_command.update();
this.lightService.updateCharacteristic(this.platform.Characteristic.On, this.accessoryState.LightOn);
this.lightService.updateCharacteristic(this.platform.Characteristic.Brightness, this.accessoryState.Brightness);
this.fanService.updateCharacteristic(this.platform.Characteristic.Active, this.accessoryState.FanOn);
this.fanService.updateCharacteristic(this.platform.Characteristic.RotationSpeed, this.accessoryState.Speed);
}
}
saveState() {
//Update the accessory context
this.accessory.context.state = this.accessoryState;
this.platform.api.updatePlatformAccessories([this.accessory]);
}
}
//# sourceMappingURL=platformAccessory.js.map