Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for MQTTS certificates #16

Open
wants to merge 3 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 41 additions & 24 deletions alexa.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,48 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="alexa-smart-home-v3-conf">
<script type="text/html" data-template-name="alexa-smart-home-v3-conf">
<div class="form-row">
<label for="node-config-input-username" style="width:130px"><i class="icon-tag"></i> Username</label>
<input type="text" id="node-config-input-username">
</div>
<div class="form-row">
<label for="node-config-input-password" style="width:130px"><i class="icon-tag"></i> Password</label>
<input type="password" id="node-config-input-password">
</div>
<div class="form-row">
<label for="node-config-input-mqttserver" style="width:130px"><i class="icon-tag"></i> MQTT Hostname</label>
<input type="text" id="node-config-input-mqttserver">
</div>
<div class="form-row">
<label for="node-config-input-webapiurl" style="width:130px"><i class="icon-tag"></i> Web API Hostname</label>
<input type="text" id="node-config-input-webapiurl">
</div>
<div class="form-row" title="Select the Node-RED context store to use with this Node.">
<label for="node-config-input-contextName" style="width:130px"><i class="icon-tag"></i> Context Store</label>
<select id="node-config-input-contextName"></select>
<div id="contextName-hint"></div>
<br />
<div class="form-tips">
This node requires a Node-RED memory-based <a href='https://nodered.org/docs/user-guide/context' target="_blank">context store</a>.
</div>
</div>
</div>
<div class="form-row">
<label for="node-config-input-mqttserver" style="width:130px"><i class="icon-tag"></i> MQTT Hostname</label>
<input type="text" id="node-config-input-mqttserver">
</div>
<div class="form-row">
<label for="node-config-input-mqttport" style="width:130px"><i class="icon-tag"></i> MQTT Port</label>
<input type="text" id="node-config-input-mqttport" placeholder="1883">
</div>
<div class="form-row">
<label for="node-config-input-mqttca" style="width:130px"><i class="icon-tag"></i> MQTT CA</label>
<input type="text" id="node-config-input-mqttca" placeholder="optional, only for mqtts">
</div>
<div class="form-row">
<label for="node-config-input-mqttcert" style="width:130px"><i class="icon-tag"></i> MQTT Cert</label>
<input type="text" id="node-config-input-mqttcert" placeholder="optional, only for mqtts">
</div>
<div class="form-row">
<label for="node-config-input-mqttkey" style="width:130px"><i class="icon-tag"></i> MQTT Key</label>
<input type="text" id="node-config-input-mqttkey" placeholder="optional, only for mqtts">
</div>
<div class="form-row">
<label for="node-config-input-webapiurl" style="width:130px"><i class="icon-tag"></i> Web API Hostname</label>
<input type="text" id="node-config-input-webapiurl">
</div>
<div class="form-row" title="Select the Node-RED context store to use with this Node.">
<label for="node-config-input-contextName" style="width:130px"><i class="icon-tag"></i> Context Store</label>
<select id="node-config-input-contextName"></select>
<div id="contextName-hint"></div>
<br />
<div class="form-tips">
This node requires a Node-RED memory-based <a href='https://nodered.org/docs/user-guide/context' target="_blank">context store</a>.
</div>
</div>
</script>

<script type="text/x-red" data-help-name="alexa-smart-home-v3-conf">
Expand All @@ -50,6 +66,10 @@
defaults: {
username: {},
mqttserver: { value: "mq-red.cb-net.co.uk" },
mqttport: { value: "1883" },
mqttca: { },
mqttcert: { },
mqttkey: { },
webapiurl: { value: "red.cb-net.co.uk" },
contextName: { value: "default" },
},
Expand Down Expand Up @@ -387,10 +407,7 @@

<!-- ############################################################################################## -->

<script
type="text/x-red"
data-template-name="alexa-smart-home-v3-resp"
></script>
<script type="text/x-red" data-template-name="alexa-smart-home-v3-resp"></script>

