Skip to content

Commit

Permalink
add temperature sensor
Browse files Browse the repository at this point in the history
  • Loading branch information
donavanbecker committed Jan 10, 2024
1 parent fe2fc29 commit 312ee86
Show file tree
Hide file tree
Showing 7 changed files with 436 additions and 961 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@
},
"[jsonc]": {
"editor.defaultFormatter": "vscode.json-language-features"
}
},
"typescript.tsserver.nodePath": "/usr/local/bin/node"
}
16 changes: 8 additions & 8 deletions package-lock.json

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

203 changes: 203 additions & 0 deletions src/device/meater.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import { Service, PlatformAccessory, CharacteristicValue, API, HAP, Logging } from 'homebridge';
import { MeaterPlatform } from '../platform';
import { interval } from 'rxjs';
import { request } from 'undici';
import { MeaterPlatformConfig, device, meaterUrl } from '../settings.js';

/**
* 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 Meater {
public readonly api: API;
public readonly log: Logging;
protected readonly hap: HAP;
// Services
internalTemperatureService!: Service;
ambientTemperatureService!: Service;

// Characteristic Values
internalCurrentTemperature?: CharacteristicValue;
ambientCurrentTemperature?: CharacteristicValue;

// Updates
SensorUpdateInProgress!: boolean;

constructor(
private readonly platform: MeaterPlatform,
private readonly accessory: PlatformAccessory,
public device: device,
public config: MeaterPlatformConfig,

) {

this.api = this.platform.api;
this.log = this.platform.log;
this.hap = this.api.hap;

this.internalCurrentTemperature = accessory.context.internalCurrentTemperature;
this.ambientCurrentTemperature = accessory.context.ambientCurrentTemperature;

// Retrieve initial values and updateHomekit
this.refreshStatus();

// set accessory information
accessory
.getService(this.hap.Service.AccessoryInformation)!
.setCharacteristic(this.hap.Characteristic.Manufacturer, 'Meater')
.setCharacteristic(this.hap.Characteristic.Model, 'Smart Meat Thermometer')
.setCharacteristic(this.hap.Characteristic.SerialNumber, device.id)
.setCharacteristic(this.hap.Characteristic.FirmwareRevision, accessory.context.FirmwareRevision);

// Temperature Sensor Services
(this.internalTemperatureService =
this.accessory.getService(this.hap.Service.TemperatureSensor) || this.accessory.addService(this.hap.Service.TemperatureSensor)), device.id;
this.internalTemperatureService.setCharacteristic(this.hap.Characteristic.Name, `${accessory.displayName} Internal Temperature`);
if (
!this.internalTemperatureService.testCharacteristic(this.hap.Characteristic.ConfiguredName) &&
!this.internalTemperatureService.testCharacteristic(this.hap.Characteristic.Name)
) {
this.internalTemperatureService.addCharacteristic(this.hap.Characteristic.ConfiguredName, `${accessory.displayName} Internal Temperature`);
}

(this.ambientTemperatureService =
this.accessory.getService(this.hap.Service.TemperatureSensor) || this.accessory.addService(this.hap.Service.TemperatureSensor)), device.id;
this.ambientTemperatureService.setCharacteristic(this.hap.Characteristic.Name, `${accessory.displayName} Ambient Temperature`);
if (
!this.ambientTemperatureService.testCharacteristic(this.hap.Characteristic.ConfiguredName) &&
!this.ambientTemperatureService.testCharacteristic(this.hap.Characteristic.Name)
) {
this.ambientTemperatureService.addCharacteristic(this.hap.Characteristic.ConfiguredName, `${accessory.displayName} Ambient Temperature`);
}

// Retrieve initial values and update Homekit
this.updateHomeKitCharacteristics();

// Start an update interval
(async () => {
interval(await this.refreshRate() * 1000)
.subscribe(async () => {
await this.refreshStatus();
});
})();
}

async refreshRate() {
return this.platform.config.refreshRate || 360;
}

/**
* Parse the device status from the SwitchBot api
*/
async parseStatus(): Promise<void> {
// Internal Temperature
this.internalCurrentTemperature = this.internalCurrentTemperature!;
if (this.internalCurrentTemperature !== this.accessory.context.internalCurrentTemperature) {
this.log.debug(`${this.accessory.displayName} Internal Current Temperature: ${this.internalCurrentTemperature}°c`);
}

// Ambient Temperature
this.ambientCurrentTemperature = this.ambientCurrentTemperature!;
if (this.ambientCurrentTemperature !== this.accessory.context.ambientCurrentTemperature) {
this.log.debug(`${this.accessory.displayName} Ambient Current Temperature: ${this.ambientCurrentTemperature}°c`);
}
}

