Skip to content

Commit

Permalink
initial change over for websocket
Browse files Browse the repository at this point in the history
incorporated node-home-assistant into this repo
added home-assistant-js-websocket to handle the websocket connect
added in new home assistant long-lived access tokens with support for legacy api password
  • Loading branch information
zachowj committed Sep 16, 2018
1 parent 4b75d3e commit b838023
Show file tree
Hide file tree
Showing 28 changed files with 3,200 additions and 2,823 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ module.exports = {
'indent': [1, 4, {
VariableDeclarator: { var: 1, let: 1, const: 1 }
}],
'key-spacing': [1, { 'align': 'value' } ],
// 'key-spacing': [1, { 'align': 'value' } ],
'no-multi-spaces': [ 0, {
exceptions: { Property: true, VariableDeclarator: true, ImportDeclaration: true }
}],
Expand Down
102 changes: 51 additions & 51 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -1,51 +1,51 @@
{
// Use IntelliSense to learn about possible Node.js debug attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Attach Docker: node-red",
"type": "node",
"request": "attach",
"timeout": 20000,
"restart": true,
"address": "localhost",
"port": 9123,
"localRoot": "${workspaceFolder}",
"remoteRoot": "/data/node_modules/node-red-contrib-home-assistant",
"trace": false,
"skipFiles": [
"<node_internals>/**/*.js"
]
},

{
"name": "Attach Local: node-red",
"type": "node",
"request": "attach",
"timeout": 20000,
"restart": true,
"address": "localhost",
"port": 9123,
"trace": false,
"skipFiles": [
"<node_internals>/**/*.js"
]
},
{
"name": "Attach Local: test",
"type": "node",
"request": "attach",
"timeout": 20000,
"restart": true,
"address": "localhost",
"port": 9124,
"trace": false,

"skipFiles": [
"<node_internals>/**/*.js"
]
}
]
}
{
// Use IntelliSense to learn about possible Node.js debug attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Attach Docker: node-red",
"type": "node",
"request": "attach",
"timeout": 20000,
"restart": true,
"address": "localhost",
"port": 9123,
"localRoot": "${workspaceFolder}",
"remoteRoot": "/data/node_modules/node-red-contrib-home-assistant",
"trace": false,
"skipFiles": [
"<node_internals>/**/*.js"
]
},

{
"name": "Attach Local: node-red",
"type": "node",
"request": "attach",
"timeout": 20000,
"restart": true,
"address": "localhost",
"port": 9123,
"trace": false,
"skipFiles": [
"<node_internals>/**/*.js"
]
},
{
"name": "Attach Local: test",
"type": "node",
"request": "attach",
"timeout": 20000,
"restart": true,
"address": "localhost",
"port": 9124,
"trace": false,

"skipFiles": [
"<node_internals>/**/*.js"
]
}
]
}
21 changes: 1 addition & 20 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,2 @@
# v0.3.6
# v0.0.1
* Feat: Added <= and => comparison for Constraints and Outputs. Renamed greater_than and less_than to > and < but kept backwards compatibility
* Fix: Fixed label for outputs that was getting undefined tacked on to the front of the string.
* Fix: poll-state node now matches events-state output
* Fix: entity_id is no longer required on current-state

# v0.3.2
* Feat: History node now supports end date and entityid filtering
* Fix: Nodered configuration value userDir is fetched correctly now

# v0.3.1
* Fix/Feat: Current State Node: Preserve original message with options to override topic and payload (Thanks @declankenny)
* Fix: Added try/catch in onHaEventsStateChanged (Thanks @oskargronqvist)
* Docs: Added quotes to 'Call Service' placeholder text (Thanks @brubaked)
* Docs: Readme node version clarification (Thanks @Bodge-IT)
* Docs: Keyword discoverability addition (Thanks @jcullen86)
* Docs: Note of server URL when running inside container (Thanks @drogfild)
* Dev: Updated docker dev environment to latest, easier setup and run, added some tests within node-red sample flows
* Chore: Added LICENSE.md
* Chore: Added Feature and Bug templates
* Chore: Added CONTRIBUTING.md
2 changes: 1 addition & 1 deletion docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ services:
build: ./node-red
command: npm run dev:watch
volumes:
- '..:/data/node_modules/-websocket'
- '..:/data/node_modules/node-red-contrib-home-assistant-websocket'
# - './node-red/root-fs/data/flows.json:/data/flows.json' # Map flows file for easy commitable examples building
ports:
- 1880:1880
Expand Down
2 changes: 1 addition & 1 deletion docker/home-assistant/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM homeassistant/home-assistant:0.67.1
FROM homeassistant/home-assistant:0.77.3

ENV HA_HOME_ASSISTANT_TIME_ZONE=America/New_York \
HA_HOME_ASSISTANT_LATITUDE=42.360082 \
Expand Down
3 changes: 3 additions & 0 deletions docker/home-assistant/root-fs/config/configuration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ homeassistant:
temperature_unit: !env_var HA_HOME_ASSISTANT_TEMPERATURE_UNIT
time_zone: !env_var HA_HOME_ASSISTANT_TIME_ZONE
unit_system: !env_var HA_HOME_ASSISTANT_UNIT_SYSTEM
auth_providers:
- type: homeassistant
- type: legacy_api_password

http:
api_password: !env_var HA_HTTP_API_PASSWORD
Expand Down
3 changes: 2 additions & 1 deletion docker/node-red/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ RUN npm install

# Install Useful Home Automation Nodes
RUN npm install \
node-red-contrib-bigstatus
node-red-contrib-bigstatus \
home-assistant-js-websocket

