Skip to content

Commit

Permalink
feat(mqtt): Publish ValetudoGoToLocations & ValetudoZonePresets
Browse files Browse the repository at this point in the history
  • Loading branch information
Hypfer committed Dec 29, 2020
1 parent 1fde1b9 commit 76a9c52
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 98 deletions.
96 changes: 54 additions & 42 deletions lib/mqtt/MqttAutoConfManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,43 +18,8 @@ class MqttAutoConfManager { //TODO: does this thing even make sense?
this.identifier = options.identifier || "robot";
this.topicPrefix = options.topicPrefix || "valetudo";
this.autoconfPrefix = options.autoconfPrefix || "homeassistant";
}

get deviceSpecification() {
return {
manufacturer: this.robot.getManufacturer(),
model: this.robot.getModelName(),
name: this.identifier,
identifiers: [this.identifier],
sw_version: Tools.GET_VALETUDO_VERSION() + " (Valetudo)"
};
}

get supportedFeatures() {
const features = [
"battery",
"status"
];

//TODO: clean_spot, send_command

if (this.robot.capabilities[capabilities.BasicControlCapability.TYPE]) {
features.push("start", "pause", "stop", "return_home");
}

if (this.robot.capabilities[capabilities.LocateCapability.TYPE]) {
features.push("locate");
}

if (this.robot.capabilities[capabilities.FanSpeedControlCapability.TYPE]) {
features.push("fan_speed");
}


return features;
}