/**
* Asks the SwitchBot API for the latest device information
*/
async refreshStatus(): Promise<void> {
try {
if (this.config.token) {
const { body, statusCode, headers } = await request(`${meaterUrl}/${this.device.id}`, {
method: 'GET',
headers: {
'Authorization': 'Bearer ' + this.config.token,
},
});
this.log.info(`Device body: ${JSON.stringify(body)}`);
this.log.info(`Device statusCode: ${statusCode}`);
this.log.info(`Device headers: ${JSON.stringify(headers)}`);
const device: any = await body.json();
this.log.info(`Device: ${JSON.stringify(device)}`);
this.log.info(`Device StatusCode: ${device.statusCode}`);
if (statusCode === 200 && device.statusCode === 200) {
this.internalCurrentTemperature = device.data.temperature.internal;
this.ambientCurrentTemperature = device.data.temperature.ambient;
this.parseStatus();
this.updateHomeKitCharacteristics();
} else {
this.statusCode(statusCode);
this.statusCode(device.statusCode);
}
}
} catch (e: any) {
this.apiError(e);
this.log.error(
`${this.accessory.displayName} failed refreshStatus, Error Message: ${JSON.stringify(e.message)}`,
);
}
}

/**
* Updates the status for each of the HomeKit Characteristics
*/
async updateHomeKitCharacteristics(): Promise<void> {
if (this.internalCurrentTemperature === undefined) {
this.log.debug(`${this.accessory.displayName} Internal Current Temperature: ${this.internalCurrentTemperature}`);
} else {
this.accessory.context.internalCurrentTemperature = this.internalCurrentTemperature;
this.internalTemperatureService?.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, this.internalCurrentTemperature);
this.log.debug(`${this.accessory.displayName} updateCharacteristic Internal Current Temperature: ${this.internalCurrentTemperature}`);
}

if (this.ambientCurrentTemperature === undefined) {
this.log.debug(`${this.accessory.displayName} Ambient Current Temperature: ${this.ambientCurrentTemperature}`);
} else {
this.accessory.context.ambientCurrentTemperature = this.ambientCurrentTemperature;
this.ambientTemperatureService?.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, this.ambientCurrentTemperature);
this.log.debug(`${this.accessory.displayName} updateCharacteristic Ambient Current Temperature: ${this.ambientCurrentTemperature}`);
}
}

async statusCode(statusCode: number): Promise<void> {
/**
* Meater API Status Codes (https://github.com/apption-labs/meater-cloud-public-rest-api)
*
* Standard Response Codes: 200(OK), 201(Created), 204(No Content)
* https://github.com/apption-labs/meater-cloud-public-rest-api#standard-response
*
* Error Response: 400(Bad Request), 401(Unauthorized), 404(Not Found), 429(Too Many Requests), 500(Internal Server Error)
* https://github.com/apption-labs/meater-cloud-public-rest-api#error-response
**/
switch (statusCode) {
case 200:
this.log.debug(`${this.accessory.displayName} Standard Response, statusCode: ${statusCode}`);
break;
case 400:
this.log.error(`${this.accessory.displayName} Bad Request, statusCode: ${statusCode}`);
break;
case 401:
this.log.error(`${this.accessory.displayName} Unauthorized, statusCode: ${statusCode}`);
break;
case 404:
this.log.error(`${this.accessory.displayName} Not Found, statusCode: ${statusCode}`);
break;
case 429:
this.log.error(`${this.accessory.displayName} Too Many Requests, statusCode: ${statusCode}`);
break;
case 500:
this.log.error(`${this.accessory.displayName} Internal Server Error (Meater Server), statusCode: ${statusCode}`);
break;
default:
this.log.info(
`${this.accessory.displayName} Unknown statusCode: ${statusCode}, Report Bugs Here: https://bit.ly/homebridge-meater-bug-report`);
}
}

async apiError(e: any): Promise<void> {
this.internalTemperatureService?.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, e);

}
}
Loading

0 comments on commit 312ee86

Please sign in to comment.