daikin-controller-cloud
Version:
Interact with Daikin Cloud devices and retrieve Tokens
242 lines • 10.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DaikinCloudDevice = void 0;
const events_1 = require("events");
/**
* Class to represent and control one Daikin Cloud Device
*/
class DaikinCloudDevice extends events_1.EventEmitter {
#client;
desc;
managementPoints;
/**
* Constructor, called from DaikinCloud class when initializing all devices
*
* @param deviceDescription object with device description from Cloud request
* @param client Instance of DaikinCloud used for communication
*/
constructor(deviceDescription, client) {
super();
this.managementPoints = {};
this.#client = client;
this.setDescription(deviceDescription);
}
/**
* Helper method to traverse the Device object returned by Daikin cloud for subPath datapoints
*
* @param obj Object to traverse
* @param data Data object where all data are collected
* @param [pathPrefix] remember the path when traversing through structure
* @returns collected data
* @private
*/
#traverseDatapointStructure(obj, data, pathPrefix) {
if (obj === null)
return;
data = data || {};
pathPrefix = pathPrefix || '';
//console.log('ENTER: ' + pathPrefix);
Object.keys(obj).forEach(sub => {
if (!sub || !obj[sub])
return;
const subKeys = Object.keys(obj[sub]);
if (sub === 'meta' || subKeys.includes('value') || subKeys.includes('settable') || subKeys.includes('unit')) { // we found end leaf
//console.log('FINAL ' + pathPrefix + '/' + sub);
data[pathPrefix + '/' + sub] = obj[sub];
}
else if (sub === "electrical" && pathPrefix === '' && typeof obj[sub] === 'object' && obj[sub] !== null) { // hack for missing "unit" field in electical on Altherma devices
obj[sub].unit = 'kWh';
data[pathPrefix + '/' + sub] = obj[sub];
}
else if (typeof obj[sub] === 'object' && obj[sub] !== null) { // go one level deeper
//console.log(' found ' + sub);
this.#traverseDatapointStructure(obj[sub], data, pathPrefix + '/' + sub);
}
else {
//console.log('HHHMMM ' + sub);
}
});
return data;
}
/**
* Set a device description and parse/traverse data structure
*
* @param desc Device Description
*/
setDescription(desc) {
this.desc = desc;
// re-map some data for more easy access
this.managementPoints = {};
this.desc.managementPoints.forEach((mp) => {
const dataPoints = {};
Object.keys(mp).forEach((key) => {
if (!mp[key] || typeof mp[key] !== 'object')
return; // we ignore non dataPoints
if (typeof mp[key].value !== 'object' || (Object.keys(mp[key].value).length === 1 && mp[key].value.hasOwnProperty('enabled'))) { // normal datatype like string or number
dataPoints[key] = mp[key]; // we simply take of the datapoint
}
else { // data goes deeper in structure
dataPoints[key] = this.#traverseDatapointStructure(mp[key].value);
//console.log('RES-KEY ' + mp.embeddedId + ' - ' + key + ': ' + JSON.stringify(dataPoints[key]));
}
});
this.managementPoints[mp.embeddedId] = dataPoints;
});
//console.log('RES: ' + JSON.stringify(this.managementPoints));
this.emit('updated');
}
/**
* Get Daikin Device UUID
* @returns Device Id (UUID)
*/
getId() {
return this.desc.id;
}
/**
* Get the original Daikin Device Description
*
* @returns Daikin Device Description
*/
getDescription() {
return this.desc;
}
/**
* Get the timestamp when data were last updated
*
* @returns {Date} Last updated timestamp
*/
getLastUpdated() {
return new Date(this.desc.lastUpdateReceived || this.desc.timestamp);
}
/**
* Get the info if device is connected to cloud
*
* @returns {boolean} Connected status
*/
isCloudConnectionUp() {
return !!this.desc.isCloudConnectionUp.value;
}
/**
* Get a current data object (includes value and meta information).
* Without any parameter the full internal data structure is returned and
* can be further detailed by sending parameters
*
* @param {string} [managementPoint] Management point name
* @param {string} [dataPoint] Datapoint name for management point
* @param {string} [dataPointPath] further detailed datapoints with subpath data
* @returns {object|null} Data object
*/
getData(managementPoint, dataPoint, dataPointPath) {
if (!managementPoint) { // return all data
return this.managementPoints;
}
if (!this.managementPoints[managementPoint]) {
return null;
}
if (!dataPoint) { // return data from one managementPoint
return this.managementPoints[managementPoint];
}
if (!this.managementPoints[managementPoint][dataPoint]) {
return null;
}
if (!dataPointPath) { // return data from one managementPoint and dataPoint
return this.managementPoints[managementPoint][dataPoint];
}
if (!this.managementPoints[managementPoint][dataPoint][dataPointPath]) {
return null;
}
// return data for managementPoint and dataPoint and dataPointPath
return this.managementPoints[managementPoint][dataPoint][dataPointPath];
}
/**
* Update the data of this device from the cloud
*
* @returns {Promise<boolean>}
*/
async updateData() {
// TODO: Enhance this method to also allow to get some partial data like only one managementPoint or such; needs checking how to request
const desc = await this.#client.requestResource('/v1/gateway-devices/' + this.getId());
this.setDescription(desc);
return true;
}
/**
* Validates a value that should be sent to the Daikin Device
*
* @param {object} def Datapoint definition/meta data to verify
* @param {any} value Value to be set
* @param {boolean} [ignoreWritableCheck=false] Ignore the writable check
* @throws Error
* @private
*/
#validateData(def, value, ignoreWritableCheck = false) {
if (!def.hasOwnProperty('value') && !def.hasOwnProperty('settable')) {
throw new Error('Value can not be set without dataPointPath');
}
if (!ignoreWritableCheck && (!def.hasOwnProperty('settable') || !def.settable)) {
throw new Error('Value is not writable');
}
if (def.hasOwnProperty('value') && typeof def.value !== typeof value) {
throw new Error('Type of value (' + typeof value + ') is not the expected type ' + typeof def.value);
}
if (Array.isArray(def.values) && !def.values.includes(value)) {
throw new Error('Value (' + value + ') is not in the list of allowed values ' + def.values.join(','));
}
if (typeof def.maxLength === 'number' && typeof value === 'string' && value.length > def.maxLength) {
throw new Error('Length of value (' + value.length + ') is longer then the allowed ' + def.maxLength + ' characters');
}
if (typeof def.minValue === 'number' && typeof value === 'number' && value < def.minValue) {
throw new Error('Value (' + value + ') must not be smaller then ' + def.minValue);
}
if (typeof def.maxValue === 'number' && typeof value === 'number' && value > def.maxValue) {
throw new Error('Value (' + value + ') must not be bigger then ' + def.maxValue);
}
// TODO add more validations for stepValue(number)
}
/**
* Set a datapoint on this device
*
* @param {string} managementPoint Management point name
* @param {string} dataPoint Datapoint name for management point
* @param {string} [dataPointPath] further detailed datapoints with subpath data, if needed
* @param {number|string} value Value to set
* @param {SetDataOptions|boolean} options Options object for setData
* @returns {Promise<Object|boolean>} should return a true - or if a body is returned teh body object (can this happen?)
*/
async setData(managementPoint, dataPoint, dataPointPath, value, options = { ignoreWritableCheck: false, updateLocalData: false }) {
if (typeof options === 'boolean') {
console.warn('ignoreWritableCheck is deprecated and replaced with an options object. Please provide a SetDataOptions object for setData()');
options = {
ignoreWritableCheck: options,
updateLocalData: false
};
}
if (value === undefined) {
value = dataPointPath;
dataPointPath = undefined;
}
if (!this.managementPoints[managementPoint] || !this.managementPoints[managementPoint][dataPoint] || (dataPointPath && !this.managementPoints[managementPoint][dataPoint][dataPointPath])) {
throw new Error('Please provide a valid datapoint definition that exists in the data structure');
}
const dataPointDef = dataPointPath ? this.managementPoints[managementPoint][dataPoint][dataPointPath] : this.managementPoints[managementPoint][dataPoint];
this.#validateData(dataPointDef, value, options.ignoreWritableCheck);
const setPath = '/v1/gateway-devices/' + this.getId() + '/management-points/' + managementPoint + '/characteristics/' + dataPoint;
const setBody = {
value,
path: dataPointPath
};
const setOptions = {
method: 'PATCH',
body: JSON.stringify(setBody),
headers: {
'Content-Type': 'application/json'
}
};
const response = this.#client.requestResource(setPath, setOptions);
if (options.updateLocalData) {
dataPointDef.value = value;
}
return response;
}
}
exports.DaikinCloudDevice = DaikinCloudDevice;
//# sourceMappingURL=device.js.map