# User configuration directory volume
EXPOSE 1880
Expand Down
18 changes: 11 additions & 7 deletions lib/base-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const utils = {
selectn,
merge,
Joi,
reach: (path, obj) => selectn(path, obj),
reach: (path, obj) => selectn(path, obj),
formatDate: (date) => dateFns.format(date, 'ddd, h:mm:ss A'),
toCamelCase(str) {
return str.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, function(match, index) {
Expand All @@ -23,11 +23,11 @@ const utils = {
const DEFAULT_OPTIONS = {
config: {
debugenabled: {},
name: {},
server: { isNode: true }
name: {},
server: { isNode: true }
},
input: {
topic: { messageProp: 'topic' },
topic: { messageProp: 'topic' },
payload: { messageProp: 'payload' }
}
};
Expand Down Expand Up @@ -119,7 +119,7 @@ class BaseNode {

setConnectionStatus(isConnected, additionalText) {
let connectionStatus = isConnected
? { shape: 'dot', fill: 'green', text: 'connected' }
? { shape: 'dot', fill: 'green', text: 'connected' }
: { shape: 'ring', fill: 'red', text: 'disconnected' };
if (this.hasOwnProperty('isenabled') && this.isenabled === false) {
connectionStatus.text += '(DISABLED)';
Expand All @@ -135,7 +135,7 @@ class BaseNode {
if (!this.nodeConfig.debugenabled) return;
for (let msg of arguments) {
const debugMsgObj = {
id: this.id,
id: this.id,
name: this.name || '',
msg
};
Expand All @@ -148,7 +148,7 @@ class BaseNode {
}

get eventsClient() {
return this.reach('nodeConfig.server.events');
return this.reach('nodeConfig.server.websocket');
}

get isConnected() {
Expand All @@ -160,6 +160,10 @@ class BaseNode {
reach(path) {
return selectn(path, this);
}

getPrettyDate() {
return new Date().toLocaleDateString('en-US', {month: 'short', day: 'numeric', hour12: false, hour: 'numeric', minute: 'numeric'});
}
}

const _internals = {
Expand Down
4 changes: 2 additions & 2 deletions lib/events-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ const merge = require('lodash.merge');
const BaseNode = require('./base-node');

const DEFAULT_NODE_OPTIONS = {
debug: false,
debug: false,
config: {
name: {},
name: {},
server: { isNode: true }
}
};
Expand Down
66 changes: 13 additions & 53 deletions lib/ha-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,72 +6,32 @@ const debug = require('debug')('home-assistant:api');
class HaApi {
constructor(config) {
this.config = config;

const apiOpts = { baseURL: config.baseUrl + '/api' };
apiOpts.headers = (config.apiPass)
? { 'x-ha-access': config.apiPass }
: {};
apiOpts.headers = (config.legacy) ? ((config.apiPass) ? { 'x-ha-access': config.apiPass } : {})
: { 'Authorization': 'Bearer ' + config.apiPass };

this.client = axios.create(apiOpts);
}

callService(domain, service, data) {
return this._post(`/services/${domain}/${service}`, data);
}

getServices() {
return this._get('services');
}

getEvents() {
return this._get('events');
}

getStates (entity_id, { include, exclude } = {}) {
const path = (entity_id) ? `states/${entity_id}` : 'states';

return this._get(path)
.then(states => {
// If passing in an entity_id will only have one result, just return formatted
if (!states.length) { return { [states.entity_id]: states } }

return states.reduce((acc, state) => {
if (utils.shouldInclude(state.entity_id, include, exclude)) {
acc[state.entity_id] = state;
}
return acc;
}, {});
});
}

getEntities(entity_id, { include, exclude } = {}) {
return this.getStates(entity_id, { include, exclude })
.then(states => {
const entities = Object.keys(states);

return entities.sort()
});
}

getHistory(timestamp, filterEntityId, endTimestamp, { include, exclude, flatten } = {}) {
let path = 'history/period';

if (timestamp) { path = path + `/${timestamp}`; }
if (timestamp) { path = path + `/${timestamp}` }
let params = {};
if (filterEntityId) { params.filter_entity_id = filterEntityId; }
if (endTimestamp) { params.end_time = endTimestamp; }
if (filterEntityId) { params.filter_entity_id = filterEntityId }
if (endTimestamp) { params.end_time = endTimestamp }

// History returns an array for each entity_id and that array containes objects for each history item
return this._get(path, params)
.then(result => {
if (!include && !exclude && !flatten) { return result; }
if (!include && !exclude && !flatten) { return result }

// Filter out results by regex, include/exclude should already be an instance of RegEx
if (include || exclude) {
result = result.reduce((acc, entityArr) => {
const entityId = (entityArr && entityArr.length > 0) ? entityArr[0].entity_id : null;

if (entityId && utils.shouldInclude(entityId, include, exclude)) { acc.push(entityArr); }
if (entityId && utils.shouldInclude(entityId, include, exclude)) { acc.push(entityArr) }
return acc;
}, []);
}
Expand All @@ -84,9 +44,9 @@ class HaApi {
acc = acc.concat(entityArray);
return acc;
}, [])
.sort((a,b) => {
if (a.last_updated < b.last_updated) { return -1; }
if (a.last_updated > b.last_updated) { return 1; }
.sort((a, b) => {
if (a.last_updated < b.last_updated) { return -1 }
if (a.last_updated > b.last_updated) { return 1 }
return 0;
});
}
Expand All @@ -95,13 +55,13 @@ class HaApi {
});
}

getConfig(){
getConfig() {
return this._get('config');
}
getDiscoveryInfo(){
getDiscoveryInfo() {
return this._get('discovery_info');
}
getErrorLog(){
getErrorLog() {
return this._get('error_log');
}
// TODO: Test, entity_id should be `camera.some_cam_entity`
Expand Down
Loading

0 comments on commit b838023

Please sign in to comment.