Skip to content
This repository has been archived by the owner on Sep 24, 2024. It is now read-only.

Commit

Permalink
v0.1.2 uploads
Browse files Browse the repository at this point in the history
Align to docker image source
  • Loading branch information
markh authored Jul 14, 2023
1 parent 9426e12 commit 1fb2fb2
Show file tree
Hide file tree
Showing 9 changed files with 3,534 additions and 2,839 deletions.
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ COPY package.json ./
COPY Nest_accfactory.js ./
COPY Nest_camera_*.jpg ./
COPY HomeKitHistory.js ./
COPY HomeKitDevice.js ./
COPY nexusstreamer.js ./

# perform installation based on details in package.json
Expand All @@ -27,4 +28,4 @@ LABEL org.opencontainers.image.title="Nest_accfactory"
LABEL org.opencontainers.image.description="HomeKit integration for Nest devices based on HAP-NodeJS library"
LABEL org.opencontainers.image.url="https://github.com/n0rt0nthec4t/Nest_accfactory"
LABEL org.opencontainers.image.authors="n0rt0nthec4t@outlook.com"
LABEL org.opencontainers.image.version="v0.1.1"
LABEL org.opencontainers.image.version="v0.1.2"
229 changes: 229 additions & 0 deletions HomeKitDevice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
// HomeKitDevice class
//
// This is the base class for all HomeKit accessories we code to
//
// The deviceData structure should, at a minimum contain the following elements. These also need to be a "string" type
// mac_address
// serial_number
// software_version
// description
// location
// manufacturer
// model
//
// Following constants should be overridden in the module loading this class file
//
// HomeKitDevice.HOMEKITHISTORY - HomeKit History module
//
// Code version 13/7/2023
// Mark Hulskamp

"use strict";

// Define HAP-NodeJS requirements
var HAP = require("hap-nodejs");

// Define nodejs module requirements
var util = require("util");

