Skip to content

Commit

Permalink
Cleanup; Add accessory config; Add internal temperature
Browse files Browse the repository at this point in the history
  • Loading branch information
dblommesteijn committed Apr 11, 2023
1 parent 8a92044 commit b843446
Show file tree
Hide file tree
Showing 10 changed files with 226 additions and 183 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,6 @@ dist
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.pnp.*
.pnp.*

*.DS_Store
18 changes: 18 additions & 0 deletions config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,24 @@
"description": "UUIID found in: https://www.semsportal.com/PowerStation/PowerStatusSnMin/<power-station-uuid>",
"type": "string"
}
},
"showCurrentPowerLevel": {
"title": "Show current power level (Watt)",
"type": "boolean",
"required": true,
"default": true
},
"showDayTotal": {
"title": "Show total Day generation (Watt)",
"type": "boolean",
"required": true,
"default": true
},
"showInternalTemperature": {
"title": "Show Internal Temperature (degrees C)",
"type": "boolean",
"required": true,
"default": true
}
}
}
Expand Down
32 changes: 32 additions & 0 deletions package-lock.json

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

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"private": true,
"private": false,
"displayName": "Homebridge SEMS",
"name": "homebridge-plusems",
"version": "0.0.1",
"description": "This plugin consumes the API from SEMS portal for GoodWE inverters",
"description": "This plugin consumes the API from SEMS portal for GoodWE inverters (for solar power)",
"license": "Apache-2.0",
"repository": {
"type": "git",
Expand All @@ -28,7 +28,8 @@
],
"dependencies": {
"request": "^2.88.2",
"util": "^0.12.4"
"util": "^0.12.4",
"dig-ts": ">= 2.9"
},
"devDependencies": {
"@types/node": "^16.10.9",
Expand Down
44 changes: 44 additions & 0 deletions src/electricityWattAccessory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Service, PlatformAccessory } from 'homebridge';
import { HomebridgeSems } from './platform';
import { PowerStationAccessory } from './powerStationAccessory';
import { dig } from 'dig-ts';


export class ElectricityWattAccessory extends PowerStationAccessory {
private service: Service;

constructor(
private readonly platform: HomebridgeSems,
private readonly accessory: PlatformAccessory,
) {
super();
// set accessory information TODO: add model and serialnumber!
this.accessory.getService(this.platform.Service.AccessoryInformation)!
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'GoodWe')
.setCharacteristic(this.platform.Characteristic.Model, 'model')
.setCharacteristic(this.platform.Characteristic.SerialNumber, 'sn');

this.service = this.accessory.getService(this.platform.Service.LightSensor) ||
this.accessory.addService(this.platform.Service.LightSensor);
this.service.setCharacteristic(this.platform.Characteristic.Name, accessory.context.device.name);

this.update(accessory.context.device.data);
}

async update(powerStationData) {
let value = dig(powerStationData, this.accessory.context.device.dataDigPath).get() as number;
const multiplier = this.accessory.context.device.multiplier;
if(multiplier) {
value = value * multiplier;
}
this.setValue(value);
}

setValue(value) {
this.platform.log.info('New value', this.accessory.context.device.name, value);
if(value < 0.0001) {
value = 0.0001;
}
this.service.getCharacteristic(this.platform.Characteristic.CurrentAmbientLightLevel).updateValue(value);
}
}
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { API } from 'homebridge';

import { PLATFORM_NAME } from './settings';
import { HomeBridgeSems } from './platform';
import { HomebridgeSems } from './platform';

/**
* This method registers the platform with Homebridge
*/
export = (api: API) => {
api.registerPlatform(PLATFORM_NAME, HomeBridgeSems);
api.registerPlatform(PLATFORM_NAME, HomebridgeSems);
};
116 changes: 79 additions & 37 deletions src/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@ import { PLATFORM_NAME, PLUGIN_NAME } from './settings';

import request from 'request';
import util from 'util';
import { PowerStationAccessory } from './powerStationAccessory';
import { ElectricityWattAccessory } from './electricityWattAccessory';
import { TemperatureAccessory } from './temperatureAccessory';
const requestPromise = util.promisify(request);