get topics() {
/**
* We're defining all of them, but not all of them will be used by all robots.
*
Expand All @@ -64,7 +29,7 @@ class MqttAutoConfManager { //TODO: does this thing even make sense?
* Unless of course V8 caches the result of that iteration but I don't think that
* it is that smart nor that one should rely on that.
*/
return {
this.internalTopics = {
availability: this.topicPrefix + "/" + this.identifier + "/status",
command: this.topicPrefix + "/" + this.identifier + "/command",
state: this.topicPrefix + "/" + this.identifier + "/state",
Expand All @@ -74,11 +39,8 @@ class MqttAutoConfManager { //TODO: does this thing even make sense?

map_data: this.topicPrefix + "/" + this.identifier + "/map_data"
};
}

get autoconfData() {
// noinspection UnnecessaryLocalVariableJS
const autoconfData = [
this.internalAutoconfData = [
{
topic: this.autoconfPrefix + "/vacuum/" + this.topicPrefix + "_" + this.identifier + "/config",
payload: {
Expand All @@ -94,13 +56,63 @@ class MqttAutoConfManager { //TODO: does this thing even make sense?
fan_speed_list: this.supportedFeatures.includes("fan_speed") ? this.robot.capabilities.FanSpeedControlCapability.getPresets() : undefined
}
}
//Since the map_data will kill the recorder: component, we can't add autoconfig for it (yet) :(
];


this.registeredTopics = {};
this.registeredAutoconfData = [];


}

get deviceSpecification() {
return {
manufacturer: this.robot.getManufacturer(),
model: this.robot.getModelName(),
name: this.identifier,
identifiers: [this.identifier],
sw_version: Tools.GET_VALETUDO_VERSION() + " (Valetudo)"
};
}

get supportedFeatures() {
const features = [
"battery",
"status"
];

//Since the map_data will kill the recorder: component, we can't add autoconfig for it (yet) :(

if (this.robot.capabilities[capabilities.BasicControlCapability.TYPE]) {
features.push("start", "pause", "stop", "return_home");
}

if (this.robot.capabilities[capabilities.LocateCapability.TYPE]) {
features.push("locate");
}

if (this.robot.capabilities[capabilities.FanSpeedControlCapability.TYPE]) {
features.push("fan_speed");
}


return features;
}

get topics() {
return Object.assign({}, this.registeredTopics, this.internalTopics);
}

get autoconfData() {
return [].concat(this.registeredAutoconfData, this.internalAutoconfData);
}

registerTopic(key, value) {
this.registeredTopics[key] = value;
}

return autoconfData;
registerAutoconfData(data) {
this.registeredAutoconfData.push(data);
}
}

Expand Down
75 changes: 41 additions & 34 deletions lib/mqtt/MqttClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const ValetudoMapSegment = require("../entities/core/ValetudoMapSegment");
/**
* This class will provide 2 + n Home Assistant Entities
* 1) A Vacuum entitiy containing the robot state
* 2) An entity containing the Map Data as Base64
* 2) An entity containing the Map Data (as Base64)
* n) An entity for an attribute
*/

Expand All @@ -31,6 +31,9 @@ class MqttClient {
this.config = options.config;
this.robot = options.robot;

this.stateAttributeHandlers = {};
this.capabilityAttributeHandlers = {};

this.loadConfig();
if (this.enabled) {
this.connect();
Expand All @@ -57,9 +60,6 @@ class MqttClient {
this.updateMapDataTopic(this.robot.state.map);
}
});

this.stateAttributeHandlers = {};
this.capabilityAttributeHandlers = {};
}

/**
Expand All @@ -76,7 +76,6 @@ class MqttClient {
});



this.enabled = mqttConfig.enabled;
this.server = mqttConfig.server;
this.port = mqttConfig.port || 1883;
Expand All @@ -92,6 +91,36 @@ class MqttClient {
this.attributesUpdateInterval = mqttConfig.attributesUpdateInterval || 60000;
this.provideMapData = mqttConfig.provideMapData !== undefined ? mqttConfig.provideMapData : true;
this.base64EncodeMapData = mqttConfig.base64EncodeMapData !== undefined ? mqttConfig.base64EncodeMapData : true;


this.registerCapabilityAttributeHandlers();
}

/**
* @private
*/
registerCapabilityAttributeHandlers() {
Object.values(this.robot.capabilities).forEach(robotCapability => {
const matchedHandler = CAPABILITY_TYPE_TO_HANDLER_MAPPING[robotCapability.getType()];

if (matchedHandler) {
const capabilityAttributeHandler = new matchedHandler({
capability: robotCapability
});

this.capabilityAttributeHandlers[robotCapability.getType()] = capabilityAttributeHandler;

this.autoConfManager.registerAutoconfData(
capabilityAttributeHandler.getAutoConfData({
topicPrefix: this.autoConfManager.topicPrefix,
autoconfPrefix: this.autoConfManager.autoconfPrefix,
availabilityTopic: this.autoConfManager.topics.availability,
deviceSpecification: this.autoConfManager.deviceSpecification,
identifier: this.autoConfManager.identifier
})
);
}
});
}

/**
Expand Down Expand Up @@ -287,35 +316,6 @@ class MqttClient {
* @private
*/
async pollCapabilityAttributes() {
const autoconfData = [];

if (this.robot.capabilities[capabilities.WifiConfigurationCapability.TYPE]) {
if (!this.capabilityAttributeHandlers[capabilities.WifiConfigurationCapability.TYPE]) {

this.capabilityAttributeHandlers[capabilities.WifiConfigurationCapability.TYPE] =
new attributeHandlers.capability.WifiConfigurationCapabilityBasedAttributeMqttHandler({
capability: this.robot.capabilities[capabilities.WifiConfigurationCapability.TYPE]
});

autoconfData.push(
this.capabilityAttributeHandlers[capabilities.WifiConfigurationCapability.TYPE].getAutoConfData({
topicPrefix: this.autoConfManager.topicPrefix,
autoconfPrefix: this.autoConfManager.autoconfPrefix,
availabilityTopic: this.autoConfManager.topics.availability,
deviceSpecification: this.autoConfManager.deviceSpecification,
identifier: this.autoConfManager.identifier
})
);
}
}


if (autoconfData.length > 0) {
autoconfData.forEach(data => {
this.publish(data.topic, data.payload);
});
}

for (const handler of Object.values(this.capabilityAttributeHandlers)) {
this.publish(
handler.getStateTopic({
Expand Down Expand Up @@ -647,4 +647,11 @@ MqttClient.CUSTOM_COMMANDS = {
SEGMENT_CLEANUP: "segment_cleanup"
};


const CAPABILITY_TYPE_TO_HANDLER_MAPPING = {
[capabilities.WifiConfigurationCapability.TYPE]: attributeHandlers.capability.WifiConfigurationCapabilityBasedAttributeMqttHandler,
[capabilities.ZoneCleaningCapability.TYPE]: attributeHandlers.capability.ZoneCleaningCapabilityBasedAttributeMqttHandler,
[capabilities.GoToLocationCapability.TYPE]: attributeHandlers.capability.GoToLocationCapabilityBasedAttributeMqttHandler
};

module.exports = MqttClient;
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
const CapabilityBasedAttributeMqttHandler = require("./CapabilityBasedAttributeMqttHandler");

class GoToLocationCapabilityBasedAttributeMqttHandler extends CapabilityBasedAttributeMqttHandler {
/**
* @public
*
* @param {object} options
* @param {string} options.topicPrefix
* @param {string} options.autoconfPrefix
* @param {string} options.identifier
* @param {object} options.deviceSpecification
* @param {string} options.availabilityTopic There's only one because there can only be one LWT
*/
getAutoConfData(options) {
return {
topic: options.autoconfPrefix + "/sensor/" + options.identifier + "/GoToLocationCapability/config",
payload: {
availability_topic: options.availabilityTopic,
device: options.deviceSpecification,
name: "ValetudoGoToLocations",
state_topic: this.getStateTopic({
topicPrefix: options.topicPrefix,
identifier: options.identifier
}),
unique_id: options.identifier + "_GoToLocationCapability",
icon: "mdi:map-marker-outline",
value_template: "{{value_json.state}}",
json_attributes_topic: this.getStateTopic({
topicPrefix: options.topicPrefix,
identifier: options.identifier
}),
json_attributes_template: "{{value_json.attributes | to_json}}"
}
};
}

/**
* @public
*
*
* @param {object} options
* @param {string} options.topicPrefix
* @param {string} options.identifier
*/
getStateTopic(options) {
return options.topicPrefix + "/" + options.identifier + "/GoToLocationCapability/presets";
}

/**
* @public
* @abstract
*
* @returns {Promise<Array | boolean | object | number | string>}
*/
async getPayload() {
const presetsFromConfig = this.capability.robot.config.get("goToLocationPresets") || {};

return {
state: Object.keys(presetsFromConfig).length,
attributes: {
presets: presetsFromConfig
}
};
}
}

module.exports = GoToLocationCapabilityBasedAttributeMqttHandler;
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
const CapabilityBasedAttributeMqttHandler = require("./CapabilityBasedAttributeMqttHandler");

class ZoneCleaningCapabilityBasedAttributeMqttHandler extends CapabilityBasedAttributeMqttHandler {
/**
* @public
*
* @param {object} options
* @param {string} options.topicPrefix
* @param {string} options.autoconfPrefix
* @param {string} options.identifier
* @param {object} options.deviceSpecification
* @param {string} options.availabilityTopic There's only one because there can only be one LWT
*/
getAutoConfData(options) {
return {
topic: options.autoconfPrefix + "/sensor/" + options.identifier + "/ZoneCleaningCapability/config",
payload: {
availability_topic: options.availabilityTopic,
device: options.deviceSpecification,
name: "ValetudoZonePresets",
state_topic: this.getStateTopic({
topicPrefix: options.topicPrefix,
identifier: options.identifier
}),
unique_id: options.identifier + "_ZoneCleaningCapability",
icon: "mdi:square-outline",
value_template: "{{value_json.state}}",
json_attributes_topic: this.getStateTopic({
topicPrefix: options.topicPrefix,
identifier: options.identifier
}),
json_attributes_template: "{{value_json.attributes | to_json}}"
}
};
}

/**
* @public
*
*
* @param {object} options
* @param {string} options.topicPrefix
* @param {string} options.identifier
*/
getStateTopic(options) {
return options.topicPrefix + "/" + options.identifier + "/ZoneCleaningCapability/presets";
}

/**
* @public
* @abstract
*
* @returns {Promise<Array | boolean | object | number | string>}
*/
async getPayload() {
const presetsFromConfig = this.capability.robot.config.get("zonePresets") || {};

return {
state: Object.keys(presetsFromConfig).length,
attributes: {
presets: presetsFromConfig
}
};
}
}

module.exports = ZoneCleaningCapabilityBasedAttributeMqttHandler;
6 changes: 4 additions & 2 deletions lib/mqtt/attributeHandlers/capability/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module.exports = {
"WifiConfigurationCapabilityBasedAttributeMqttHandler": require("./WifiConfigurationCapabilityBasedAttributeMqttHandler")
};
"WifiConfigurationCapabilityBasedAttributeMqttHandler": require("./WifiConfigurationCapabilityBasedAttributeMqttHandler"),
"ZoneCleaningCapabilityBasedAttributeMqttHandler": require("./ZoneCleaningCapabilityBasedAttributeMqttHandler"),
"GoToLocationCapabilityBasedAttributeMqttHandler": require("./GoToLocationCapabilityBasedAttributeMqttHandler")
};

0 comments on commit 76a9c52

Please sign in to comment.