<script type="text/x-red" data-help-name="alexa-smart-home-v3-resp">
<p>This node sends a response to Alexa to acknowledge if the command
Expand All @@ -414,4 +431,4 @@
return this.name || "Alexa Smart Home v3 Response";
},
});
</script>
</script>
72 changes: 38 additions & 34 deletions alexa.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
module.exports = function (RED) {
"use strict";
var mqtt = require("mqtt");
var fs = require("fs");
var bodyParser = require("body-parser");
var devices = {};
const https = require("https");
Expand All @@ -43,6 +44,10 @@ module.exports = function (RED) {
this.username = this.credentials.username || n.username; // enable transition to credential store for username
this.password = this.credentials.password;
this.mqttserver = n.mqttserver;
this.mqttport = n.mqttport;
this.mqttca = n.mqttca;
this.mqttcert = n.mqttcert;
this.mqttkey = n.mqttkey;
this.webapiurl = n.webapiurl;
this.contextName = n.contextName || "memory"; // enable transition to user-configurable context storage
this.users = {};
Expand All @@ -55,41 +60,40 @@ module.exports = function (RED) {
password: node.password,
//clientId: node.username,
clientId: node.username + "-" + clientId,
reconnectPeriod: 5000,
servers: [
{
protocol: "mqtts",
host: node.mqttserver,
port: 8883,
},
{
protocol: "mqtt",
host: node.mqttserver,
port: 1883,
},
],
reconnectPeriod: 5000,
protocol: node.mqttca !== "" ? "mqtts" : "mqtt",
host: node.mqttserver,
port: node.mqttport || (node.mqttca !== "" ? 8883 : 1883)
};

if (node.mqttca) {
options = Object.assign(options, {
ca: fs.readFileSync(node.mqttca),
key: fs.readFileSync(node.mqttkey),
cert: fs.readFileSync(node.mqttcert),
});
}

getDevices(node.webapiurl, node.username, node.password, node.id);

this.connect = function () {
// Log version to console to assist future debugging
node.log("Node-RED contrib version: v" + packageJson.version);
node.log(
"Connecting to Alexa/ Google Home Skill MQTT server: " +
node.mqttserver +
", account username: " +
node.username
node.mqttserver +
", account username: " +
node.username
);
node.client = mqtt.connect(options);
node.client.setMaxListeners(0);

node.client.on("connect", function () {
node.log(
"Successfully connected to Alexa/ Google Home Skill MQTT server: " +
node.mqttserver +
", account username: " +
node.username
node.mqttserver +
", account username: " +
node.username
);
node.setStatus({ text: "connected", shape: "dot", fill: "green" });
node.client.removeAllListeners("message");
Expand Down Expand Up @@ -145,9 +149,9 @@ module.exports = function (RED) {
node.client.on("reconnect", function () {
node.warn(
"Re-connecting to Alexa/ Google Home Skill MQTT server: " +
node.mqttserver +
", account username: " +
node.username
node.mqttserver +
", account username: " +
node.username
);
node.setStatus({ text: "reconnecting", shape: "ring", fill: "red" });
});
Expand Down Expand Up @@ -241,10 +245,10 @@ module.exports = function (RED) {
var topic = "state/" + node.username + "/" + endpointId;
node.log(
deviceName +
" : sending state update, topic:" +
topic +
" message:" +
JSON.stringify(response)
" : sending state update, topic:" +
topic +
" message:" +
JSON.stringify(response)
);

if (node.client && node.client.connected) {
Expand Down Expand Up @@ -714,7 +718,7 @@ module.exports = function (RED) {
) {
node.log(
"Timer throttled/ deleted state update: " +
keys[nodeContext.get("tmpKey", node.contextName)]
keys[nodeContext.get("tmpKey", node.contextName)]
);
delete onGoingCommands[
keys[nodeContext.get("tmpKey", node.contextName)]
Expand Down Expand Up @@ -815,14 +819,14 @@ module.exports = function (RED) {
if (arrayStatelessCommands.indexOf(msg.command) > -1) {
node.log(
node.name +
" state node: 'stateless' command received, dropping message (expected for specific commands)."
" state node: 'stateless' command received, dropping message (expected for specific commands)."
);
statelessCommand = true;
} else if (msg.command) {
node.warn(
node.name +
" state node: message object includes unexpected or invalid msg.command, please remove this from payload: " +
msg.command
" state node: message object includes unexpected or invalid msg.command, please remove this from payload: " +
msg.command
);
}
}
Expand All @@ -845,7 +849,7 @@ module.exports = function (RED) {
// Duplicate Payload to last payload received, discard unless an adjustment payload which is likely to be duplicate
if (
JSON.stringify(nodeContext.get("lastPayload", node.contextName)) ==
JSON.stringify(msg.payload) &&
JSON.stringify(msg.payload) &&
!(
msg.payload.state.hasOwnProperty("percentageDelta") ||
msg.payload.state.hasOwnProperty("targetSetpointDelta") ||
Expand Down Expand Up @@ -919,7 +923,7 @@ module.exports = function (RED) {
) {
node.warn(
node.name +
" state node: you cannot send combined 'colorTemperatrure' and 'color' state updates, send most recent update/ change only"
" state node: you cannot send combined 'colorTemperatrure' and 'color' state updates, send most recent update/ change only"
);
stateValid = false;
}
Expand Down Expand Up @@ -1103,13 +1107,13 @@ module.exports = function (RED) {
// Either auto-acknowledge is enabled on sender node, or validation has taken place
node.warn(
node.name +
" state node: valid state update but msg.payload.acknowledge is false/ invalid/ missing"
" state node: valid state update but msg.payload.acknowledge is false/ invalid/ missing"
);
} else {
// State update not valid, logic above will explain why
node.warn(
node.name +
" state node: msg.payload.state not valid, check data types (numbers are not strings etc.) / format of state element"
" state node: msg.payload.state not valid, check data types (numbers are not strings etc.) / format of state element"
);
}
}
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "node-red-contrib-alexa-smart-home",
"version": "0.4.68",
"version": "0.5.0",
"description": "Integrate Alexa and Google Assistant / Google Home in your Node-RED flows.",
"main": "index.js",
"scripts": {
Expand Down Expand Up @@ -29,6 +29,9 @@
}
},
"author": "chrismbradford@gmail.com",
"contributors": [
"naimo84 <git@naimo84.dev>"
],
"license": "Apache-2.0",
"dependencies": {
"body-parser": "^1.19.0",
Expand Down