/**
* HomebridgePlatform
* This class is the main constructor for your plugin, this is where you should
* parse the user config and discover/register accessories with Homebridge.
*/
export class HomeBridgeSems implements DynamicPlatformPlugin {
export class HomebridgeSems implements DynamicPlatformPlugin {
public readonly Service: typeof Service = this.api.hap.Service;
public readonly Characteristic: typeof Characteristic = this.api.hap.Characteristic;

Expand All @@ -28,46 +26,98 @@ export class HomeBridgeSems implements DynamicPlatformPlugin {
public readonly api: API,
) {
this.log.debug('Finished initializing platform:', this.config.name);

// When this event is fired it means Homebridge has restored all cached accessories from disk.
// Dynamic Platform plugins should only register new accessories after this event was fired,
// in order to ensure they weren't added to homebridge already. This event can also be used
// to start discovery of new accessories.
this.api.on('didFinishLaunching', () => {
log.debug('Executed didFinishLaunching callback');
// run the method to discover / register your devices as accessories
this.discoverDevices();
});
}

/**
* This function is invoked when homebridge restores cached accessories from disk at startup.
* It should be used to setup event handlers for characteristics and update respective values.
*/
configureAccessory(accessory: PlatformAccessory) {
this.log.info('Loading accessory from cache:', accessory.displayName);

// add the restored accessory to the accessories cache so we can track if it has already been registered
this.accessories.push(accessory);
}

/**
* This is an example method showing how to register discovered accessories.
* Accessories must only be registered once, previously created accessories
* must not be registered again to prevent "duplicate UUID" errors.
*/
loadElectricityAccessory(powerStationId, powerStationData, dataDigPath, name, multiplier = 1): ElectricityWattAccessory {
const uuid = this.api.hap.uuid.generate(`power_station_${powerStationId}_${dataDigPath.join()}`);
const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid);
let ret;

if(existingAccessory) {
this.log.info('found cached accessory', existingAccessory.displayName);
ret = new ElectricityWattAccessory(this, existingAccessory);
} else {
this.log.info('found new accessory', name);
const accessory = new this.api.platformAccessory(name, uuid);
accessory.context.device = { data: powerStationData, dataDigPath: dataDigPath, id: powerStationId, name: name,
multiplier: multiplier };
ret = new ElectricityWattAccessory(this, accessory);
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
}
return ret;
}

loadTemperatureAccessory(powerStationId, powerStationData, dataDigPath, name, multiplier = 1): ElectricityWattAccessory {
const uuid = this.api.hap.uuid.generate(`power_station_${powerStationId}_${dataDigPath.join()}`);
const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid);
let ret;

if(existingAccessory) {
this.log.info('found cached accessory', existingAccessory.displayName);
ret = new TemperatureAccessory(this, existingAccessory);
} else {
this.log.info('found new accessory', name);
const accessory = new this.api.platformAccessory(name, uuid);
accessory.context.device = { data: powerStationData, dataDigPath: dataDigPath, id: powerStationId, name: name,
multiplier: multiplier };
ret = new TemperatureAccessory(this, accessory);
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
}
return ret;
}

async discoverDevices() {
this.log.debug('test');
// iterate all configured power-stations
for(const powerStationId of this.config.powerStationIds){
// load power-station-data form API
const powerStationData = await this.getPowerStationDataById(powerStationId);
this.log.debug(`powerStationData for: ${powerStationId}`, powerStationData);

// TODO: implement current incoming: powerStationData.soc.power for generated in kW?a
// TODO: implement total per day: powerStationData.kpi.power for day total in kW
this.log.debug('powerStationData for:', powerStationId);
const accessories: PowerStationAccessory[] = [];

if(this.config.showCurrentPowerLevel) {
accessories.push(
this.loadElectricityAccessory(powerStationId, powerStationData, ['kpi', 'pac'], 'Current Production W', 1));
}

if(this.config.showDayTotal) {
accessories.push(
this.loadElectricityAccessory(powerStationId, powerStationData, ['kpi', 'power'], 'Day Production Wh', 1000));
}

if(this.config.showInternalTemperature) {
// NOTE: tempperature is not a typo :-D
accessories.push(
this.loadTemperatureAccessory(powerStationId, powerStationData, ['inverter', 0, 'tempperature'], 'Internal Temperature', 1));
}

// update all accessories with new data
this.fetchPowerStationUpdate(5000, powerStationId, accessories);
}
}

async fetchPowerStationUpdate(timeout: number, powerStationId, accessories) {
setInterval(async () => {
const powerStationData = await this.getPowerStationDataById(powerStationId);
this.log.debug('fetchPowerStationUpdate', powerStationId, accessories.length);
for(const accessory of accessories) {
accessory.update(powerStationData);
}
}, timeout);
}

async login() {
if(this.loginResponseBody.data) {
return;
}
this.loginAttempts++;

const json = { account: this.config.email, pwd: this.config.password };
Expand All @@ -78,7 +128,6 @@ export class HomeBridgeSems implements DynamicPlatformPlugin {

this.log.debug('login response: ', response.statusCode);
if(response.statusCode === 200) {
this.log.debug('login response: ', response.body);
this.loginResponseBody = response.body;
this.log.info('Login successful');
} else {
Expand All @@ -104,7 +153,7 @@ export class HomeBridgeSems implements DynamicPlatformPlugin {
await this.clearAuthenticationAndSleepAfterTooManyAttempts();
return await this.getPowerStationDataById(powerStationId);
} else {
this.log.debug('data response: ', response.statusCode, response.body);
// this.log.debug('data response: ', response.statusCode, response.body);
}
return response.body.data;
}
Expand All @@ -128,11 +177,4 @@ export class HomeBridgeSems implements DynamicPlatformPlugin {
});
}
}

async haltOnConfigError(errorMessage) {
this.log.info(`Configuration error: ${errorMessage}`);
await this.sleep(() => {
this.log.debug('sleeping...');
});
}
}
Loading

0 comments on commit b843446

Please sign in to comment.