@di-zed/yandex-smart-home
Version:
The Yandex Smart Home skills for the different device types.
251 lines (250 loc) • 10.5 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const mqttService_1 = __importDefault(require("./mqttService"));
const configProvider_1 = __importDefault(require("../providers/configProvider"));
const redisProvider_1 = __importDefault(require("../providers/redisProvider"));
const mqttRepository_1 = __importDefault(require("../repositories/mqttRepository"));
const topicExpiredEvent_1 = __importDefault(require("../events/topicExpiredEvent"));
/**
* Topic Service.
*/
class TopicService {
constructor() {
/**
* The Topic Timeout IDs.
*
* @protected
*/
this.topicTimeoutIds = {};
}
/**
* Prepare Message for the MQTT Topic.
*
* @param aliceValue
* @param topicData
* @returns Promise<string>
*/
convertAliceValueToMqttMessage(aliceValue, topicData) {
return __awaiter(this, void 0, void 0, function* () {
let mqttMessage = '';
if (topicData) {
for (const [message, value] of Object.entries(topicData.messageValueMapping)) {
if (value === aliceValue && message !== '__default') {
mqttMessage = String(message).toLowerCase();
}
}
}
if (mqttMessage === '') {
switch (typeof aliceValue) {
case 'boolean':
mqttMessage = aliceValue ? 'on' : 'off';
break;
case 'object':
mqttMessage = JSON.stringify(aliceValue);
break;
default:
mqttMessage = String(aliceValue).toLowerCase();
}
}
const functionConvertAliceValueToMqttMessage = configProvider_1.default.getConfigOption('functionConvertAliceValueToMqttMessage');
if (typeof functionConvertAliceValueToMqttMessage === 'function') {
return (yield functionConvertAliceValueToMqttMessage(aliceValue, mqttMessage, topicData));
}
return mqttMessage;
});
}
/**
* Prepare Value for the Alice Device Capability State.
*
* @param mqttMessage
* @param topicData
* @returns Promise<any>
*/
convertMqttMessageToAliceValue(mqttMessage, topicData) {
return __awaiter(this, void 0, void 0, function* () {
let aliceValue = undefined;
const handledMessage = mqttMessage.toLowerCase();
if (topicData) {
if (topicData.messageValueMapping[handledMessage] !== undefined) {
aliceValue = topicData.messageValueMapping[handledMessage];
}
else if (topicData.messageValueMapping['__default'] !== undefined) {
aliceValue = topicData.messageValueMapping['__default'];
}
}
if (aliceValue === undefined) {
if (handledMessage === 'on' || handledMessage === 'off') {
aliceValue = handledMessage === 'on';
}
else if (!isNaN(Number(handledMessage))) {
aliceValue = Number(handledMessage);
}
else {
try {
const value = JSON.parse(mqttMessage);
if (value && typeof value === 'object') {
aliceValue = value;
}
}
catch (err) {
aliceValue = handledMessage.toString();
}
}
}
const functionConvertMqttMessageToAliceValue = configProvider_1.default.getConfigOption('functionConvertMqttMessageToAliceValue');
if (typeof functionConvertMqttMessageToAliceValue === 'function') {
return (yield functionConvertMqttMessageToAliceValue(mqttMessage, aliceValue, topicData));
}
return aliceValue;
});
}
/**
* Set Topic Message to the Cache.
*
* @param topic
* @param message
* @returns Promise<boolean>
*/
setTopicMessage(topic, message) {
return __awaiter(this, void 0, void 0, function* () {
const redisClient = yield redisProvider_1.default.getClientAsync();
const result = yield redisClient.hSet('topics', topic, message);
const topicLifetimes = [
{
topicType: 'availableTopic',
lifetimeSec: parseInt(process.env.TOPIC_AVAILABLE_CACHE_LIFETIME_SEC, 10),
},
{
topicType: 'commandTopic',
lifetimeSec: parseInt(process.env.TOPIC_COMMAND_CACHE_LIFETIME_SEC, 10),
},
{
topicType: 'stateTopic',
lifetimeSec: parseInt(process.env.TOPIC_STATE_CACHE_LIFETIME_SEC, 10),
},
];
for (const topicLifetime of topicLifetimes) {
if (!isNaN(topicLifetime.lifetimeSec)) {
if (yield mqttService_1.default.isTopicType(topic, topicLifetime.topicType)) {
yield redisClient.sendCommand(['HEXPIRE', 'topics', String(topicLifetime.lifetimeSec), 'FIELDS', '1', topic]);
// Event when the topic has disappeared from the Redis storage:
const self = this;
clearTimeout(this.topicTimeoutIds[topic]);
this.topicTimeoutIds[topic] = setTimeout(function () {
delete self.topicTimeoutIds[topic];
topicExpiredEvent_1.default.execute(topic, message).catch((err) => {
console.log('ERROR! Topic Expired Event.', err instanceof Error ? err.message : err);
});
}, topicLifetime.lifetimeSec * 1000 * 1.5);
break;
}
}
}
return result > 0;
});
}
/**
* Get Topic Message from the Cache.
*
* @param topic
* @returns Promise<string | undefined>
*/
getTopicMessage(topic) {
return __awaiter(this, void 0, void 0, function* () {
const redisClient = yield redisProvider_1.default.getClientAsync();
const value = yield redisClient.hGet('topics', topic);
return value !== undefined && value !== null ? value : undefined;
});
}
/**
* Get Value from the State Topic by Keys.
*
* @param topic
* @param keys
* @returns Promise<string | undefined>
*/
getStateTopicValueByKey(topic, keys) {
return __awaiter(this, void 0, void 0, function* () {
if (!topic.trim() || keys.length === 0) {
return undefined;
}
if (!(yield mqttService_1.default.isTopicType(topic, 'stateTopic'))) {
return undefined;
}
let result = undefined;
const message = yield this.getTopicMessage(topic);
if (message !== undefined) {
try {
const data = JSON.parse(message);
keys.forEach((key) => {
if (result === undefined) {
result = data[key] !== undefined ? String(data[key]) : undefined;
}
});
}
catch (err) {
return undefined;
}
}
return result;
});
}
/**
* Get State Topic changes.
*
* @param oldMessage
* @param newMessage
* @param deviceType
* @returns Promise<ChangedCommandTopicInterface[]>
*/
getStateTopicChanges(oldMessage_1, newMessage_1) {
return __awaiter(this, arguments, void 0, function* (oldMessage, newMessage, deviceType = '') {
const isStateTopicChecked = process.env.TOPIC_STATE_CHECK_IF_COMMAND_IS_UNDEFINED.trim() === '1';
if (!isStateTopicChecked) {
return [];
}
const result = [];
const configTopics = yield mqttRepository_1.default.getConfigTopics();
try {
const oldStateTopic = oldMessage ? JSON.parse(oldMessage) : {};
const newStateTopic = JSON.parse(newMessage);
for (const configTopic of configTopics) {
if (deviceType) {
if (configTopic.deviceType !== deviceType) {
continue;
}
}
for (const commandTopic of configTopic.commandTopics) {
for (const key of commandTopic.topicStateKeys || []) {
if (oldStateTopic[key] !== newStateTopic[key]) {
const item = Object.assign(commandTopic, {
deviceType: configTopic.deviceType,
mqttValueOld: oldStateTopic[key],
mqttValueNew: newStateTopic[key],
});
result.push(item);
}
}
}
}
}
catch (err) {
return [];
}
return result;
});
}
}
exports.default = new TopicService();