class HomeKitDevice {
constructor(HomeKitAccessoryName, HomeKitPairingCode, HomeKitMDNSAdvertiser, uniqueUUIDForDevice, currentDeviceData, globalEventEmitter) {
this.eventEmitter = null; // Event emitter to use. Allow comms from other objects

this.deviceUUID = uniqueUUIDForDevice; // Unique UUID for this device. Used for event messaging to this device4
this.deviceData = currentDeviceData; // Current data for the device

this.HomeKitAccessory = null; // HomeKit Accessory object
this.HomeKitManufacturerName = HomeKitAccessoryName; // HomeKit device manufacturer name. Used for logging output prefix also
this.HomeKitHistory = null; // History logging service
this.HomeKitPairingCode = HomeKitPairingCode; // HomeKit pairing code

this.mDNSAdvertiser = HomeKitMDNSAdvertiser; // MDNS Provider to use for this device

// Validate if globalEventEmitter object passed to us is an instance of EventEmitter
if (globalEventEmitter instanceof require("events").EventEmitter == true) {
this.eventEmitter = globalEventEmitter; // Store

// Setup event listener to process "messages" to/from our device
this.eventEmitter.addListener(this.deviceUUID, this.#message.bind(this));
}
}

// Class functions
add(mDNSAdvertiseName, HomeKitAccessoryCategory, useHistoryService) {
if (typeof this.deviceData != "object" || typeof HAP.Accessory.Categories[HomeKitAccessoryCategory] == "undefined" || typeof mDNSAdvertiseName != "string" || typeof useHistoryService != "boolean" ||
(this.deviceData.hasOwnProperty("mac_address") == false && typeof this.deviceData.mac_address != "string" && this.deviceData.mac_address == "") ||
(this.deviceData.hasOwnProperty("serial_number") == false && typeof this.deviceData.serial_number != "string" && this.deviceData.serial_number == "") ||
(this.deviceData.hasOwnProperty("software_version") == false && typeof this.deviceData.software_version != "string" && this.deviceData.software_version == "") ||
(this.deviceData.hasOwnProperty("description") == false && typeof this.deviceData.description != "string" && this.deviceData.description == "") ||
(this.deviceData.hasOwnProperty("location") == false && typeof this.deviceData.location != "string") ||
(this.deviceData.hasOwnProperty("model") == false && typeof this.deviceData.model != "string" && this.deviceData.model == "") ||
this.HomeKitAccessory != null ||
mDNSAdvertiseName == "") {

return;
}

this.HomeKitAccessory = exports.accessory = new HAP.Accessory(mDNSAdvertiseName, HAP.uuid.generate("hap-nodejs:accessories:" + this.deviceData.manufacturer.toLowerCase() + "_" + this.deviceData.serial_number));
this.HomeKitAccessory.username = this.deviceData.mac_address;
this.HomeKitAccessory.pincode = this.HomeKitPairingCode;
this.HomeKitAccessory.category = HomeKitAccessoryCategory;
this.HomeKitAccessory.getService(HAP.Service.AccessoryInformation).updateCharacteristic(HAP.Characteristic.Manufacturer, this.deviceData.manufacturer);
this.HomeKitAccessory.getService(HAP.Service.AccessoryInformation).updateCharacteristic(HAP.Characteristic.Model, this.deviceData.model);
this.HomeKitAccessory.getService(HAP.Service.AccessoryInformation).updateCharacteristic(HAP.Characteristic.SerialNumber, this.deviceData.serial_number);
this.HomeKitAccessory.getService(HAP.Service.AccessoryInformation).updateCharacteristic(HAP.Characteristic.FirmwareRevision, this.deviceData.software_version);

if (useHistoryService == true && typeof HomeKitDevice.HOMEKITHISTORY != "undefined" && this.HomeKitHistory == null) {
// Setup logging service as requsted
this.HomeKitHistory = new HomeKitDevice.HOMEKITHISTORY(this.HomeKitAccessory, {});
}

try {
this.addHomeKitServices((this.deviceData.location == "" ? this.deviceData.description : this.deviceData.description + " - " + this.deviceData.location));
} catch (error) {
this.#outputLogging("addHomeKitServices call for device '%s' on '%s' failed. Error was", this.HomeKitAccessory.displayName, this.HomeKitAccessory.username, error);
}

this.update(this.deviceData, true); // perform an initial update using current data

// Publish accessory on local network and push onto export array for HAP-NodeJS "accessory factory"
this.HomeKitAccessory.publish({username: this.HomeKitAccessory.username, pincode: this.HomeKitAccessory.pincode, category: this.HomeKitAccessory.category, advertiser: this.mDNSAdvertiser});
this.#outputLogging("Advertising '%s (%s)' as '%s' to local network for HomeKit", (this.deviceData.location == "" ? this.deviceData.description : this.deviceData.description + " - " + this.deviceData.location), this.HomeKitAccessory.username, this.HomeKitAccessory.displayName);
}

remove() {
this.#outputLogging("Device '%s' on '%s' has been removed", this.HomeKitAccessory.displayName, this.HomeKitAccessory.username);

if (this.eventEmitter != null) {
// Remove listener for "messages"
this.eventEmitter.removeAllListeners(this.deviceUUID);
}

try {
this.removeHomeKitServices();
} catch (error) {
this.#outputLogging("removeHomeKitServices call for device '%s' on '%s' failed. Error was", this.HomeKitAccessory.displayName, this.HomeKitAccessory.username, error);
}

this.HomeKitAccessory.unpublish();
this.deviceData = null;
this.HomeKitAccessory = null;
this.eventEmitter = null;
this.HomeKitHistory = null;

// Do we destroy this object??
// this = null;
// delete this;
}

update(updatedDeviceData, forceHomeKitUpdate) {
if (typeof updatedDeviceData != "object" || typeof forceHomeKitUpdate != "boolean") {
return;
}

// Updated data may only contain selected fields, so we'll handle that here by taking our internally stored data
// and merge with the updates to ensure we have a complete data object
Object.entries(this.deviceData).forEach(([key, value]) => {
if (typeof updatedDeviceData[key] == "undefined") {
// Updated data doesn't have this key, so add it to our internally stored data
updatedDeviceData[key] = value;
}
});

// Check to see what data elements have changed
var changedObjectElements = {};
Object.entries(updatedDeviceData).forEach(([key, value]) => {
if (JSON.stringify(updatedDeviceData[key]) !== JSON.stringify(this.deviceData[key])) {
changedObjectElements[key] = updatedDeviceData[key];
}
});

// If we have any changed data elements OR we've been requested to force an update, do so
if (Object.keys(changedObjectElements).length != 0 || forceHomeKitUpdate == true) {
if (updatedDeviceData.hasOwnProperty("software_version") == true && updatedDeviceData.software_version != this.deviceData.software_version) {
// Update software version
this.HomeKitAccessory.getService(HAP.Service.AccessoryInformation).updateCharacteristic(HAP.Characteristic.FirmwareRevision, updatedDeviceData.software_version);
}

if (updatedDeviceData.hasOwnProperty("online") == true && updatedDeviceData.online != this.deviceData.online) {
// Update online/offline status
this.#outputLogging("Device '%s' on '%s' is %s", this.HomeKitAccessory.displayName, this.HomeKitAccessory.username, (updatedDeviceData.online == true ? "online" : "offline"));
}

try {
this.updateHomeKitServices(updatedDeviceData); // Pass updated data on for accessory to process as it needs
} catch (error) {
this.#outputLogging("updateHomeKitServices call for device '%s' on '%s' failed. Error was", this.HomeKitAccessory.displayName, this.HomeKitAccessory.username, error);
}
this.deviceData = updatedDeviceData; // Finally, update our internally stored data about the device
}
}

set(keyValues) {
if (typeof keyValues != "object" || this.eventEmitter == null) {
return;
}

// Send event with data to set
this.eventEmitter.emit(HomeKitDevice.SET, this.deviceUUID, keyValues);
}

get() {
// <---- To Implement
}

addHomeKitServices(serviceName) {
// <---- override in class which extends this class
}

removeHomeKitServices() {
// <---- override in class which extends this class
}

updateHomeKitServices(updatedDeviceData) {
// <---- override in class which extends this class
}

messageHomeKitServices(messageType, messageData) {
// <---- override in class which extends this class
}

#message(messageType, messageData) {
switch (messageType) {
case HomeKitDevice.UPDATE : {
this.update(messageData, false); // Got some device data, so process any updates
break;
}

case HomeKitDevice.REMOVE : {
this.remove(); // Got message for device removal
break
}

default : {
// This is not a message we know about, so pass onto accessory for it to perform any processing
try {
this.messageHomeKitServices(messageType, messageData);
} catch (error) {
this.#outputLogging("messageHomeKitServices call for device '%s' on '%s' failed. Error was", this.HomeKitAccessory.displayName, this.HomeKitAccessory.username, error);
}
break;
}
}
}

#outputLogging(...outputMessage) {
var timeStamp = String(new Date().getFullYear()).padStart(4, "0") + "-" + String(new Date().getMonth() + 1).padStart(2, "0") + "-" + String(new Date().getDate()).padStart(2, "0") + " " + String(new Date().getHours()).padStart(2, "0") + ":" + String(new Date().getMinutes()).padStart(2, "0") + ":" + String(new Date().getSeconds()).padStart(2, "0");
console.log(timeStamp + " [" + this.HomeKitManufacturerName + "] " + util.format(...outputMessage));
}
}

// Export defines for this module
HomeKitDevice.UPDATE = "HomeKitDevice.update"; // Device update message
HomeKitDevice.REMOVE = "HomeKitDevice.remove"; // Device remove message
HomeKitDevice.SET = "HomeKitDevice.set"; // Device set property message
HomeKitDevice.GET = "HomeKitDevice.get"; // Device get property message
HomeKitDevice.UNPUBLISH = "HomeKitDevice.unpublish"; // Device unpublish message
HomeKitDevice.HOMEKITHISTORY = undefined; // HomeKit History module
module.exports = HomeKitDevice;

Loading

0 comments on commit 1fb2fb2

Please sign in to comment.