Skip to content
This repository has been archived by the owner on Nov 21, 2024. It is now read-only.

hwl-api v1.1 #6

Merged
merged 3 commits into from
Oct 13, 2020
Merged
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
55 changes: 33 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,40 @@ HomeWizard Lite Proxy API, built for integrating cheap HomeWizard Lite smartplug
$ npm start
$ node index.js

The API depends on environment variables for authentication with HomeWizard API.

| Key | Value |
|--|--|
| PORT | Port (default 3000) |
| HWL_USERNAME | HomeWizard Lite App Username |
| HWL_PASSWORD | HomeWizard Lite App Password |
| LOGLEVEL | [Tracer Logger Level](https://github.com/baryon/tracer#customize-output-format) (default trace) |
The API depends on environment variables. **Make sure to configure the bold variables, as these have no default**.

| Key | Type | Value |
|--|--|--|
| PORT | number | Port on which the API will listen (default: 3000) |
| | | |
| **HWL_USERNAME** | string | HomeWizard Lite App Username |
| **HWL_PASSWORD** | string | HomeWizard Lite App Password |
| | | |
| **SMARTPLUG_ID** | string | ID of the Internet Connected Smartplug. GET this ID from /api/smartplug |
| MIN_DIMMING_VALUE | number | The API supports dimmers. Some dimmers have a certain minimum threshold. When you request the API to go below the minimum dimming value, it'll instead turn off the light. (default: 1) |
| | | |
| CACHE_TTL | number | Default expiration time for cache objects in seconds. Currently only in use for caching session-tokens from HWL. (default: 1800)
| LOGLEVEL | string | [Tracer Logger Level](https://github.com/baryon/tracer#customize-output-format) (default: warn) |
| | | |

## Deploying
The API should be deployed on a local network, because it has no built-in authentication. It is always possible to protect the API with HTTP Basic Auth and make it accessible over the internet, but take note of the insecurities of HTTP Basic Auth.

The API could also be deployed in a docker container

## Requests
|Method | URL | Description |
| -- | -- | -- |
| GET | /api/test/session | Returns valid session key (Only necessary for testing)* |
| GET | /api/test/communication | Returns status 200 when API can connect to Google Test API to verify internet connectivity |
| -- | -- | -- |
| GET | /api/plug | Returns all registered plugs |
| GET | /api/act/smartplug/:smartPlugID/plug/:plugID | Returns `true` or `false` for is_active state of a plug specified by plugID |
| POST | /api/act/smartplug/:smartPlugID/plug/:plugID | Switches plug when request body contains `"action": "on"` or `"action": "off"`. Returns is_active state.

* The API should be deployed on a local network and shouldn't be accessible over the interwebs, because it currently has no built-in authentication. It is always possible to configure [HTTP Basic Authentication](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/).
* The API should have access to homewizard.com and its subdomains.

Using the included Dockerfile, it is also possible to run this API inside a container.

## Endpoints
| Method | URL | Description | Example Request | Example Response |
| -- | -- | -- | -- | -- |
| GET | /api/test/session | Returns valid session key (Only necessary for testing) | | `{ "session": "string"` } |
| GET | /api/test/communication | Returns status 200 when API can connect to Google to verify internet connectivity |
| | | |
| GET | /api/plug | Returns all registered plugs | | * |
| GET | /api/smartplug | Returns info about your internet connected smartplug. (Tip: Use this to determine the ID of your smartplug. Use the `id` property.) | | * |
| GET | /api/plug/:plugID | Returns `true` or `false` for is_active state of a plug specified by plugID | | `{ "is_active": false }` |
| POST | /api/plug/:plugID | [DIMMER] Change dimmer state when request body contains `"type": "dimmer"` and `"value": number`. When value is below `MIN_DIMMING_VALUE`, the dimmer will switch off. Returns `is_active state`. | `{ "type": "dimmer", "value": 75 }` | `{ "is_active": true }` |
| | | [SWITCH] Switches plug when request body contains `"type": "switch"` and `"value": "on/off"`. Returns `is_active` state |`{ "type": "switch", "value": "on" }` | `{ "is_active": true }` |

`*` Try this yourself. Response contains a bunch of useful info and does not fit needly in this table.
## Contribution
This project was started by an IT-student as a small 2 day project to improve home-automation by enabling Home Assistant to control HomeWizard Lite plugs. Development will continue when I need more features. Contribution is appreciated.
2 changes: 0 additions & 2 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ const express = require('express');
const cors = require('cors');
const bodyParser = require('body-parser');
const morgan = require("morgan");
const config = require('./config/config');
const logger = require('./config/config').logger

// Import models
const ApiResponse = require('./models/ApiResponse');
Expand Down
88 changes: 40 additions & 48 deletions auth/authentication_manager.js
Original file line number Diff line number Diff line change
@@ -1,56 +1,48 @@
const logger = require('../config/config').logger
const config = require('../config/config');
var sha1 = require('sha1');
const axios = require('axios');
const logger = require("../config/config").logger;
const config = require("../config/config");
var sha1 = require("sha1");
const axios = require("axios");
const NodeCache = require("node-cache");

module.exports = {
const cache = new NodeCache({ stdTTL: config.cacheTTL });

module.exports = {
// Get Session Key from HomeWizard Lite
getSessionKey() {

// Get Username and Password from config file
let username = config.hwlUsername;
let password = sha1(config.hwlPassword);
logger.debug(username);
logger.debug(password);

// Login to HWL using user credentials
return axios({
method: 'get',
url: 'https://cloud.homewizard.com/account/login',
// Check if sessionkey is cached
let sessionKey = cache.get("session");

if (sessionKey) {
logger.debug(`Cached session key found: ${sessionKey}`);
return Promise.resolve(sessionKey);
} else {
// Session key is not found in cache
let username = config.hwlUsername;
let password = sha1(config.hwlPassword);
logger.debug(username);
logger.debug(password);

return axios({
method: "get",
url: "https://cloud.homewizard.com/account/login",
auth: {
username: username,
password: password
}
password: password,
},
})
.then(response => {

// Check status
if (response.data.status === "ok") {

// Authentication passed, returning session token
logger.debug(response.data.session);
.then((response) => {
logger.debug(
`Received session key: ${response.data.session}`
);
cache.set("session", response.data.session);
return response.data.session;

} else if (response.data.status === "failed" && response.data.error === 110) {

// Authentication failed, returning FAILED
logger.error("HWL returned invalid credentials! Check credentials for validity!")
logger.debug(response.data);
return "ERROR";

} else {

// Authentication failed, but with unknown reason
logger.error("HWL returned unknown authentication error. ERROR :", response.data);
return "ERROR";
}
})
.catch(e => {
// Cannot communicate with HWL, returning error
logger.error("Failed to communicate with HWL! ERROR: ", e.message);
return "ERROR";
});
}

}
})
.catch((e) => {
// Exception occured while trying to communicate with HWL.
logger.error("Can't get session key from HW");
logger.error(e.response);
return Promise.reject("Can't get session key from HW. Check logs");
});
}
},
};
35 changes: 20 additions & 15 deletions config/config.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,33 @@
// Set debug level for tracer
const loglevel = process.env.LOGLEVEL || 'trace'
const loglevel = process.env.LOGLEVEL || "warn";

module.exports = {

// Web port where the server will listen to
"webPort": process.env.PORT || 3000,
"authKey": process.env.AUTHKEY || "",
webPort: process.env.PORT || 3000,

// Authentication for Homewizard Lite API
"hwlUsername": process.env.HWL_USERNAME || "",
"hwlPassword": process.env.HWL_PASSWORD || "",
hwlUsername: process.env.HWL_USERNAME || "",
hwlPassword: process.env.HWL_PASSWORD || "",

// SmartPlugID
// Should be returned from HW when you GET on /api/smartplug
smartPlugId:
process.env.SMARTPLUG_ID || "",

// Dimmer Ranges
// Some dimmers only support dimming up to a certain amount.
minDimmingValue: process.env.MIN_DIMMING_VALUE || 1,

// Cache Time To Live
cacheTTL: process.env.CACHE_TTL || 1800,

// Tracer for logging purposes
logger: require('tracer')
.console({
format: [
"{{timestamp}} <{{title}}> {{file}}:{{line}} : {{message}}"
],
logger: require("tracer").console({
format: ["{{timestamp}} <{{title}}> {{file}}:{{line}} : {{message}}"],
preprocess: function (data) {
data.title = data.title.toUpperCase();
},
dateformat: "isoUtcDateTime",
level: loglevel
})

}
level: loglevel,
}),
};
Loading