Skip to content

Commit 746e7b6

Browse files
committed
Initial release commit
0 parents  commit 746e7b6

7 files changed

+333
-0
lines changed

LICENSE.md

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
The node-raspi-rcswitch-api is licensed under the MIT License.
2+
3+
>Copyright 2017: Chris Klinger
4+
>
5+
>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6+
>
7+
>The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8+
>
9+
>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10+
11+
The node-raspi-rcswitch-api uses the following libraries and frameworks, which have their own licenses:
12+
13+
- [bunyan-winston-adapter](https://www.npmjs.com/package/bunyan-winston-adapter) [MIT]
14+
- [rcswitch](https://www.npmjs.com/package/rcswitch) [GPL-2.0]
15+
- [restify](https://www.npmjs.com/package/restify) [MIT]
16+
- [restify-errors](https://www.npmjs.com/package/restify-errors) [MIT]
17+
- [winston](https://www.npmjs.com/package/winston) [MIT]

README.md

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
node-raspi-rcswitch-api
2+
=======================
3+
4+
RESTful API for the [node-rcswitch](https://github.com/marvinroger/node-rcswitch) binding based on Node.js/restify.
5+
Allows controlling of 433Mhz RC remote controlled power sockets with the raspberry-pi using HTTP Protocol.
6+
7+
## Requirements
8+
9+
* To use node-rcswitch, [WiringPi](https://projects.drogon.net/raspberry-pi/wiringpi/download-and-install/) must be installed in order to compile.
10+
* Tthe data and power Pins of the transmitter must be connected to the Raspberry Pi.
11+
12+
## Installation
13+
```bash
14+
$ npm install node-raspi-rcswitch-api
15+
```
16+
17+
## Configuration
18+
19+
* `transmitter_pin` defines the GIPO pin on which the transmitter is connected to the Raspberry Pi. Note the number of the WiringPi data Pin. (see http://wiringpi.com/pins/)
20+
* `retries` number of times the signal is send (optional)
21+
22+
## Usage
23+
### Starting
24+
```bash
25+
$ npm start
26+
27+
> [email protected] start /home/pi/node-raspi-rcswitch-api
28+
> node daemon.js start
29+
30+
raspi-rcswitch-api Server started. PID: 9082
31+
raspi-rcswitch-api listening at port 3000
32+
```
33+
34+
The server is running as deamon using [daemonize2](https://github.com/niegowski/node-daemonize2/) by default. As alternative you can start with
35+
```bash
36+
$ node server.js
37+
```
38+
39+
### Stopping
40+
```bash
41+
$ npm stop
42+
```
43+
44+
### Direct device access
45+
http://host:port/api/v1/switch/systemCode/unitCode/state
46+
47+
* `systemCode` five character long binary system code identifying the rc switch system.
48+
* `systemCode` integer number between 1 and 4 identifying the power socket number in the system.
49+
* `state` can be either `on` or `off` for the target state of the power socket.
50+
51+
For example a GET call to `http://host:port/api/v1/switch/10101/2/on` will switch on the second power socket of the system `10101`.
52+
53+
### Mapped device access
54+
You can specify named devices in the `device_config.json` file to gain quick access to them.
55+
56+
The following example provides access the power socket with the system code `01001` and the unit code `1` under the name `Living_Room_Ambient_Light`.
57+
```json
58+
{
59+
"Living_Room_Ambient_Light": {
60+
"systemCode": "01001",
61+
"unitCode": 1
62+
}
63+
}
64+
```
65+
66+
Now you can quickly access the power plug using http://host:port/api/v1/switch/Living_Room_Ambient_Light/state. For the `state` and the config values the same restrictions as mentioned in 'Direct device access' section apply.
67+
68+
## License
69+
Copyright (c) 2017 Chris Klinger. Licensed under MIT license, see [LICENSE](LICENSE) for the full license.
70+
71+
## Bugs
72+
See <https://github.com/c-klinger/node-raspi-rcswitch-api/issues>.

config.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
'use strict'
2+
3+
module.exports = {
4+
name: 'raspi-rcswitch-api',
5+
version: '0.1.0',
6+
env: process.env.NODE_ENV || 'development',
7+
port: process.env.PORT || 3000,
8+
base_url: process.env.BASE_URL || 'http://localhost:3000',
9+
transmitter_pin: 0,
10+
retries: 5
11+
}

daemon.js

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
'use strict'
2+
3+
const config = require('./config');
4+
const daemonize = require("daemonize2");
5+
6+
var daemon = daemonize.setup({
7+
main: 'server.js',
8+
name: 'raspi-rcswitch-api',
9+
pidfile: 'raspircswitchapi.pid',
10+
silent: true
11+
});
12+
13+
switch (process.argv[2]) {
14+
15+
case "start":
16+
daemon.start();
17+
break;
18+
19+
case "stop":
20+
daemon.stop();
21+
break;
22+
23+
default:
24+
console.log("Usage: [start|stop]");
25+
}
26+
27+
daemon.on("started", function(pid) {
28+
console.log("raspi-rcswitch-api Server started. PID: " + pid);
29+
console.log('raspi-rcswitch-api listening on port ' + config.port);
30+
});

device_config.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"Living_Room_Ambient_Light": {
3+
"systemCode": "01001",
4+
"unitCode": 1
5+
}
6+
}

package.json

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"name": "raspi-rcswitch-api",
3+
"version": "0.1.0",
4+
"description": "RESTful API for the node-rcswitch binding based on Node.js/restify.",
5+
"main": "server.js",
6+
"engines": {
7+
"node": ">=4.0.0"
8+
},
9+
"scripts": {
10+
"start": "node daemon.js start",
11+
"stop": "node daemon.js stop",
12+
"test": "echo \"Error: no test specified\" && exit 1"
13+
},
14+
"repository": {
15+
"type": "git",
16+
"url": "git://github.com/c-klinger/node-raspi-rcswitch-api.git"
17+
},
18+
"keywords": [
19+
"rpi",
20+
"raspberrypi",
21+
"rcswitch",
22+
"433",
23+
"rf",
24+
"wireless",
25+
"power",
26+
"outlet",
27+
"rest",
28+
"restful",
29+
"api",
30+
"homeautomation"
31+
],
32+
"dependencies": {
33+
"bunyan-winston-adapter": "^0.2.0",
34+
"daemonize2": "^0.4.2",
35+
"rcswitch": "^0.3.1",
36+
"restify": "^5.0.0",
37+
"restify-errors": "^4.3.0",
38+
"winston": "^2.3.1"
39+
},
40+
"author": "Chris Klinger",
41+
"license": "MIT",
42+
"bugs": {
43+
"url": "https://github.com/c-klinger/node-raspi-rcswitch-api/issues"
44+
},
45+
"homepage": "https://github.com/c-klinger/node-raspi-rcswitch-api/"
46+
}

server.js

+151
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
'use strict'
2+
3+
/**
4+
* Module Dependencies
5+
*/
6+
const config = require('./config');
7+
const fs = require('fs');
8+
const restify = require('restify');
9+
const errors = require('restify-errors');
10+
const bunyan = require('bunyan');
11+
const winston = require('winston');
12+
const bunyanWinston = require('bunyan-winston-adapter');
13+
const rcswitch = require('rcswitch');
14+
const path = require('path');
15+
16+
/**
17+
* Logging
18+
*/
19+
// workaround for https://github.com/winstonjs/winston/issues/875
20+
if (!fs.existsSync(path.join(__dirname, "logs"))) {
21+
fs.mkdirSync(path.join(__dirname, "logs"));
22+
}
23+
24+
global.logger = new winston.Logger({
25+
transports: [
26+
new winston.transports.File({
27+
filename: path.join(__dirname, "logs", "info.log"),
28+
level: 'info',
29+
timestamp: true
30+
})
31+
]
32+
})
33+
34+
/**
35+
* Initialize Server
36+
*/
37+
global.server = restify.createServer({
38+
name : config.name,
39+
version : config.version,
40+
log : bunyanWinston.createAdapter(logger),
41+
})
42+
43+
/**
44+
* Initialize 443mhz Transmitter
45+
*/
46+
rcswitch.enableTransmit(config.transmitter_pin);
47+
48+
/**
49+
* Error Handling
50+
*/
51+
server.on('uncaughtException', (req, res, route, err) => {
52+
logger.error(err.stack)
53+
res.send(err)
54+
});
55+
56+
/**
57+
* Lift Server & Bind Routes
58+
*/
59+
server.listen(config.port, function() {
60+
logger.info('raspi-rcswitch-api Server listening on port ' + config.port);
61+
logger.info('Using configuration:');
62+
logger.info('config.transmitter_pin: ' + config.transmitter_pin);
63+
logger.info('config.reties: ' + config.retries);
64+
});
65+
66+
server.get('/api/v1/switch/:systemCode/:unitCode/:state', switchFunction);
67+
server.get('/api/v1/switch/:deviceName/:state', mappedSwitchFunction);
68+
69+
/**
70+
* Route functions
71+
*/
72+
function switchFunction(request, response, next){
73+
// Argument validation
74+
if(request.params.systemCode.length != 5 || !isBinaryString(request.params.systemCode)) {
75+
return next(new errors.InvalidArgumentError("The systen code have to be a 5 character long binary string"));
76+
}
77+
78+
if(isNaN(request.params.unitCode) || parseInt(request.params.unitCode) < 1 || parseInt(request.params.unitCode) > 4) {
79+
return next(new errors.InvalidArgumentError("The unit code have to be an number between 1 and 4."));
80+
}
81+
82+
if(!request.params.state || (request.params.state != 'on' && request.params.state != 'off')) {
83+
return next(new errors.InvalidArgumentError("The new state have to be either 'on' or 'off'."));
84+
}
85+
86+
switchUnit(request.params.systemCode, parseInt(request.params.unitCode), request.params.state);
87+
88+
response.send({"systemCode:": request.params.systemCode, "systemCode:": request.params.unitCode, "state": request.params.state});
89+
next();
90+
}
91+
92+
function mappedSwitchFunction(request, response, next){
93+
var switchConfig = JSON.parse(fs.readFileSync('device_config.json', 'utf8'));
94+
95+
// Argument validation
96+
if(switchConfig[request.params.deviceName]) {
97+
if(switchConfig[request.params.deviceName].systemCode.length != 5 || !isBinaryString(switchConfig[request.params.deviceName].systemCode)) {
98+
return next(new errors.InvalidArgumentError("The systen code have to be a 5 character long binary string"));
99+
}
100+
101+
if(isNaN(switchConfig[request.params.deviceName].unitCode) || parseInt(switchConfig[request.params.deviceName].unitCode) < 1 || parseInt(switchConfig[request.params.deviceName].unitCode) > 4) {
102+
return next(new errors.InvalidArgumentError("The unit code have to be an number between 1 and 4."));
103+
}
104+
} else {
105+
return next(new errors.InternalError("Configuration file 'device_config.json' doesn't define such a device or is missing."));
106+
}
107+
108+
if(!request.params.state || (request.params.state != 'on' && request.params.state != 'off')) {
109+
return next(new errors.InvalidArgumentError("The new state have to be either 'on' or 'off'."));
110+
}
111+
112+
switchUnit(switchConfig[request.params.deviceName].systemCode, parseInt(switchConfig[request.params.deviceName].unitCode), request.params.state);
113+
114+
response.send('{"systemCode:" '+switchConfig[request.params.deviceName].systemCode+',"systemCode:" '+switchConfig[request.params.deviceName].unitCode+',"state": '+request.params.state+'}');
115+
next();
116+
}
117+
118+
/**
119+
* Logic functions
120+
*/
121+
function switchUnit(systemCode, unitCode, state) {
122+
logger.info('switchUnit: systemCode='+systemCode+', unitCode='+unitCode+', state='+state);
123+
124+
var retries = config.retries ? config.retries : 1;
125+
var currentTry = 0;
126+
var intervalObj = setInterval((systemCode, unitCode) => {
127+
logger.debug('switchUnit: systemCode='+systemCode+', unitCode='+unitCode+', state='+state+', try='+currentTry);
128+
129+
if(state === 'on') {
130+
rcswitch.switchOn(systemCode, unitCode);
131+
} else if(state === 'off') {
132+
rcswitch.switchOff(systemCode, unitCode);
133+
}
134+
135+
if(++currentTry == retries) {
136+
clearInterval(intervalObj);
137+
}
138+
}, 100, systemCode, unitCode);
139+
}
140+
141+
/**
142+
* Help functions
143+
*/
144+
function isBinaryString(input) {
145+
for (var i = 0; i < input.length; i++) {
146+
if(!(input[i] === '1' || input[i] === '0')) {
147+
return false;
148+
}
149+
}
150+
return true;
151+
}

0 commit comments

Comments
 (0)