├── .babelrc
├── .eslintrc
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.MD
├── index.js
├── lib
├── AccessoryProvider.js
├── Colorpicker.js
├── CustomHomeKitTypes.js
├── ElementType.js
├── OpenHAB2_UpdateListener.js
├── RestClient.js
├── SitemapParser.js
├── Slider.js
├── Switch.js
├── Text.js
├── UpdateListener.js
├── index.js
├── openHABBridge.js
└── subtypes
│ ├── LightbulbItem.js
│ ├── OutletItem.js
│ └── RollershutterItem.js
├── package.json
├── start.sh.template
└── test
├── AccessoryProviderTest.js
├── ColorpickerTest.js
├── LightbulbItemTest.js
├── OutletItemTest.js
├── RestClientTest.js
├── RollershutterItemTest.js
├── SitemapParserTest.js
├── SliderTest.js
├── TextTest.js
└── resources
├── sitemap.json
└── sitemap_single_item.json
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-2"]
3 | }
4 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "env": {
4 | "es6": true
5 | },
6 | "ecmaFeatures" : {
7 | "modules": true
8 | },
9 | "rules": {
10 | "brace-style": [2, "1tbs", { "allowSingleLine": false } ],
11 | "complexity": [2, 10],
12 | "strict": 2,
13 | "max-len": [2, 120, 4],
14 | "no-use-before-define": [2, "nofunc"],
15 | "quotes": [2, "single", "avoid-escape"],
16 | "no-var": 2,
17 | "yoda": 0,
18 | "curly": [2, "multi-line"],
19 | "new-cap": 0,
20 | "eqeqeq": 2
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | persist
3 | npm-debug.log
4 | lcov-report
5 | lib-cov
6 | lib-test
7 | lcov.info
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.12"
4 | - "4.0"
5 | - "4.1"
6 |
7 | compiler:
8 | - gcc
9 |
10 | before_install:
11 | - sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
12 | - sudo apt-get update -qq
13 | - sudo apt-get install -qq g++-4.8
14 | - export CXX="g++-4.8" CC="gcc-4.8"
15 | - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 90
16 | - sudo apt-get install -y libavahi-compat-libdnssd-dev
17 | after_success:
18 | npm run coveralls
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/htreu/OpenHAB-HomeKit-Bridge)
2 | [](https://coveralls.io/github/htreu/OpenHAB-HomeKit-Bridge?branch=master)
3 | [](https://www.codacy.com/app/henning-treu/OpenHAB-HomeKit-Bridge)
4 |
5 | OpenHAB HomeKit Bridge
6 | =============
7 | OpenHAB HomeKit Bridge bridges openHAB items to Apple´s HomeKit Accessory Protocol. It is based on [hap-nodejs](https://github.com/KhaosT/HAP-NodeJS).
8 |
9 | Watch the [demo video here](https://youtu.be/QAbOHhjo05U) and [Siri controlling roller shutters here](https://youtu.be/z9G06-dxblo).
10 |
11 | ##### Prerequisites
12 | * [openHAB](http://www.openhab.org) server running with at least one Switch item configured
13 | * node.js 0.12.x, 4.0.x or 4.1.x
14 | * an iOS device running iOS 8 or 9
15 | * an iOS app for modifying the HomeKit database (like [elgato eve](https://www.elgato.com/de/eve-app-homekit))
16 | * this repository
17 |
18 | ##### Install the OpenHAB HomeKit Bridge:
19 | * On non OS X systems install the avahi library:
20 |
21 | `sudo apt-get install avahi-daemon libavahi-compat-libdnssd-dev`
22 | * Install the node module dependencies:
23 |
24 | `npm update`
25 | * Create a custom sitemap which lists all items you want to control with HomeKit:
26 | ```
27 | sitemap demo label="HomeKit" {
28 | # Lightbulb (On/Off), SwitchItem is always a lightbulb. See Frames for outlets.
29 | Switch item=DemoSwitch label="Toggle Switch"
30 |
31 | # Dimmer items (On/Off, Brightness)
32 | Slider item=DimmedLight label="Dimmed Light" switchSupport
33 |
34 | # RGB light items (On/Off, Brightness, Hue, Saturation)
35 | Colorpicker item=RGBLight icon="slider"
36 |
37 | # Rollershutter/Blinds
38 | Switch item=Shutter_GF_Living
39 |
40 | # Temperature sensor, value is taken from label
41 | Text item=Temperature_GF_Living label="Livingroom [%.1f °C]"
42 |
43 | Frame label="outlet" icon="outlet" { # Outlets must be configured in a Frame.
44 | Switch item=Outlet_GF_Living label="Radio"
45 | }
46 | }
47 | ```
48 |
49 | The following mapping of openHAB items and HomeKit items apply:
50 |
51 | openHAB item | sitemap item | HomeKit item | Functions
52 | -------------|--------------|--------------|----------
53 | Switch | Switch | Lightbulb | On/Off
54 | Dimmer | Slider | Lightbulb | On/Off, Brightness
55 | Color | Colorpicker | Lightbulb | On/Off, Brightness, Hue, Saturation
56 | Rollershutter | Switch | Rollershutter | Opening state 0% - 100%
57 | Number | Text | Temperature | temp in °C
58 | Switch | Frame* + Switch | Outlet | On/Off
59 |
60 | \* The Frame must be tagged with `label=outlet` and/or `icon=outlet` to let the bridge pick up its Switch items as outlets.
61 |
62 | ##### Run OpenHAB HomeKit Bridge
63 | This connects to OpenHAB at 192.168.0.99 on port 8080 and loads all items from a sitemap named 'demo.sitemap':
64 |
65 | `npm start -- --name "myOpenHAB-Bridge" --server 192.1.0.99:8080 --sitemap demo`
66 |
67 | ###### Command line options:
68 |
69 |
70 | - -n, --name
71 | - The name of the bridge as shown in the HomeKit database. _This option is mandatory._
72 | - -s, --server
73 | - The network address and port of the OpenHAB server as ```ip:port```. Defaults to 127.0.0.1:8080
74 | - -p, --pincode
75 | - The pincode used for the bridge accessory. Defaults to 031-45-154.
76 | - -m, --sitemap
77 | - The name of the sitemap to load all items from. Items must not be nested in frames or groups. Defaults to 'homekit'.
78 |
79 |
80 | To access the bridge from an iOS device (9.0+) use Apples [HomeKit Catalog 2.0](https://developer.apple.com/library/ios/samplecode/HomeKitCatalog/Introduction/Intro.html) demo application or a vendor app like elgato´s eve (see _Prerequisites_). Use the predefined pin code _031-45-154_ to add the bridge as an accessory.
81 |
82 | You may use the `start.sh.template` to create your own simple start up script. This gives you at least simple logging output and debugging possibilities in case the bridge runs in the background.
83 |
84 | ##### Running Tests
85 |
86 | To execute unit tests run
87 |
88 | `npm test`
89 |
90 | To produce test coverage output run
91 |
92 | `npm run-script coverage`
93 |
94 | ##### Known issues
95 | * iOS HomeKit may not delete the bridge properly from its database. In this case the bridge can neither be connected nor added again. Delete the _persist_ folder and restart the bridge with a new name using the 'name' option.
96 |
97 | ##### Backlog/ToDo
98 | * Distinguish between switches and lights when adding SwitchItems -> see [Issue 29](https://github.com/htreu/OpenHAB-HomeKit-Bridge/issues/29)
99 | * Implement Rollershutter -> see [Issue 7](https://github.com/htreu/OpenHAB-HomeKit-Bridge/issues/7)
100 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // enable es2015 support
4 | require("babel-core/register");
5 |
6 | var lib_dir = 'lib'; // default classes
7 |
8 | if (process.env.TEST) {
9 | lib_dir = 'lib-test'; // precompiled for tests
10 | }
11 | if (process.env.COVER) {
12 | lib_dir = 'lib-cov'; // compiled and instrumented for coverage
13 | }
14 |
15 | module.exports = require('./' + lib_dir);
16 |
--------------------------------------------------------------------------------
/lib/AccessoryProvider.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Henning Treu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { ElementType } from './ElementType.js';
18 | import debug from 'debug'; let logger = debug('AccessoryProvider');
19 |
20 | class AccessoryProvider {
21 |
22 | createHomeKitAccessories(openhabElements) {
23 | let homeKitAccessories = [];
24 | let elementType = new ElementType();
25 |
26 | for (let i = 0; i < openhabElements.length; i++) {
27 | let openHABWidget = openhabElements[i];
28 |
29 | /* istanbul ignore next */
30 | if (process.env.NODE_ENV !== 'test') {
31 | logger('processing widget: '
32 | + openHABWidget.type
33 | + ' ' + openHABWidget.name);
34 | }
35 |
36 | let widgetConstructor = elementType.elementFactory[openHABWidget.type];
37 |
38 | if (!widgetConstructor) {
39 | continue;
40 | }
41 |
42 | let accessory = new widgetConstructor(
43 | openHABWidget.name,
44 | openHABWidget.link,
45 | openHABWidget.state,
46 | openHABWidget.itemType);
47 |
48 | if (accessory) {
49 | homeKitAccessories.push(accessory.accessory);
50 | }
51 | }
52 |
53 | return homeKitAccessories;
54 | }
55 |
56 | }
57 |
58 | export { AccessoryProvider };
59 |
--------------------------------------------------------------------------------
/lib/Colorpicker.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Henning Treu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { Accessory, Service, Characteristic, uuid } from 'hap-nodejs';
18 | import request from 'request';
19 | import debug from 'debug'; let logger = debug('ColorItem');
20 |
21 | import { UpdateListener } from './UpdateListener.js';
22 |
23 | class Colorpicker {
24 | constructor(name, url, state, itemType) {
25 | this.HUE = 'hue';
26 | this.SATURATION = 'saturation';
27 |
28 | this.name = name;
29 | this.url = url;
30 | this.accessory = this.buildAccessory(state);
31 | this.updatingFromOpenHAB = false;
32 |
33 | // listen for OpenHAB updates
34 | this.listener = undefined;
35 | this.registerOpenHABListener();
36 | }
37 |
38 | registerOpenHABListener() {
39 | this.listener = new UpdateListener(this.url, this.updateCharacteristics.bind(this));
40 | this.listener.startListener();
41 | };
42 |
43 | buildAccessory(state) {
44 | let accessory = new Accessory(
45 | this.name, uuid.generate(this.constructor.name + this.name));
46 |
47 | let singleStates = this.parseState(state);
48 | let hue = +singleStates[0];
49 | let saturation = +singleStates[1];
50 | let brightness = +singleStates[2];
51 |
52 | let service = accessory.addService(Service.Lightbulb, this.name);
53 |
54 | let charactersiticOnOff =
55 | service.getCharacteristic(Characteristic.On);
56 | charactersiticOnOff.setValue(brightness > 0);
57 | charactersiticOnOff.on('set', this.updateOpenHabBrightness.bind(this));
58 | charactersiticOnOff.on('get', this.readOpenHabPowerState.bind(this));
59 |
60 | let charactersiticBrightness =
61 | service.addCharacteristic(Characteristic.Brightness);
62 | charactersiticBrightness.setValue(brightness);
63 | charactersiticBrightness.on('set', this.updateOpenHabBrightness.bind(this));
64 | charactersiticBrightness.on('get', this.readOpenHabBrightnessState.bind(this));
65 |
66 | let charactersiticHue =
67 | service.addCharacteristic(Characteristic.Hue);
68 | charactersiticHue.setValue(hue);
69 | charactersiticHue.on('set', this.updateHue.bind(this));
70 | charactersiticHue.on('get', this.readOpenHabHueState.bind(this));
71 |
72 | let charactersiticSaturation =
73 | service.addCharacteristic(Characteristic.Saturation);
74 | charactersiticSaturation.setValue(saturation);
75 | charactersiticSaturation.on('set', this.updateSaturation.bind(this));
76 | charactersiticSaturation.on('get', this.readOpenHabSaturationState.bind(this));
77 |
78 | return accessory;
79 | }
80 |
81 | readOpenHabPowerState(callback) {
82 | /* istanbul ignore next */
83 | if (process.env.NODE_ENV !== 'test') {
84 | logger('ColorItem: read power state called');
85 | }
86 | this.getCurrentStateFromOpenHAB(function(brightness, hue, saturation) {
87 | callback(false, brightness > 0 ? true : false);
88 | });
89 | }
90 |
91 | readOpenHabBrightnessState(callback) {
92 | /* istanbul ignore next */
93 | if (process.env.NODE_ENV !== 'test') {
94 | logger('ColorItem: read brightness state called');
95 | }
96 | this.getCurrentStateFromOpenHAB(function(brightness, hue, saturation) {
97 | callback(false, brightness);
98 | });
99 | }
100 |
101 | readOpenHabHueState(callback) {
102 | /* istanbul ignore next */
103 | if (process.env.NODE_ENV !== 'test') {
104 | logger('ColorItem: read hue state called');
105 | }
106 | this.getCurrentStateFromOpenHAB(function(brightness, hue, saturation) {
107 | callback(false, hue);
108 | });
109 | }
110 |
111 | readOpenHabSaturationState(callback) {
112 | /* istanbul ignore next */
113 | if (process.env.NODE_ENV !== 'test') {
114 | logger('ColorItem: read saturation state called');
115 | }
116 | this.getCurrentStateFromOpenHAB(function(brightness, hue, saturation) {
117 | callback(false, saturation);
118 | });
119 | }
120 |
121 | parseState(state) {
122 | let regex = /[\.\d]+/g;
123 | let result = [];
124 | let v;
125 | while (v = regex.exec(state)) {
126 | result.push(v[0]);
127 | }
128 |
129 | return result;
130 | }
131 |
132 | updateHue(value, callback) {
133 | this.updateHS(value, this.HUE, callback);
134 | }
135 |
136 | updateSaturation(value, callback) {
137 | this.updateHS(value, this.SATURATION, callback);
138 | }
139 |
140 | updateHS(value, type, callback) {
141 | if (this.updatingFromOpenHAB) {
142 | callback();
143 | return;
144 | }
145 | let message = type === this.HUE ? ('hue: ' + value) : ('saturation: ' + value)
146 |
147 | /* istanbul ignore next */
148 | if (process.env.NODE_ENV !== 'test') {
149 | logger('received color information from iOS: ' + message);
150 | }
151 |
152 | let _this = this;
153 | this.getCurrentStateFromOpenHAB(function(brightness, hue, saturation) {
154 | hue = type === _this.HUE ? value : hue;
155 | saturation = type === _this.SATURATION ? value : saturation;
156 | let command = hue + ',' + saturation + ',' + brightness;
157 |
158 | /* istanbul ignore next */
159 | if (process.env.NODE_ENV !== 'test') {
160 | logger('sending color command to openHAB: ' + command);
161 | }
162 |
163 | request.post(
164 | _this.url,
165 | {
166 | body: command,
167 | headers: {'Content-Type': 'text/plain'}
168 | },
169 | function (error, response, body) {
170 | if (!error && response.statusCode === 200) {
171 | logger(body)
172 | }
173 | callback();
174 | }
175 | );
176 | });
177 | };
178 |
179 | updateOpenHabBrightness(value, callback) {
180 | if (this.updatingFromOpenHAB) {
181 | callback();
182 | return;
183 | }
184 |
185 | /* istanbul ignore next */
186 | if (process.env.NODE_ENV !== 'test') {
187 | logger('received brightness value from iOS: ' + value);
188 | }
189 |
190 | let command = 0;
191 | if (typeof value === 'boolean') {
192 | command = value ? '100' : '0';
193 | } else {
194 | command = '' + value;
195 | }
196 | request.post(
197 | this.url,
198 | {
199 | body: command,
200 | headers: {'Content-Type': 'text/plain'}
201 | },
202 | function (error, response, body) {
203 | if (!error && response.statusCode === 200) {
204 | logger(body)
205 | }
206 | callback();
207 | }
208 | );
209 | };
210 |
211 | getCurrentStateFromOpenHAB(updateValues) {
212 | // request current HSB state from openHAB:
213 | let _this = this;
214 | request.get(
215 | this.url + '/state',
216 | function (error, response, body) {
217 | if (!error && response.statusCode === 200) {
218 | /* istanbul ignore next */
219 | if (process.env.NODE_ENV !== 'test') {
220 | logger('received color information from openHAB: ' + body);
221 | }
222 |
223 | let state = _this.parseState(body);
224 | let hue = state[0];
225 | let saturation = state[1];
226 | let brightness = state[2];
227 |
228 | updateValues(brightness, hue, saturation);
229 | }
230 | }
231 | );
232 | }
233 |
234 | updateCharacteristics(message) {
235 | this.updatingFromOpenHAB = true;
236 | let finished = 0;
237 | let state = this.parseState(message);
238 | let hue = +state[0];
239 | let saturation = +state[1];
240 | let brightness = +state[2];
241 | let power = brightness > 0;
242 |
243 | // set brightness
244 | this.getCharacteristic(Characteristic.Brightness).setValue(brightness,
245 | function() { // callback to signal us iOS did process the update
246 | finished++;
247 | if (finished === 4) {
248 | this.updatingFromOpenHAB = false;
249 | }
250 | }.bind(this)
251 | );
252 | // set hue
253 | this.getCharacteristic(Characteristic.Hue).setValue(hue,
254 | function() { // callback to signal us iOS did process the update
255 | finished++;
256 | if (finished === 4) {
257 | this.updatingFromOpenHAB = false;
258 | }
259 | }.bind(this)
260 | );
261 | // set saturation
262 | this.getCharacteristic(Characteristic.Saturation).setValue(saturation,
263 | function() { // callback to signal us iOS did process the update
264 | finished++;
265 | if (finished === 4) {
266 | this.updatingFromOpenHAB = false;
267 | }
268 | }.bind(this)
269 | );
270 | // update ON/OFF state
271 | this.getCharacteristic(Characteristic.On).setValue(power,
272 | function() { // callback to signal us iOS did process the update
273 | finished++;
274 | if (finished === 4) {
275 | this.updatingFromOpenHAB = false;
276 | }
277 | }.bind(this)
278 | );
279 | };
280 |
281 | getCharacteristic(type) {
282 | return this.accessory.getService(Service.Lightbulb).getCharacteristic(type);
283 | }
284 |
285 | }
286 |
287 | export { Colorpicker };
288 |
--------------------------------------------------------------------------------
/lib/CustomHomeKitTypes.js:
--------------------------------------------------------------------------------
1 | import { inherits } from 'util';
2 | import { Accessory, Service, Characteristic, uuid } from 'hap-nodejs';
3 |
4 | let CustomServices = {};
5 | let CustomCharacteristics = {};
6 |
7 | // TextInfoCharacteristic
8 | let textInfoCharacteristicUUID = uuid.generate('hap-nodejs:characteristics:textinfo');
9 | CustomCharacteristics.TextInfoCharacteristic = function() {
10 | Characteristic.call(this, 'Text Info', textInfoCharacteristicUUID);
11 | this.setProps({
12 | format: Characteristic.Formats.STRING,
13 | perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY]
14 | });
15 | this.value = this.getDefaultValue();
16 | };
17 | inherits(CustomCharacteristics.TextInfoCharacteristic, Characteristic);
18 | CustomCharacteristics.TextInfoCharacteristic.UUID = textInfoCharacteristicUUID;
19 |
20 |
21 | // TextInfoService
22 | let textInfoServiceUUID = uuid.generate('hap-nodejs:services:textinfo');
23 | CustomServices.TextInfoService = function(displayName, subtype) {
24 | Service.call(this, displayName, textInfoServiceUUID, subtype);
25 | this.addCharacteristic(CustomCharacteristics.TextInfoCharacteristic);
26 | };
27 | inherits(CustomServices.TextInfoService, Service);
28 | CustomServices.TextInfoService.UUID = textInfoServiceUUID;
29 |
30 |
31 |
32 | export { CustomServices, CustomCharacteristics };
--------------------------------------------------------------------------------
/lib/ElementType.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Henning Treu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { Slider } from './Slider';
18 | import { Colorpicker } from './Colorpicker';
19 | import { Switch } from './Switch';
20 | import { Text } from './Text';
21 |
22 |
23 | class ElementType {
24 | constructor() {
25 | this.SWITCH_ELEMENT = 'Switch';
26 | this.SLIDER_ELEMENT = 'Slider';
27 | this.COLORPICKER_ELEMENT = 'Colorpicker';
28 | this.TEXT_ELEMENT = 'Text';
29 |
30 | this.elementFactory = {};
31 | this.elementFactory[this.SWITCH_ELEMENT] = Switch;
32 | this.elementFactory[this.SLIDER_ELEMENT] = Slider;
33 | this.elementFactory[this.COLORPICKER_ELEMENT] = Colorpicker;
34 | this.elementFactory[this.TEXT_ELEMENT] = Text;
35 | };
36 | }
37 |
38 | module.exports = { ElementType };
39 |
--------------------------------------------------------------------------------
/lib/OpenHAB2_UpdateListener.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Henning Treu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import WebSocket from 'ws';
18 | import request from 'request';
19 | import EventSource from 'eventsource';
20 |
21 | import debug from 'debug'; let logger = debug('UpdateListener2');
22 |
23 | class UpdateListener {
24 | constructor(url, callback) {
25 | this.url = url;
26 | this.callback = callback;
27 | }
28 |
29 | //starts a WebSockets listener - works with OpenHab 1
30 | startListener() {
31 | let _this = this;
32 | let ws = new WebSocket(this.url.replace('http:', 'ws:') + '/state?type=json');
33 | ws.on('open', function() {
34 | logger('open ws connection for url ' + _this.url);
35 | });
36 | ws.on('message', function(message) {
37 | _this.callback(message);
38 | });
39 | }
40 |
41 | // Server Sent Events - works only with OpenHab 2
42 | static startSseListener(url) {
43 | if (!UpdateListener.subscribers) {
44 | UpdateListener.subscribers = {};
45 | }
46 |
47 | logger('Starting Server Side Events (SSE) listener for OpenHab 2');
48 | logger(url);
49 | let es = new EventSource(url);
50 | es.onmessage = UpdateListener.sseEventHandler;
51 |
52 | es.onerror = function() {
53 | logger('Error occurred with update listener. Restarting listener in 30 seconds.');
54 | es.close();
55 | setTimeout(function() {
56 | UpdateListener.startSseListener(url);
57 | }, 30000);
58 | };
59 | }
60 |
61 | static addSseSubscriber(itemName, callback) {
62 | if (!UpdateListener.subscribers) {
63 | UpdateListener.subscribers = {};
64 | }
65 |
66 | logger(itemName + ' subscribing for state updates')
67 | UpdateListener.subscribers[itemName] = callback;
68 | }
69 |
70 | static sseEventHandler(event) {
71 | if (event.type === 'message') {//not sure if this check needed
72 | let data = JSON.parse(event.data);
73 |
74 | if (data.type === 'ItemStateChangedEvent') {//make sure this is a state change
75 | let item = data.topic.split('/')[2];
76 | if (UpdateListener.subscribers[item] !== undefined) {
77 | let payload = JSON.parse(data.payload);
78 | UpdateListener.subscribers[item](payload.value);
79 | }
80 | }
81 | }
82 | }
83 | }
84 |
85 | export { UpdateListener };
86 |
--------------------------------------------------------------------------------
/lib/RestClient.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Henning Treu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import request from 'request';
18 |
19 | export class RestClient {
20 |
21 | fetchSitemap(serverAddress, sitemapName, callback) {
22 | let url = 'http://' + serverAddress + '/rest/sitemaps/' + sitemapName + '?type=json';
23 | try {
24 | request(url, function (error, response, body) {
25 | if (error) {
26 | callback(error);
27 | } else if (response.statusCode !== 200) {
28 | callback(new Error(
29 | 'openHAB Rest interface returned ' + response.statusCode + ' for URL ' + url));
30 | } else if (response.statusCode === 200) {
31 | callback(undefined, JSON.parse(body));
32 | }
33 | });
34 | } catch (e) {
35 | throw new Error ('error connecting openHAB Rest interface: ' + e.message);
36 | }
37 | };
38 |
39 | fetchItem(itemURL, callback) {
40 | let url = itemURL + '?type=json';
41 | try {
42 | request(url, function (error, response, body) {
43 | if (error) {
44 | callback(error);
45 | } else if (response.statusCode !== 200) {
46 | callback(new Error(
47 | 'openHAB Rest interface returned ' + response.statusCode + ' for URL ' + url));
48 | } else if (response.statusCode === 200) {
49 | callback(undefined, JSON.parse(body));
50 | }
51 | });
52 | } catch (e) {
53 | throw new Error ('error connecting openHAB Rest interface: ' + e.message);
54 | }
55 | };
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/lib/SitemapParser.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Henning Treu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import debug from 'debug'; let logger = debug('SitemapParser');
18 |
19 | class SitemapParser {
20 | constructor() {
21 | // nop
22 | }
23 |
24 | parseSitemap(sitemap) {
25 | let widgets = [].concat(sitemap.homepage.widget);
26 | let result = [];
27 | for (let i = 0; i < widgets.length; i++) {
28 | let widget = widgets[i];
29 | // parse Frames (Outlets)
30 | if (this.looksLikeOutletFrame(widget)) {
31 | let outlets = this.parseOutlets([].concat(widget.widget));
32 | result = result.concat(outlets);
33 | continue;
34 | }
35 |
36 | let parsedWidget = this.parseWidget(widget);
37 | if (parsedWidget) {
38 | result.push(parsedWidget);
39 | }
40 | }
41 |
42 | return result;
43 | }
44 |
45 | parseOutlets(widgets) {
46 | let result = [];
47 | for (let i = 0; i < widgets.length; i++) {
48 | let parsedWidget = this.parseWidget(widgets[i]);
49 | if (parsedWidget) {
50 | parsedWidget.itemType = 'OutletItem';
51 | result.push(parsedWidget);
52 | }
53 | }
54 |
55 | return result;
56 | }
57 |
58 | parseWidget(widget) {
59 | if (!widget.item) {
60 | /* istanbul ignore next */
61 | if (process.env.NODE_ENV !== 'test') {
62 | logger("WARN: The widget '" + widget.label + "' does not reference an item.");
63 | }
64 | return;
65 | }
66 | return {
67 | type: widget.type,
68 | itemType: widget.item.type,
69 | name: widget.label,
70 | link: widget.item.link,
71 | state: widget.item.state
72 | }
73 | }
74 |
75 | looksLikeOutletFrame(widget) {
76 | return widget.type === 'Frame'
77 | && (widget.icon === 'outlet' || widget.label === 'outlet');
78 | }
79 |
80 | };
81 |
82 | export { SitemapParser };
83 |
--------------------------------------------------------------------------------
/lib/Slider.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Henning Treu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { Accessory, Service, Characteristic, uuid } from 'hap-nodejs';
18 | import request from 'request';
19 | import debug from 'debug'; let logger = debug('Slider');
20 |
21 | import { UpdateListener } from './UpdateListener.js';
22 |
23 | class Slider {
24 | constructor(name, url, state, itemType) {
25 | this.name = name;
26 | this.url = url;
27 | this.state = state;
28 | this.accessory = this.buildAccessory();
29 | this.updatingFromOpenHAB = false;
30 |
31 | // listen for OpenHAB updates
32 | let listener = undefined;
33 | this.registerOpenHABListener();
34 | }
35 |
36 | registerOpenHABListener() {
37 | this.listener = new UpdateListener(this.url, this.updateCharacteristics.bind(this));
38 | this.listener.startListener();
39 | };
40 |
41 | buildAccessory() {
42 | let accessory = new Accessory(this.name,
43 | uuid.generate(this.constructor.name + this.name));
44 |
45 | let charactersiticOnOff = accessory
46 | .addService(Service.Lightbulb, this.name)
47 | .getCharacteristic(Characteristic.On);
48 |
49 | charactersiticOnOff.setValue(+this.state > 0);
50 | charactersiticOnOff.on('set', this.updateOpenHabItem.bind(this));
51 | charactersiticOnOff.on('get', this.readOpenHabPowerState.bind(this));
52 |
53 | let charactersiticBrightness = accessory
54 | .getService(Service.Lightbulb)
55 | .addCharacteristic(Characteristic.Brightness);
56 |
57 | charactersiticBrightness.setValue(+this.state);
58 | charactersiticBrightness.on('set', this.updateOpenHabItem.bind(this));
59 | charactersiticBrightness.on('get', this.readOpenHabBrightnessState.bind(this));
60 |
61 | return accessory;
62 | }
63 |
64 | updateOpenHabItem(value, callback) {
65 | if (this.updatingFromOpenHAB) {
66 | callback();
67 | return;
68 | }
69 |
70 | let command = 0;
71 | if (typeof value === 'boolean') {
72 | command = value ? '100' : '0';
73 | } else {
74 | command = '' + value;
75 | }
76 |
77 | /* istanbul ignore next */
78 | if (process.env.NODE_ENV !== 'test') {
79 | logger('slider value from iOS: ' + value + ' for ' + this.name + ', sending command to openhab: ' + command + '');
80 | }
81 |
82 | request.post(
83 | this.url,
84 | { body: command },
85 | function (error, response, body) {
86 | callback();
87 | }
88 | );
89 | }
90 |
91 | readOpenHabPowerState(callback) {
92 | let widgetName = this.name;
93 | let widgetUrl = this.url;
94 |
95 | request(this.url + '/state?type=json', function (error, response, body) {
96 | if (!error && response.statusCode === 200) {
97 | let value = +body > 0 ? true : false;
98 | /* istanbul ignore next */
99 | if (process.env.NODE_ENV !== 'test') {
100 | logger('read power state: [' + body + '] ' + value + ' for ' + widgetName + ' from ' + widgetUrl);
101 | }
102 | callback(false, value);
103 | }
104 | });
105 | }
106 |
107 | readOpenHabBrightnessState(callback) {
108 | let widgetName = this.name;
109 | let widgetUrl = this.url;
110 | request(this.url + '/state?type=json', function (error, response, body) {
111 | if (!error && response.statusCode === 200) {
112 | let value = +body;
113 | /* istanbul ignore next */
114 | if (process.env.NODE_ENV !== 'test') {
115 | logger('read brightness state: ' + value + ' for ' + widgetName + ' from ' + widgetUrl);
116 | }
117 | callback(false, value);
118 | }
119 | })
120 | }
121 |
122 | updateCharacteristics(message) {
123 | let brightness = +message;
124 | /* istanbul ignore next */
125 | if (process.env.NODE_ENV !== 'test') {
126 | logger('slider value from openHAB: ' + message + ' for ' + this.name + ', updating iOS: ' + brightness + '');
127 | }
128 |
129 | this.updatingFromOpenHAB = true;
130 | let finished = 0;
131 | if (brightness >= 0) {
132 | // set brightness
133 | this.getCharacteristic(Characteristic.Brightness)
134 | .setValue(brightness,
135 | function() { // callback to signal us iOS did process the update
136 | finished++;
137 | if (finished === 2) {
138 | this.updatingFromOpenHAB = false;
139 | }
140 | }.bind(this));
141 | // update ON/OFF state
142 | this.getCharacteristic(Characteristic.On)
143 | .setValue(brightness > 0 ? true : false,
144 | function() { // callback to signal us iOS did process the update
145 | finished++;
146 | if (finished === 2) {
147 | this.updatingFromOpenHAB = false;
148 | }
149 | }.bind(this));
150 | }
151 | }
152 |
153 | getCharacteristic(type) {
154 | return this.accessory.getService(Service.Lightbulb)
155 | .getCharacteristic(type);
156 | }
157 |
158 | }
159 |
160 | export { Slider };
161 |
--------------------------------------------------------------------------------
/lib/Switch.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Henning Treu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { Accessory, Service, Characteristic, uuid } from 'hap-nodejs';
18 | import { LightbulbItem } from './subtypes/LightbulbItem.js';
19 | import { OutletItem } from './subtypes/OutletItem.js';
20 | import { RollershutterItem } from './subtypes/RollershutterItem.js';
21 | import request from 'request';
22 | import debug from 'debug'; let logger = debug('Switch');
23 |
24 | import { UpdateListener } from './UpdateListener.js';
25 |
26 | class Switch {
27 | constructor(name, url, state, itemType) {
28 | this.name = name;
29 | this.url = url;
30 | this.state = state;
31 |
32 | if ('GroupItem' === itemType) {
33 | // Look up the first group member to determine item type
34 | new RestClient().fetchItem(url, function (error, item) {
35 | if (item.members && item.members.length > 0) {
36 | this.item = this.buildItemType(item.members[0].type);
37 | }
38 | });
39 | } else {
40 | this.item = this.buildItemType(itemType);
41 | }
42 |
43 | if (this.item) {
44 | this.accessory = this.item.accessory;
45 | }
46 | };
47 |
48 | buildItemType(itemType) {
49 | switch (itemType) {
50 | case 'RollershutterItem':
51 | return new RollershutterItem(this.name, this.url, this.state);
52 | break;
53 | case 'OutletItem':
54 | return new OutletItem(this.name, this.url, this.state);
55 | break;
56 | default:
57 | return new LightbulbItem(this.name, this.url, this.state);
58 | }
59 | };
60 | }
61 |
62 | export { Switch };
63 |
--------------------------------------------------------------------------------
/lib/Text.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Henning Treu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { Accessory, Service, Characteristic, uuid } from 'hap-nodejs';
18 | import request from 'request';
19 | import debug from 'debug'; let logger = debug('Text');
20 |
21 | import { CustomServices, CustomCharacteristics } from './CustomHomeKitTypes.js';
22 | import { UpdateListener } from './UpdateListener.js';
23 | import { OpenHABBridge } from './openHABBridge.js'
24 | import { RestClient } from './RestClient.js'
25 | import { SitemapParser } from './SitemapParser.js';
26 |
27 | class Text {
28 | constructor(name, url, state, itemType) {
29 | this.textType = this.inferredTextType(name, itemType);
30 |
31 | // Remove formatted value from name (label)
32 | this.name = this.labelName(name);
33 | this.url = url;
34 | this.accessory = this.buildAccessory(state, name);
35 | this.listener = undefined;
36 |
37 | // listen for OpenHAB updates
38 | this.registerOpenHABListener();
39 | };
40 |
41 | inferredTextType(name, itemType) {
42 | let lowerName = name.toLowerCase();
43 |
44 | // Contact sensor text item
45 | if ('ContactItem' === itemType) {
46 | return 'contact';
47 | }
48 |
49 | // Temperature text item
50 | let looksLikeTemp = lowerName.match(/(temperature|temp([^\w]|$))/)
51 | || this.labelValue(lowerName).match(/[\d+\.]+\s*(\xB0|f|c)/);
52 | if ('NumberItem' === itemType && looksLikeTemp) {
53 | return 'temperature';
54 | }
55 |
56 | // Fall back to generic text item
57 | return 'generic';
58 | };
59 |
60 | registerOpenHABListener() {
61 | this.listener = new UpdateListener(this.url, this.updateCharacteristics.bind(this));
62 | this.listener.startListener();
63 | };
64 |
65 | buildAccessory(state, name) {
66 | let accessory = new Accessory(this.name,
67 | uuid.generate(this.constructor.name + this.name));
68 |
69 | let service = CustomServices.TextInfoService;
70 | let characteristic = CustomCharacteristics.TextInfoCharacteristic;
71 | let initialValue = this.labelValue(name);
72 |
73 | switch (this.textType) {
74 |
75 | case 'contact':
76 | service = Service.ContactSensor;
77 | characteristic = Characteristic.ContactSensorState;
78 | initialValue = this.stateValue(state);
79 | break;
80 |
81 | case 'temperature':
82 | service = Service.TemperatureSensor;
83 | characteristic = Characteristic.CurrentTemperature;
84 | initialValue = this.stateValue(state);
85 | }
86 |
87 | this.characteristic = accessory.addService(service, this.name).getCharacteristic(characteristic);
88 | this.characteristic.on('get', this.readOpenHabText.bind(this));
89 | this.characteristic.setValue(initialValue);
90 |
91 | return accessory;
92 | };
93 |
94 | updateCharacteristics(message, callback) {
95 | /* istanbul ignore next */
96 | if (process.env.NODE_ENV !== 'test') {
97 | logger('text widget received message: ' + message);
98 | }
99 |
100 | switch (this.textType) {
101 | case 'contact':
102 | case 'temperature':
103 | let value = this.stateValue(message);
104 | this.characteristic.setValue(value);
105 | if (callback) {
106 | callback(value);
107 | }
108 | break;
109 |
110 | default:
111 | let characteristic = this.characteristic
112 | this.readOpenHabText(function (err, value) {
113 | characteristic.setValue(value);
114 | if (callback) {
115 | callback(value);
116 | }
117 | });
118 | }
119 | };
120 |
121 | readOpenHabText(callback) {
122 | switch (this.textType) {
123 | case 'contact':
124 | case 'temperature':
125 | this.readStateValue(callback);
126 | break;
127 | default:
128 | this.readLabelValue(callback);
129 | }
130 | };
131 |
132 | readStateValue(callback) {
133 | let widgetName = this.name;
134 | let widgetUrl = this.url;
135 | let stateValue = this.stateValue.bind(this);
136 |
137 | request(this.url + '/state?type=json', function (error, response, body) {
138 | if (!error && response.statusCode === 200) {
139 | let value = stateValue(body);
140 | /* istanbul ignore next */
141 | if (process.env.NODE_ENV !== 'test') {
142 | logger('read temperature state: [' + body + '] ' + value + ' for ' + widgetName + ' from ' + widgetUrl);
143 | }
144 | callback(false, value);
145 | }
146 | });
147 | };
148 |
149 | readLabelValue(callback) {
150 | // To get the formatted text element string for generic text,
151 | // we need to look at the element label rather than the item state
152 | let widgetUrl = this.url;
153 | let widgetName = this.name;
154 | let labelValue = this.labelValue.bind(this);
155 | let labelName = this.labelName;
156 | let bridge = OpenHABBridge.getInstance();
157 |
158 | new RestClient().fetchSitemap(bridge.serverAddress, bridge.sitemapName, function (error, sitemap) {
159 | let elements = new SitemapParser().parseSitemap(sitemap);
160 | elements.forEach(function(element) {
161 | if (element.link === widgetUrl && element.type === 'Text' && labelName(element.name) === widgetName) {
162 | let value = labelValue(element.name);
163 | /* istanbul ignore next */
164 | if (process.env.NODE_ENV !== 'test') {
165 | logger('read label state: ' + value + ' for ' + widgetName);
166 | }
167 | callback(false, value);
168 | }
169 | });
170 | });
171 | };
172 |
173 | labelValue(label) {
174 | return label.replace(/.*\[/, '').replace(/\].*/, '');
175 | };
176 |
177 | stateValue(state) {
178 | switch (this.textType) {
179 |
180 | case 'temperature':
181 | if ('Uninitialized' === state) {
182 | return 0.0;
183 | }
184 | return +state;
185 |
186 | case 'contact':
187 | if ('CLOSED' === state) {
188 | return Characteristic.ContactSensorState.CONTACT_DETECTED;
189 | }
190 | // fall back to 'no contact' if uninitialized or OPEN
191 | return Characteristic.ContactSensorState.CONTACT_NOT_DETECTED;
192 |
193 | default:
194 | // For generic text values, we use labels rather than state
195 | return '';
196 | }
197 | };
198 |
199 | labelName(name) {
200 | return name.replace(/\s*\[[^\]]+\]/g, '');
201 | };
202 |
203 | }
204 |
205 | export { Text };
206 |
--------------------------------------------------------------------------------
/lib/UpdateListener.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Henning Treu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import WebSocket from 'ws';
18 | import debug from 'debug'; let logger = debug('UpdateListener');
19 |
20 | class UpdateListener {
21 | constructor(url, callback) {
22 | this.url = url;
23 | this.callback = callback;
24 | this.ws = undefined;
25 | }
26 |
27 | startListener() {
28 | if (!this.url) {
29 | /* istanbul ignore next */
30 | if (process.env.NODE_ENV !== 'test') {
31 | logger('No URL defined, no listener attached.');
32 | }
33 | return;
34 | }
35 |
36 | this.openConnection();
37 | }
38 |
39 | registerWebSocketListeners() {
40 | let _this = this;
41 | this.ws.on('open', function() {
42 | /* istanbul ignore next */
43 | if (process.env.NODE_ENV !== 'test') {
44 | logger('open ws connection for url ' + _this.url);
45 | }
46 | });
47 |
48 | this.ws.on('message', function(message) {
49 | _this.callback(message);
50 | });
51 |
52 | this.ws.on('close', function() {
53 | /* istanbul ignore next */
54 | if (process.env.NODE_ENV !== 'test') {
55 | logger('ws disconnected for url ' + _this.url);
56 | }
57 | _this.reopenConnection();
58 | });
59 |
60 | this.ws.on('error', function() {
61 | /* istanbul ignore next */
62 | if (process.env.NODE_ENV !== 'test') {
63 | logger('error connecting to url ' + _this.url);
64 | }
65 | _this.reopenConnection();
66 | });
67 | }
68 |
69 | reopenConnection() {
70 | setTimeout(function (updateListener) {
71 | /* istanbul ignore next */
72 | if (process.env.NODE_ENV !== 'test') {
73 | logger('reopen ws connection for url ' + updateListener.url);
74 | }
75 | updateListener.openConnection();
76 | }, 10000, this);
77 | }
78 |
79 | openConnection() {
80 | this.ws = new WebSocket(this.url.replace('http:', 'ws:') + '/state?type=json');
81 | this.registerWebSocketListeners();
82 | }
83 |
84 | }
85 |
86 | export { UpdateListener };
87 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Henning Treu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | // load the main file and start openHAB Bridge
18 | import { OpenHABBridge } from './openHABBridge.js';
19 |
20 | import { Colorpicker } from './Colorpicker.js';
21 | import { Slider } from './Slider.js';
22 | import { Switch } from './Switch.js';
23 | import { Text } from './Text.js';
24 |
25 | import { AccessoryProvider } from './AccessoryProvider.js';
26 | import { ElementType } from './ElementType.js';
27 | import { RestClient } from './RestClient.js';
28 | import { SitemapParser } from './SitemapParser.js';
29 | import { CustomServices, CustomCharacteristics } from './CustomHomeKitTypes.js';
30 |
31 | module.exports = {
32 | Colorpicker,
33 | Slider,
34 | Switch,
35 | Text,
36 | AccessoryProvider,
37 | ElementType,
38 | RestClient,
39 | SitemapParser,
40 | CustomServices,
41 | CustomCharacteristics
42 | }
43 |
44 | /* istanbul ignore next */
45 | let args = process.argv.slice(2);
46 | /* istanbul ignore next */
47 | if (args.length > 0 && args[0] === 'start') {
48 | OpenHABBridge.getInstance();
49 | }
50 |
--------------------------------------------------------------------------------
/lib/openHABBridge.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Henning Treu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | // external library imports
18 | import stdio from 'stdio';
19 | import crypto from 'crypto';
20 | import { Accessory, Bridge, uuid, init } from 'hap-nodejs';
21 |
22 | // internal library imports
23 | import { RestClient } from './RestClient.js';
24 | import { SitemapParser } from './SitemapParser.js';
25 | import { AccessoryProvider } from './AccessoryProvider.js';
26 |
27 | let targetPort = 52826;
28 | let pincode = '031-45-154';
29 |
30 | // OpenHAB bridge entry point
31 | /* istanbul ignore next */
32 | let OpenHABBridge = (function() {
33 | let bridgeInstance;
34 |
35 | function createInstance() {
36 | // check command line options
37 | let ops = stdio.getopt({
38 | 'name' : {key: 'n', args: 1, description:
39 | 'The name of the bridge.', mandatory: true},
40 | 'server' : {key: 's', args: 1, description:
41 | 'The network address and port of the OpenHAB server. Defaults to 127.0.0.1:8080.'},
42 | 'pincode': {key: 'p', args: 1, description:
43 | 'The pincode used for the bridge accessory. Defaults to 031-45-154.'},
44 | 'sitemap': {key: 'm', args: 1, description:
45 | 'The name of the sitemap to load all items from. Defaults to "homekit".'}
46 | });
47 |
48 | pincode = ops['pincode'] ? ops['pincode'] : pincode;
49 | let serverAddress = ops['server'] ? ops['server'] : '127.0.0.1:8080';
50 | let sitemapName = ops['sitemap'] ? ops['sitemap'] : 'homekit';
51 | let name = ops['name'];
52 |
53 | console.log('Starting the bridge ' + name + ' ...');
54 |
55 | // initialize the hap-nodejs storage
56 | init();
57 |
58 | new RestClient().fetchSitemap(serverAddress, sitemapName, function (error, sitemap) {
59 | let items = new SitemapParser().parseSitemap(sitemap);
60 | publishOpenHABBridgeAccessory(name, items);
61 | });
62 |
63 | return {
64 | serverAddress,
65 | sitemapName,
66 | name
67 | }
68 | };
69 |
70 | return {
71 | getInstance: function () {
72 | if (!bridgeInstance) {
73 | if (process.env.NODE_ENV !== 'test') {
74 | bridgeInstance = createInstance();
75 | } else {
76 | bridgeInstance = { serverAddress: 'openhab.test', sitemapName: 'test.sitemap', name: 'testBridge' };
77 | }
78 | }
79 | return bridgeInstance;
80 | }
81 | };
82 |
83 | })();
84 |
85 | // iterate all items and create HAP compatible objects
86 | /* istanbul ignore next */
87 | function publishOpenHABBridgeAccessory(bridgeName, openHABWidgets) {
88 | let accessoryProvider = new AccessoryProvider();
89 | let homeKitAccessories = accessoryProvider.createHomeKitAccessories(openHABWidgets);
90 | let openHabBridge = new Bridge(bridgeName, uuid.generate(bridgeName));
91 |
92 | homeKitAccessories.forEach(function(accessory) {
93 | openHabBridge.addBridgedAccessory(accessory);
94 | });
95 |
96 | // Publish the Bridge on the local network.
97 | openHabBridge.publish({
98 | username: generateUniqueUsername(bridgeName),
99 | port: parseInt(targetPort),
100 | pincode: pincode,
101 | category: Accessory.Categories.OTHER
102 | });
103 | };
104 |
105 | /* istanbul ignore next */
106 | function generateUniqueUsername(name) {
107 | let shasum = crypto.createHash('sha1')
108 | shasum.update(name);
109 | let hash = shasum.digest('hex');
110 |
111 | return '' +
112 | hash[0] + hash[1] + ':' +
113 | hash[2] + hash[3] + ':' +
114 | hash[4] + hash[5] + ':' +
115 | hash[6] + hash[7] + ':' +
116 | hash[8] + hash[9] + ':' +
117 | hash[10] + hash[11];
118 | };
119 |
120 |
121 | export { OpenHABBridge };
122 |
--------------------------------------------------------------------------------
/lib/subtypes/LightbulbItem.js:
--------------------------------------------------------------------------------
1 | /**
2 | /**
3 | * Copyright 2016 Henning Treu
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | import { Accessory, Service, Characteristic, uuid } from 'hap-nodejs';
19 | import request from 'request';
20 | import debug from 'debug'; let logger = debug('LightbulbItem');
21 |
22 | import { UpdateListener } from '../UpdateListener.js';
23 |
24 | class LightbulbItem {
25 | constructor(name, url, state) {
26 | this.name = name;
27 | this.url = url;
28 | this.accessory = this.buildAccessory(state);
29 | this.updatingFromOpenHAB = false;
30 |
31 | // listen for OpenHAB updates
32 | this.listener = undefined;
33 | this.registerOpenHABListener();
34 | };
35 |
36 | buildAccessory(state) {
37 | let accessory = new Accessory(this.name,
38 | uuid.generate(this.constructor.name + this.name));
39 |
40 | let charactersiticOnOff = accessory
41 | .addService(Service.Lightbulb, this.name)
42 | .getCharacteristic(Characteristic.On);
43 |
44 | charactersiticOnOff.setValue(state === 'ON');
45 |
46 | charactersiticOnOff.on('set', this.updateOpenHabItem.bind(this));
47 | charactersiticOnOff.on('get', this.readOpenHabPowerState.bind(this));
48 |
49 | return accessory;
50 | };
51 |
52 | registerOpenHABListener() {
53 | this.listener = new UpdateListener(this.url, this.updateCharacteristics.bind(this));
54 | this.listener.startListener();
55 | };
56 |
57 | updateCharacteristics(message) {
58 | let command = message === 'ON' ? true : false;
59 |
60 | /* istanbul ignore next */
61 | if (process.env.NODE_ENV !== 'test') {
62 | logger('switch value from openHAB: ' + message + ' for ' + this.name + ', updating iOS: ' + command);
63 | }
64 | this.updatingFromOpenHAB = true;
65 | this.accessory.getService(Service.Lightbulb)
66 | .getCharacteristic(Characteristic.On)
67 | .setValue(command,
68 | function() { // callback to signal us iOS did process the update
69 | this.updatingFromOpenHAB = false;
70 | }.bind(this)
71 | );
72 | };
73 |
74 | readOpenHabPowerState(callback) {
75 | let widgetName = this.name;
76 | let widgetUrl = this.url;
77 | request(this.url + '/state?type=json', function (error, response, body) {
78 | if (!error && response.statusCode === 200) {
79 | let value = body === 'ON' ? true : false;
80 | /* istanbul ignore next */
81 | if (process.env.NODE_ENV !== 'test') {
82 | logger('read power state: [' + body + '] ' + value + ' for ' + widgetName + ' from ' + widgetUrl);
83 | }
84 | callback(false, value);
85 | }
86 | })
87 | };
88 |
89 | updateOpenHabItem(value, callback) {
90 | if (this.updatingFromOpenHAB) {
91 | callback();
92 | return;
93 | }
94 |
95 | let command = value ? 'ON' : 'OFF';
96 | /* istanbul ignore next */
97 | if (process.env.NODE_ENV !== 'test') {
98 | logger('switch value from iOS: ' + value + ' for ' + this.name
99 | + ', sending command to openhab: ' + command);
100 | }
101 |
102 | request.post(
103 | this.url,
104 | { body: command },
105 | function (error, response, body) {
106 | callback(); // we are done updating the switch item in OpenHAB
107 | }
108 | );
109 | };
110 | }
111 |
112 | export { LightbulbItem };
113 |
--------------------------------------------------------------------------------
/lib/subtypes/OutletItem.js:
--------------------------------------------------------------------------------
1 | /**
2 | /**
3 | * Copyright 2016 Henning Treu
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | import { Accessory, Service, Characteristic, uuid } from 'hap-nodejs';
19 | import request from 'request';
20 | import debug from 'debug'; let logger = debug('OutletItem');
21 |
22 | import { UpdateListener } from '../UpdateListener.js';
23 |
24 | class OutletItem {
25 | constructor(name, url, state) {
26 | this.name = name;
27 | this.url = url;
28 | this.accessory = this.buildAccessory(state);
29 | this.updatingFromOpenHAB = false;
30 |
31 | // listen for OpenHAB updates
32 | this.listener = undefined;
33 | this.registerOpenHABListener();
34 | };
35 |
36 | buildAccessory(state) {
37 | let accessory = new Accessory(this.name,
38 | uuid.generate(this.constructor.name + this.name));
39 |
40 | let charactersiticOnOff = accessory
41 | .addService(Service.Outlet, this.name)
42 | .getCharacteristic(Characteristic.On);
43 |
44 | charactersiticOnOff.setValue(state === 'ON');
45 |
46 | charactersiticOnOff.on('set', this.updateOpenHabItem.bind(this));
47 | charactersiticOnOff.on('get', this.readOpenHabPowerState.bind(this));
48 |
49 | return accessory;
50 | };
51 |
52 | registerOpenHABListener() {
53 | this.listener = new UpdateListener(this.url, this.updateCharacteristics.bind(this));
54 | this.listener.startListener();
55 | };
56 |
57 | updateCharacteristics(message) {
58 | let command = message === 'ON' ? true : false;
59 |
60 | /* istanbul ignore next */
61 | if (process.env.NODE_ENV !== 'test') {
62 | logger('outlet value from openHAB: ' + message + ' for ' + this.name + ', updating iOS: ' + command);
63 | }
64 | this.updatingFromOpenHAB = true;
65 | this.accessory.getService(Service.Outlet)
66 | .getCharacteristic(Characteristic.On)
67 | .setValue(command,
68 | function() { // callback to signal us iOS did process the update
69 | this.updatingFromOpenHAB = false;
70 | }.bind(this)
71 | );
72 | };
73 |
74 | readOpenHabPowerState(callback) {
75 | let widgetName = this.name;
76 | let widgetUrl = this.url;
77 | request(this.url + '/state?type=json', function (error, response, body) {
78 | if (!error && response.statusCode === 200) {
79 | let value = body === 'ON' ? true : false;
80 | /* istanbul ignore next */
81 | if (process.env.NODE_ENV !== 'test') {
82 | logger('read power state: [' + body + '] ' + value + ' for ' + widgetName + ' from ' + widgetUrl);
83 | }
84 | callback(false, value);
85 | }
86 | })
87 | };
88 |
89 | updateOpenHabItem(value, callback) {
90 | if (this.updatingFromOpenHAB) {
91 | callback();
92 | return;
93 | }
94 |
95 | let command = value ? 'ON' : 'OFF';
96 | /* istanbul ignore next */
97 | if (process.env.NODE_ENV !== 'test') {
98 | logger('outlet value from iOS: ' + value + ' for ' + this.name
99 | + ', sending command to openhab: ' + command);
100 | }
101 |
102 | request.post(
103 | this.url,
104 | { body: command },
105 | function (error, response, body) {
106 | callback(); // we are done updating the Outlet item in OpenHAB
107 | }
108 | );
109 | };
110 | }
111 |
112 | export { OutletItem };
113 |
--------------------------------------------------------------------------------
/lib/subtypes/RollershutterItem.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Henning Treu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { Accessory, Service, Characteristic, uuid } from 'hap-nodejs';
18 | import request from 'request';
19 | import debug from 'debug'; let logger = debug('Rollershutter');
20 |
21 | import { UpdateListener } from '../UpdateListener.js';
22 |
23 | class RollershutterItem {
24 | constructor(name, url, state) {
25 | this.name = name;
26 | this.url = url;
27 |
28 | this.accessory = this.buildAccessory(state);
29 | this.updatingFromOpenHAB = false;
30 |
31 | // listen for OpenHAB updates
32 | let listener = undefined;
33 | this.registerOpenHABListener();
34 | }
35 |
36 | registerOpenHABListener() {
37 | this.listener = new UpdateListener(this.url, this.updateCharacteristics.bind(this));
38 | this.listener.startListener();
39 | };
40 |
41 | buildAccessory(state) {
42 | let position = state === 'Uninitialized' ? 100 : +state;
43 | let accessory = new Accessory(
44 | this.name, uuid.generate(this.constructor.name + this.name));
45 |
46 | let service = accessory.addService(Service.WindowCovering, this.name);
47 |
48 | let charactersiticCurrentPosition =
49 | service.getCharacteristic(Characteristic.CurrentPosition);
50 | charactersiticCurrentPosition.setValue(this.convertValue(position));
51 | charactersiticCurrentPosition.on('get', this.readOpenHabCurrentPosition.bind(this));
52 |
53 | let charactersiticTargetPosition =
54 | service.getCharacteristic(Characteristic.TargetPosition);
55 | charactersiticTargetPosition.setValue(position);
56 | charactersiticTargetPosition.on('set', this.updateOpenHabItem.bind(this));
57 | charactersiticTargetPosition.on('get', this.readOpenHabCurrentPosition.bind(this));
58 |
59 | let charactersiticPositionState =
60 | service.getCharacteristic(Characteristic.PositionState);
61 | charactersiticPositionState.setValue(Characteristic.PositionState.STOPPED);
62 | charactersiticPositionState.on('get', this.readOpenHabPositionState.bind(this));
63 |
64 | return accessory;
65 | }
66 |
67 | updateOpenHabItem(value, callback) {
68 | logger('received rollershutter value from iOS: ' + value + ' for ' + this.name);
69 | if (this.updatingFromOpenHAB) {
70 | callback();
71 | return;
72 | }
73 |
74 | let command = '' + this.convertValue(value);
75 |
76 | request.post(
77 | this.url,
78 | { body: command },
79 | function (error, response, body) {
80 | if (!error) {
81 | callback();
82 | }
83 | }
84 | );
85 | };
86 |
87 | convertValue(value) {
88 | return 100 - (+value);
89 | }
90 |
91 | readOpenHabCurrentPosition(callback) {
92 | let widgetName = this.name;
93 | let widgetUrl = this.url;
94 | let _this = this;
95 |
96 | request(this.url + '/state?type=json', function (error, response, body) {
97 | if (!error && response.statusCode === 200) {
98 | let value = _this.convertValue(body);
99 | /* istanbul ignore next */
100 | if (process.env.NODE_ENV !== 'test') {
101 | logger('read current position state: [' + body + '] ' + value + ' for ' + widgetName + ' from ' + widgetUrl);
102 | }
103 | callback(false, value);
104 | }
105 | });
106 | }
107 |
108 | readOpenHabPositionState(callback) {
109 | callback(false, Characteristic.PositionState.STOPPED);
110 | }
111 |
112 | updateCharacteristics(message) {
113 | let position = this.convertValue(message);
114 | /* istanbul ignore next */
115 | if (process.env.NODE_ENV !== 'test') {
116 | logger('current rollershutter position from openHAB: ' + message
117 | + ' for ' + this.name + ', updating iOS: ' + position + '');
118 | }
119 |
120 | this.updatingFromOpenHAB = true;
121 | this.accessory.getService(Service.WindowCovering)
122 | .getCharacteristic(Characteristic.CurrentPosition)
123 | .setValue(position,
124 | function() { // callback to signal us iOS did process the update
125 | this.updatingFromOpenHAB = false;
126 | }.bind(this)
127 | );
128 | };
129 | }
130 |
131 | export { RollershutterItem };
132 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "openHAB-HomeKit-Bridge",
3 | "version": "1.3.3",
4 | "author": "Henning Treu ",
5 | "scripts": {
6 | "clean": "rm -rf lib-test lib-cov",
7 | "start": "node index.js start",
8 | "pretest": "`npm bin`/eslint lib/*js test/*js && babel --presets es2015,stage-2 -d lib-test lib",
9 | "test": "export TEST=true && mocha --compilers js:babel-core/register --reporter spec",
10 | "precoverage": "mv .babelrc babelrc && rm -rf lib-cov && `npm bin`/babel-istanbul instrument --es-modules --output lib-cov lib && mv babelrc .babelrc",
11 | "coverage": "export COVER=true && export ISTANBUL_REPORTERS='text-summary' && mocha --compilers js:babel-core/register --reporter mocha-istanbul",
12 | "precoveralls": "npm run precoverage && rm -rf lcov* && export COVER=true && export ISTANBUL_REPORTERS='lcov' && mocha --compilers js:babel-core/register --reporter mocha-istanbul",
13 | "coveralls": "`npm bin`/coveralls < lcov.info"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/htreu/OpenHAB-HomeKit-Bridge"
18 | },
19 | "bugs": {
20 | "url": "https://github.com/htreu/OpenHAB-HomeKit-Bridge/issues"
21 | },
22 | "dependencies": {
23 | "babel-cli": "^6.3.13",
24 | "babel-core": "^6.1.2",
25 | "babel-preset-es2015": "^6.1.2",
26 | "babel-preset-stage-2": "^6.1.2",
27 | "crypto": "0.0.3",
28 | "debug": "^2.2.0",
29 | "hap-nodejs": "^0.3.2",
30 | "request": "^2.55.0",
31 | "stdio": "^0.2.7",
32 | "ws": "^0.8.0"
33 | },
34 | "homepage": "https://github.com/htreu/OpenHAB-HomeKit-Bridge",
35 | "main": "openHABBridge.js",
36 | "license": "Apache-2.0",
37 | "devDependencies": {
38 | "babel-eslint": "^4.1.8",
39 | "babel-istanbul": "^0.4.1",
40 | "coveralls": "^2.11.4",
41 | "eslint": "^1.10.3",
42 | "mocha": "^2.3.4",
43 | "mocha-istanbul": "^0.2.0",
44 | "nock": "^2.13.0",
45 | "should": "^7.1.0"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/start.sh.template:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | BASEDIR=$(dirname $0)
3 | cd $BASEDIR
4 | npm start -- --name "myOpenHAB-Bridge" --server localhost:8080 --sitemap homekit &> bridge.log
5 |
--------------------------------------------------------------------------------
/test/AccessoryProviderTest.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Henning Treu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import should from 'should';
18 | import { AccessoryProvider } from '..';
19 |
20 | process.env.NODE_ENV = 'test';
21 |
22 |
23 | describe('AccessoryProvider', function () {
24 |
25 | let accessoryProvider = new AccessoryProvider();
26 |
27 | it('should process all known items', function(done) {
28 | let homeKitAccessories = accessoryProvider.createHomeKitAccessories(openHabWidgets());
29 | homeKitAccessories.should.have.length(4);
30 | done();
31 | });
32 |
33 | it('should allow duplicate names for different types', function(done) {
34 | let homeKitAccessories = accessoryProvider.createHomeKitAccessories(duplicateNamesWidgets());
35 |
36 | homeKitAccessories.forEach(function(original) {
37 | homeKitAccessories.forEach(function(accessory) {
38 | if (original === accessory) {
39 | return true;
40 | }
41 | original.UUID.should.not.equal(accessory.UUID);
42 | });
43 | });
44 |
45 | done();
46 | });
47 |
48 | });
49 |
50 | function openHabWidgets() {
51 | return [
52 | { type:'Switch', name:'switch', link:'http://openhab.test', state:'ON' },
53 | { type:'Slider', name:'slider', link:'http://openhab.test', state:'50' },
54 | { type:'Colorpicker', name:'color', link:'http://openhab.test', state:'10,100,100' },
55 | { type:'Text', name:'text', link:'http://openhab.test', state:'22.5' },
56 |
57 | // Unknown
58 | { type:'ContactSensor', name:'contact', link:'http://openhab.test', state:'OPEN' }
59 | ];
60 | };
61 |
62 | function duplicateNamesWidgets() {
63 | return [
64 | { type:'Switch', name:'itemName', link:'http://openhab.test', state:'ON' },
65 | { type:'Slider', name:'itemName', link:'http://openhab.test', state:'50' },
66 | { type:'Colorpicker', name:'itemName', link:'http://openhab.test', state:'10,100,100' },
67 | { type:'Text', name:'itemName', link:'http://openhab.test', state:'22.5' }
68 | ];
69 | };
70 |
--------------------------------------------------------------------------------
/test/ColorpickerTest.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Henning Treu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import should from 'should';
18 | import nock from 'nock';
19 | import { Service, Characteristic } from 'hap-nodejs';
20 |
21 | import { Colorpicker } from '..';
22 |
23 | process.env.NODE_ENV = 'test';
24 |
25 | function createColorpicker() {
26 | return new Colorpicker('colorpickerName', 'http://openhab.test/rest/colorpicker', '140,80,30');
27 | }
28 |
29 | describe('Colorpicker', function () {
30 |
31 | it('should contain AccessoryInformation & Lightbulb services', function () {
32 | let colorpicker = createColorpicker();
33 | colorpicker.should.have.property('accessory');
34 | colorpicker.accessory.getService(Service.AccessoryInformation).should.not.be.empty;
35 | colorpicker.accessory.getService(Service.Lightbulb).should.not.be.empty;
36 | });
37 |
38 | it('should have set the correct name', function () {
39 | let colorpicker = createColorpicker();
40 | let accessory = colorpicker.accessory;
41 | accessory.getService(Service.AccessoryInformation)
42 | .getCharacteristic(Characteristic.Name).value.should.equal('colorpickerName');
43 | accessory.getService(Service.Lightbulb)
44 | .getCharacteristic(Characteristic.Name).value.should.equal('colorpickerName');
45 | });
46 |
47 | it('should have set the initial value', function () {
48 | let colorpicker = createColorpicker();
49 | colorpicker.accessory.getService(Service.Lightbulb)
50 | .getCharacteristic(Characteristic.On).value.should.be.true;
51 |
52 | colorpicker.accessory.getService(Service.Lightbulb)
53 | .getCharacteristic(Characteristic.Hue).value.should.be.equal(140);
54 | colorpicker.accessory.getService(Service.Lightbulb)
55 | .getCharacteristic(Characteristic.Saturation).value.should.be.equal(80);
56 | colorpicker.accessory.getService(Service.Lightbulb)
57 | .getCharacteristic(Characteristic.Brightness).value.should.be.equal(30);
58 | });
59 |
60 | it('should make web socket connection to OpenHAB', function (done) {
61 | nock('http://openhab.test')
62 | .get('/rest/colorpicker/state?type=json')
63 | .reply(200, function(uri, requestBody) {
64 | done();
65 | });
66 | createColorpicker();
67 | });
68 |
69 | it('should update characteristics value when listener triggers', function (done) {
70 | let colorpicker = createColorpicker();
71 |
72 | colorpicker.updatingFromOpenHAB = true;
73 | colorpicker.listener.callback('90,100,0');
74 | colorpicker.accessory.getService(Service.Lightbulb)
75 | .getCharacteristic(Characteristic.On).value.should.be.false;
76 | colorpicker.accessory.getService(Service.Lightbulb)
77 | .getCharacteristic(Characteristic.Brightness).value.should.be.equal(0);
78 | colorpicker.accessory.getService(Service.Lightbulb)
79 | .getCharacteristic(Characteristic.Hue).value.should.be.equal(90);
80 | colorpicker.accessory.getService(Service.Lightbulb)
81 | .getCharacteristic(Characteristic.Saturation).value.should.be.equal(100);
82 | colorpicker.updatingFromOpenHAB.should.be.false;
83 |
84 | colorpicker.updatingFromOpenHAB = true;
85 | colorpicker.listener.callback('0,100,50');
86 | colorpicker.accessory.getService(Service.Lightbulb)
87 | .getCharacteristic(Characteristic.On).value.should.be.true;
88 | colorpicker.accessory.getService(Service.Lightbulb)
89 | .getCharacteristic(Characteristic.Brightness).value.should.be.equal(50);
90 | colorpicker.accessory.getService(Service.Lightbulb)
91 | .getCharacteristic(Characteristic.Hue).value.should.be.equal(0);
92 | colorpicker.accessory.getService(Service.Lightbulb)
93 | .getCharacteristic(Characteristic.Saturation).value.should.be.equal(100);
94 | colorpicker.updatingFromOpenHAB.should.be.false;
95 |
96 | colorpicker.updatingFromOpenHAB = true;
97 | colorpicker.listener.callback('90,0,100');
98 | colorpicker.accessory.getService(Service.Lightbulb)
99 | .getCharacteristic(Characteristic.On).value.should.be.true;
100 | colorpicker.accessory.getService(Service.Lightbulb)
101 | .getCharacteristic(Characteristic.Brightness).value.should.be.equal(100);
102 | colorpicker.accessory.getService(Service.Lightbulb)
103 | .getCharacteristic(Characteristic.Hue).value.should.be.equal(90);
104 | colorpicker.accessory.getService(Service.Lightbulb)
105 | .getCharacteristic(Characteristic.Saturation).value.should.be.equal(0);
106 | colorpicker.updatingFromOpenHAB.should.be.false;
107 | done();
108 | });
109 |
110 | it('should read the openHAB values when homekit asks for updates', function(done) {
111 | let colorpicker = new Colorpicker('colorpickerName', undefined, '140,80,30');
112 | colorpicker.url = 'http://openhab.test/rest/colorpicker';
113 |
114 | nock('http://openhab.test')
115 | .get('/rest/colorpicker/state')
116 | .times(4)
117 | .reply(200, '12,8,3');
118 |
119 | colorpicker.readOpenHabPowerState(function(err, value) {
120 | err.should.be.false;
121 | value.should.be.true;
122 | });
123 |
124 | colorpicker.readOpenHabBrightnessState(function(err, value) {
125 | err.should.be.false;
126 | value.should.be.equal('3');
127 | });
128 |
129 | colorpicker.readOpenHabHueState(function(err, value) {
130 | err.should.be.false;
131 | value.should.be.equal('12');
132 | });
133 |
134 | colorpicker.readOpenHabSaturationState(function(err, value) {
135 | err.should.be.false;
136 | value.should.be.equal('8');
137 | done();
138 | });
139 | });
140 |
141 | });
142 |
--------------------------------------------------------------------------------
/test/LightbulbItemTest.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Henning Treu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import should from 'should';
18 | import nock from 'nock';
19 | import { Service, Characteristic } from 'hap-nodejs';
20 |
21 | import { Switch } from '..';
22 |
23 | process.env.NODE_ENV = 'test';
24 |
25 | function createSwitch() {
26 | return new Switch('lightbulbName', 'http://openhab.test/rest/lightbulb', 'ON');
27 | }
28 |
29 | describe('LightbulbItem', function () {
30 |
31 | it('should contain AccessoryInformation & Lightbulb services', function () {
32 | let lightbulbElement = createSwitch();
33 | lightbulbElement.should.have.property('accessory');
34 | lightbulbElement.accessory.getService(Service.AccessoryInformation).should.not.be.empty;
35 | lightbulbElement.accessory.getService(Service.Lightbulb).should.not.be.empty;
36 | });
37 |
38 | it('should have set the correct name', function () {
39 | let lightbulbElement = createSwitch();
40 | let accessory = lightbulbElement.accessory;
41 | accessory.getService(Service.AccessoryInformation)
42 | .getCharacteristic(Characteristic.Name).value.should.equal('lightbulbName');
43 | accessory.getService(Service.Lightbulb)
44 | .getCharacteristic(Characteristic.Name).value.should.equal('lightbulbName');
45 | });
46 |
47 | it('should have set the initial value', function () {
48 | let lightbulbElement = createSwitch();
49 | lightbulbElement.accessory.getService(Service.Lightbulb)
50 | .getCharacteristic(Characteristic.On).value.should.be.true;
51 | });
52 |
53 | it('should make web socket connection to OpenHAB', function (done) {
54 | nock('http://openhab.test')
55 | .get('/rest/lightbulb/state?type=json')
56 | .reply(200, function(uri, requestBody) {
57 | done();
58 | });
59 | createSwitch();
60 | });
61 |
62 | it('should update characteristics value when listener triggers', function (done) {
63 | let lightbulbElement = createSwitch();
64 |
65 | lightbulbElement.item.updatingFromOpenHAB = true;
66 | lightbulbElement.item.listener.callback('ON');
67 | lightbulbElement.accessory.getService(Service.Lightbulb)
68 | .getCharacteristic(Characteristic.On).value.should.be.true;
69 | lightbulbElement.item.updatingFromOpenHAB.should.be.false;
70 |
71 | lightbulbElement.item.updatingFromOpenHAB = true;
72 | lightbulbElement.item.listener.callback('OFF');
73 | lightbulbElement.accessory.getService(Service.Lightbulb)
74 | .getCharacteristic(Characteristic.On).value.should.be.false;
75 | lightbulbElement.item.updatingFromOpenHAB.should.be.false;
76 | done();
77 | });
78 |
79 | it('should read the openHAB value when homekit asks for updates', function(done) {
80 | let lightbulbElement = new Switch('lightbulbName', undefined, 'ON');
81 | lightbulbElement.item.url = 'http://openhab.test/rest/lightbulb';
82 |
83 | nock('http://openhab.test')
84 | .get('/rest/lightbulb/state?type=json')
85 | .reply(200, 'ON');
86 |
87 | lightbulbElement.item.readOpenHabPowerState(function(err, value) {
88 | err.should.be.false;
89 | value.should.be.true;
90 | done();
91 | });
92 | });
93 |
94 | it('should update the openHAB value when homekit has new value', function(done) {
95 | let lightbulbElement = new Switch('lightbulbName', undefined, 'OFF');
96 | lightbulbElement.item.url = 'http://openhab.test/rest/lightbulb';
97 |
98 | nock('http://openhab.test')
99 | .post('/rest/lightbulb', 'ON')
100 | .reply(200, '');
101 |
102 | lightbulbElement.item.updateOpenHabItem(true, function() {
103 | done();
104 | });
105 | });
106 |
107 |
108 | });
109 |
--------------------------------------------------------------------------------
/test/OutletItemTest.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Henning Treu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import should from 'should';
18 | import nock from 'nock';
19 | import { Service, Characteristic } from 'hap-nodejs';
20 |
21 | import { Switch } from '..';
22 |
23 | process.env.NODE_ENV = 'test';
24 |
25 | function createOutlet() {
26 | return new Switch('outletName', 'http://openhab.test/rest/outlet', 'ON', 'OutletItem');
27 | }
28 |
29 | describe('OutletItem', function () {
30 |
31 | it('should contain AccessoryInformation & Outlet services', function () {
32 | let outletElement = createOutlet();
33 | outletElement.should.have.property('accessory');
34 | outletElement.accessory.getService(Service.AccessoryInformation).should.not.be.empty;
35 | outletElement.accessory.getService(Service.Outlet).should.not.be.empty;
36 | });
37 |
38 | it('should have set the correct name', function () {
39 | let outletElement = createOutlet();
40 | let accessory = outletElement.accessory;
41 | accessory.getService(Service.AccessoryInformation)
42 | .getCharacteristic(Characteristic.Name).value.should.equal('outletName');
43 | accessory.getService(Service.Outlet)
44 | .getCharacteristic(Characteristic.Name).value.should.equal('outletName');
45 | });
46 |
47 | it('should have set the initial value', function () {
48 | let outletElement = createOutlet();
49 | outletElement.accessory.getService(Service.Outlet)
50 | .getCharacteristic(Characteristic.On).value.should.be.true;
51 | });
52 |
53 | it('should make web socket connection to OpenHAB', function (done) {
54 | nock('http://openhab.test')
55 | .get('/rest/outlet/state?type=json')
56 | .reply(200, function(uri, requestBody) {
57 | done();
58 | });
59 | createOutlet();
60 | });
61 |
62 | it('should update characteristics value when listener triggers', function (done) {
63 | let outletElement = createOutlet();
64 |
65 | outletElement.item.updatingFromOpenHAB = true;
66 | outletElement.item.listener.callback('ON');
67 | outletElement.accessory.getService(Service.Outlet)
68 | .getCharacteristic(Characteristic.On).value.should.be.true;
69 | outletElement.item.updatingFromOpenHAB.should.be.false;
70 |
71 | outletElement.item.updatingFromOpenHAB = true;
72 | outletElement.item.listener.callback('OFF');
73 | outletElement.accessory.getService(Service.Outlet)
74 | .getCharacteristic(Characteristic.On).value.should.be.false;
75 | outletElement.item.updatingFromOpenHAB.should.be.false;
76 | done();
77 | });
78 |
79 | it('should read the openHAB value when homekit asks for updates', function(done) {
80 | let outletElement = new Switch('outletName', undefined, 'ON', 'OutletItem');
81 | outletElement.item.url = 'http://openhab.test/rest/outlet';
82 |
83 | nock('http://openhab.test')
84 | .get('/rest/outlet/state?type=json')
85 | .reply(200, 'ON');
86 |
87 | outletElement.item.readOpenHabPowerState(function(err, value) {
88 | err.should.be.false;
89 | value.should.be.true;
90 | done();
91 | });
92 | });
93 |
94 | it('should update the openHAB value when homekit has new value', function(done) {
95 | let outletElement = new Switch('outletName', undefined, 'ON', 'OutletItem');
96 | outletElement.item.url = 'http://openhab.test/rest/outlet';
97 |
98 | nock('http://openhab.test')
99 | .post('/rest/outlet', 'OFF')
100 | .reply(200, '');
101 |
102 | outletElement.item.updateOpenHabItem(false, function() {
103 | done();
104 | });
105 | });
106 |
107 | });
108 |
--------------------------------------------------------------------------------
/test/RestClientTest.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Henning Treu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import should from 'should';
18 | import nock from 'nock';
19 | import { RestClient } from '..';
20 |
21 | process.env.NODE_ENV = 'test';
22 |
23 | describe('RestClient', function () {
24 |
25 | describe('for fetching sitemap', function () {
26 |
27 | it('should fetch sitemap', function (done) {
28 | nock('http://openhab.test')
29 | .get('/rest/sitemaps/test.sitemap?type=json')
30 | .reply(200, '{}');
31 |
32 | new RestClient().fetchSitemap('openhab.test', 'test.sitemap',
33 | function callback(error, result) {
34 | done();
35 | });
36 | });
37 |
38 | it('should return proper object', function (done) {
39 | nock('http://openhab.test')
40 | .get('/rest/sitemaps/test.sitemap?type=json')
41 | .reply(200, '{ "homepage" : { "widget" : [] } }');
42 |
43 | new RestClient().fetchSitemap('openhab.test', 'test.sitemap',
44 | function callback(error, result) {
45 | result.should.have.property('homepage');
46 | result.homepage.should.have.property('widget');
47 | result.homepage.widget.should.be.instanceof(Array);
48 | done();
49 | });
50 | });
51 |
52 | it('should pass error on response error code', function (done) {
53 | nock('http://openhab.test')
54 | .get('/rest/sitemaps/test.sitemap?type=json')
55 | .reply(500, '{}');
56 |
57 | new RestClient().fetchSitemap('openhab.test', 'test.sitemap',
58 | function callback(error, result) {
59 | error.should.be.Error;
60 | done();
61 | }
62 | );
63 | });
64 |
65 | it('should pass error on connection error', function (done) {
66 | new RestClient().fetchSitemap('foo.bar', 'test.sitemap',
67 | function callback(error, result) {
68 | error.should.be.Error;
69 | done();
70 | }
71 | );
72 | });
73 | });
74 |
75 | describe('for fetching sitemap', function () {
76 |
77 | it('should fetch item', function (done) {
78 | nock('http://openhab.test')
79 | .get('/rest/demoItem?type=json')
80 | .reply(200, '{}');
81 |
82 | new RestClient().fetchItem('http://openhab.test/rest/demoItem',
83 | function callback(error, result) {
84 | done();
85 | });
86 | });
87 |
88 | it('should pass error on response error code', function (done) {
89 | nock('http://openhab.test')
90 | .get('/rest/demoItem?type=json')
91 | .reply(500, '{}');
92 |
93 | new RestClient().fetchItem('http://openhab.test/rest/demoItem',
94 | function callback(error, result) {
95 | error.should.be.Error;
96 | done();
97 | }
98 | );
99 | });
100 |
101 | it('should pass error on connection error', function (done) {
102 | new RestClient().fetchItem('http://openhab.test/rest/fooBar',
103 | function callback(error, result) {
104 | error.should.be.Error;
105 | done();
106 | }
107 | );
108 | });
109 | });
110 |
111 | });
112 |
--------------------------------------------------------------------------------
/test/RollershutterItemTest.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Henning Treu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import should from 'should';
18 | import nock from 'nock';
19 | import { Service, Characteristic } from 'hap-nodejs';
20 |
21 | import { Switch } from '..';
22 |
23 | process.env.NODE_ENV = 'test';
24 |
25 | function createRollershutterSwitch() {
26 | return new Switch(
27 | 'rollershutterSwitchName',
28 | 'http://openhab.test/rest/rollershutterSwitch',
29 | '80',
30 | 'RollershutterItem');
31 | }
32 |
33 | describe('RollershutterItem', function () {
34 |
35 | it('should contain AccessoryInformation & Lightbulb services', function () {
36 | let rollershutterSwitch = createRollershutterSwitch();
37 | rollershutterSwitch.should.have.property('accessory');
38 | rollershutterSwitch.accessory.getService(Service.AccessoryInformation).should.not.be.empty;
39 | rollershutterSwitch.accessory.getService(Service.WindowCovering).should.not.be.empty;
40 | });
41 |
42 | it('should have set the correct name', function () {
43 | let rollershutterSwitch = createRollershutterSwitch();
44 | let accessory = rollershutterSwitch.accessory;
45 | accessory.getService(Service.AccessoryInformation)
46 | .getCharacteristic(Characteristic.Name).value.should.equal('rollershutterSwitchName');
47 | accessory.getService(Service.WindowCovering)
48 | .getCharacteristic(Characteristic.Name).value.should.equal('rollershutterSwitchName');
49 | });
50 |
51 | it('should have set the initial value', function () {
52 | let rollershutterSwitch = createRollershutterSwitch();
53 | rollershutterSwitch.accessory.getService(Service.WindowCovering)
54 | .getCharacteristic(Characteristic.CurrentPosition).value.should.equal(20);
55 | });
56 |
57 | it('should make web socket connection to OpenHAB', function (done) {
58 | nock('http://openhab.test')
59 | .get('/rest/rollershutterSwitch/state?type=json')
60 | .reply(200, function(uri, requestBody) {
61 | done();
62 | });
63 | createRollershutterSwitch();
64 | });
65 |
66 | it('should update characteristics value when listener triggers', function (done) {
67 | let rollershutterSwitch = createRollershutterSwitch();
68 |
69 | rollershutterSwitch.item.updatingFromOpenHAB = true;
70 | rollershutterSwitch.item.listener.callback('100');
71 | rollershutterSwitch.accessory.getService(Service.WindowCovering)
72 | .getCharacteristic(Characteristic.CurrentPosition).value.should.equal(0);
73 | rollershutterSwitch.item.updatingFromOpenHAB.should.be.false;
74 |
75 | rollershutterSwitch.item.updatingFromOpenHAB = true;
76 | rollershutterSwitch.item.listener.callback('10');
77 | rollershutterSwitch.accessory.getService(Service.WindowCovering)
78 | .getCharacteristic(Characteristic.CurrentPosition).value.should.equal(90);
79 | rollershutterSwitch.item.updatingFromOpenHAB.should.be.false;
80 | done();
81 | });
82 |
83 | it('should read the openHAB value when homekit asks for updates', function(done) {
84 | let rollershutterSwitch = new Switch('rollershutterSwitchName', undefined, '60', 'RollershutterItem');
85 | rollershutterSwitch.item.url = 'http://openhab.test/rest/rollershutterSwitch';
86 |
87 | nock('http://openhab.test')
88 | .get('/rest/rollershutterSwitch/state?type=json')
89 | .reply(200, '50');
90 |
91 | rollershutterSwitch.item.readOpenHabCurrentPosition(function(err, value) {
92 | err.should.be.false;
93 | value.should.equal(50);
94 | done();
95 | });
96 | });
97 |
98 | it('should send command to openHAB when homekit sends updates', function(done) {
99 | let rollershutterSwitch = new Switch('rollershutterSwitchName', undefined, '60', 'RollershutterItem');
100 | rollershutterSwitch.item.url = 'http://openhab.test/rest/rollershutterSwitch';
101 |
102 | nock('http://openhab.test')
103 | .post('/rest/rollershutterSwitch', '30')
104 | .reply(200);
105 |
106 | rollershutterSwitch.item.updateOpenHabItem(70, function() {
107 | done();
108 | });
109 |
110 | });
111 |
112 | });
113 |
--------------------------------------------------------------------------------
/test/SitemapParserTest.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Henning Treu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import should from 'should';
18 | import { SitemapParser, ElementType } from '../index';
19 |
20 | process.env.NODE_ENV = 'test';
21 |
22 | describe('SitemapParser', function () {
23 |
24 | let sitemapParser = new SitemapParser();
25 | let elementType = new ElementType();
26 |
27 | it('should return empty list of items for emtpy sitemap', function (done) {
28 | let result = sitemapParser.parseSitemap({ homepage : { widget: [] }}, '');
29 | result.should.be.empty;
30 | done();
31 | });
32 |
33 | // load demo sitemap
34 | let sitemap = require('./resources/sitemap.json');
35 |
36 | it('should return 7 items for demo sitemap', function (done) {
37 | let items = sitemapParser.parseSitemap(sitemap, '');
38 | items.should.have.length(7);
39 | done();
40 | });
41 |
42 | it('should return items with all properties set for demo sitemap', function (done) {
43 | let items = sitemapParser.parseSitemap(sitemap, '');
44 | for (let i = 0; i < items.length; i++) {
45 | items[i].should.have.property('type');
46 | items[i].should.have.property('itemType');
47 | items[i].should.have.property('name');
48 | items[i].should.have.property('link');
49 | items[i].should.have.property('state');
50 | }
51 | done();
52 | });
53 |
54 | it('should return items with proper initial state for demo sitemap', function (done) {
55 | let items = sitemapParser.parseSitemap(sitemap, '');
56 | for (let i = 0; i < items.length; i++) {
57 | switch (items[i].name) {
58 | case 'Toggle Switch':
59 | items[i].state.should.equal('ON');
60 | break;
61 | case 'Outlet':
62 | items[i].state.should.equal('ON');
63 | break;
64 | case 'Dimmed Light':
65 | items[i].state.should.equal('80');
66 | break;
67 | case 'RGB Light':
68 | items[i].state.should.equal('144.32432432432432,41.340782122905026,70.19607843137254');
69 | break;
70 | }
71 | }
72 | done();
73 | });
74 |
75 | it('should return one item for single item sitemap', function (done) {
76 | let sitemap = require('./resources/sitemap_single_item.json');
77 | let items = sitemapParser.parseSitemap(sitemap);
78 | items.should.have.length(1);
79 | done();
80 | });
81 |
82 | });
83 |
--------------------------------------------------------------------------------
/test/SliderTest.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Henning Treu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import should from 'should';
18 | import nock from 'nock';
19 | import { Service, Characteristic } from 'hap-nodejs';
20 |
21 | import { Slider } from '..';
22 |
23 | process.env.NODE_ENV = 'test';
24 |
25 | function createSlider() {
26 | return new Slider('sliderName', 'http://openhab.test/rest/slider', '80');
27 | }
28 |
29 | describe('Slider', function () {
30 |
31 | it('should contain AccessoryInformation & Lightbulb services', function () {
32 | let slider = createSlider();
33 | slider.should.have.property('accessory');
34 | slider.accessory.getService(Service.AccessoryInformation).should.not.be.empty;
35 | slider.accessory.getService(Service.Lightbulb).should.not.be.empty;
36 | });
37 |
38 | it('should have set the correct name', function () {
39 | let slider = createSlider();
40 | let accessory = slider.accessory;
41 | accessory.getService(Service.AccessoryInformation)
42 | .getCharacteristic(Characteristic.Name).value.should.equal('sliderName');
43 | accessory.getService(Service.Lightbulb)
44 | .getCharacteristic(Characteristic.Name).value.should.equal('sliderName');
45 | });
46 |
47 | it('should have set the initial value', function () {
48 | let slider = createSlider();
49 | slider.accessory.getService(Service.Lightbulb)
50 | .getCharacteristic(Characteristic.On).value.should.be.true;
51 |
52 | slider.accessory.getService(Service.Lightbulb)
53 | .getCharacteristic(Characteristic.Brightness).value.should.be.equal(80);
54 | });
55 |
56 | it('should make web socket connection to OpenHAB', function (done) {
57 | nock('http://openhab.test')
58 | .get('/rest/slider/state?type=json')
59 | .reply(200, function(uri, requestBody) {
60 | done();
61 | });
62 | createSlider();
63 | });
64 |
65 | it('should update characteristics value when listener triggers', function (done) {
66 | let slider = createSlider();
67 |
68 | slider.updatingFromOpenHAB = true;
69 | slider.listener.callback('0');
70 | slider.accessory.getService(Service.Lightbulb)
71 | .getCharacteristic(Characteristic.On).value.should.be.false;
72 | slider.accessory.getService(Service.Lightbulb)
73 | .getCharacteristic(Characteristic.Brightness).value.should.be.equal(0);
74 | slider.updatingFromOpenHAB.should.be.false;
75 |
76 | slider.updatingFromOpenHAB = true;
77 | slider.listener.callback('50');
78 | slider.accessory.getService(Service.Lightbulb)
79 | .getCharacteristic(Characteristic.On).value.should.be.true;
80 | slider.accessory.getService(Service.Lightbulb)
81 | .getCharacteristic(Characteristic.Brightness).value.should.be.equal(50);
82 | slider.updatingFromOpenHAB.should.be.false;
83 |
84 | slider.updatingFromOpenHAB = true;
85 | slider.listener.callback('100');
86 | slider.accessory.getService(Service.Lightbulb)
87 | .getCharacteristic(Characteristic.On).value.should.be.true;
88 | slider.accessory.getService(Service.Lightbulb)
89 | .getCharacteristic(Characteristic.Brightness).value.should.be.equal(100);
90 | slider.updatingFromOpenHAB.should.be.false;
91 | done();
92 | });
93 |
94 | it('should read the openHAB values when homekit asks for updates', function(done) {
95 | let slider = new Slider('sliderName', undefined, '80');
96 | slider.url = 'http://openhab.test/rest/slider';
97 |
98 | nock('http://openhab.test')
99 | .get('/rest/slider/state?type=json')
100 | .times(2)
101 | .reply(200, '12');
102 |
103 | slider.readOpenHabPowerState(function(err, value) {
104 | err.should.be.false;
105 | value.should.be.true;
106 | });
107 |
108 | slider.readOpenHabBrightnessState(function(err, value) {
109 | err.should.be.false;
110 | value.should.equal(12);
111 | done();
112 | });
113 | });
114 |
115 | });
116 |
--------------------------------------------------------------------------------
/test/TextTest.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Henning Treu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import should from 'should';
18 | import nock from 'nock';
19 | import { Service, Characteristic } from 'hap-nodejs';
20 | import { CustomServices, CustomCharacteristics } from '..';
21 |
22 | import { Text } from '..';
23 |
24 | process.env.NODE_ENV = 'test';
25 |
26 | function createGenericText() {
27 | return new Text('Text Value [Generic Text]', 'http://openhab.test/rest/textName', 'Generic');
28 | }
29 |
30 | function createTemperatureText() {
31 | return new Text('Temperature [22 C]', 'http://openhab.test/rest/temperatureName', '23.5', 'NumberItem');
32 | }
33 |
34 | function createContactSensorText() {
35 | return new Text('Door [OPEN]', 'http://openhab.test/rest/contactSensorName', 'OPEN', 'ContactItem');
36 | }
37 |
38 | describe('Text', function () {
39 |
40 | describe('with generic string value', function () {
41 |
42 | it('should contain AccessoryInformation & TextInfo services', function () {
43 | let textElement = createGenericText();
44 | textElement.should.have.property('accessory');
45 | textElement.accessory.getService(Service.AccessoryInformation).should.not.be.empty;
46 | textElement.accessory.getService(CustomServices.TextInfoService).should.not.be.empty;
47 | });
48 |
49 | it('should have set the correct name', function () {
50 | let textElement = createGenericText();
51 | let accessory = textElement.accessory;
52 | accessory.getService(Service.AccessoryInformation)
53 | .getCharacteristic(Characteristic.Name).value.should.equal('Text Value');
54 | accessory.getService(CustomServices.TextInfoService)
55 | .getCharacteristic(Characteristic.Name).value.should.equal('Text Value');
56 | });
57 |
58 | it('should have set the initial value', function () {
59 | let textElement = createGenericText();
60 | textElement.accessory.getService(CustomServices.TextInfoService)
61 | .getCharacteristic(CustomCharacteristics.TextInfoCharacteristic).value.should.equal('Generic Text');
62 | });
63 |
64 | it('should update its value from openhab', function (done) {
65 | let textElement = createGenericText();
66 | let widgetJSON = JSON.stringify({
67 | label: 'Text Value [Updated Text]',
68 | type: 'Text',
69 | item: {
70 | link: 'http://openhab.test/rest/textName'
71 | }
72 | });
73 |
74 | nock('http://openhab.test')
75 | .get('/rest/sitemaps/test.sitemap?type=json')
76 | .reply(200, '{ "homepage" : { "widget" : [ ' + widgetJSON + ' ] } }');
77 |
78 | textElement.updateCharacteristics('11.9', function () {
79 | textElement.accessory.getService(CustomServices.TextInfoService)
80 | .getCharacteristic(CustomCharacteristics.TextInfoCharacteristic).value.should.equal('Updated Text');
81 | done();
82 | });
83 |
84 | });
85 |
86 | it('should read the openHAB values when homekit asks for updates', function(done) {
87 | let textElement = new Text('Text Value [Generic Text]', 'http://openhab.test/rest/textName', 'Generic');
88 |
89 | let widgetJSON = JSON.stringify({
90 | label: 'Text Value [New Text Value]',
91 | type: 'Text',
92 | item: {
93 | link: 'http://openhab.test/rest/textName'
94 | }
95 | });
96 |
97 | nock('http://openhab.test')
98 | .get('/rest/sitemaps/test.sitemap?type=json')
99 | .reply(200, '{ "homepage" : { "widget" : [ ' + widgetJSON + ' ] } }');
100 |
101 | textElement.readOpenHabText(function(err, value) {
102 | err.should.be.false;
103 | value.should.be.equal('New Text Value');
104 | done();
105 | });
106 |
107 | });
108 |
109 | });
110 |
111 |
112 | describe('with temperature', function () {
113 |
114 | it('should contain AccessoryInformation & TemperatureSensor services', function () {
115 | let textElement = createTemperatureText();
116 | textElement.should.have.property('accessory');
117 | textElement.accessory.getService(Service.AccessoryInformation).should.not.be.empty;
118 | textElement.accessory.getService(Service.TemperatureSensor).should.not.be.empty;
119 | });
120 |
121 | it('should have set the correct name', function () {
122 | let textElement = createTemperatureText();
123 | let accessory = textElement.accessory;
124 | accessory.getService(Service.AccessoryInformation)
125 | .getCharacteristic(Characteristic.Name).value.should.equal('Temperature');
126 | accessory.getService(Service.TemperatureSensor)
127 | .getCharacteristic(Characteristic.Name).value.should.equal('Temperature');
128 | });
129 |
130 | it('should have set the initial value', function () {
131 | let textElement = createTemperatureText();
132 | textElement.accessory.getService(Service.TemperatureSensor)
133 | .getCharacteristic(Characteristic.CurrentTemperature).value.should.equal(23.5);
134 | });
135 |
136 | it('should update its value from openhab', function () {
137 | let textElement = createTemperatureText();
138 |
139 | textElement.updateCharacteristics('11.9');
140 |
141 | textElement.accessory.getService(Service.TemperatureSensor)
142 | .getCharacteristic(Characteristic.CurrentTemperature).value.should.equal(11.9);
143 | });
144 |
145 | it('should make web socket connection to OpenHAB', function (done) {
146 | nock('http://openhab.test')
147 | .get('/rest/temperatureName/state?type=json')
148 | .reply(200, function(uri, requestBody) {
149 | done();
150 | });
151 | createTemperatureText();
152 | });
153 |
154 | it('should read the openHAB values when homekit asks for updates', function(done) {
155 | let textElement = new Text('Temperature [22 C]', undefined, '23.5', 'NumberItem');
156 | textElement.url = 'http://openhab.test/rest/temperatureName';
157 |
158 | nock('http://openhab.test')
159 | .get('/rest/temperatureName/state?type=json')
160 | .times(1)
161 | .reply(200, '32.123');
162 |
163 | textElement.readOpenHabText(function(err, value) {
164 | err.should.be.false;
165 | value.should.be.equal(32.123);
166 | done();
167 | });
168 |
169 | });
170 |
171 | });
172 |
173 |
174 | describe('with contact sensor', function () {
175 |
176 | it('should contain AccessoryInformation & ContactSensor services', function () {
177 | let contactSensor = createContactSensorText();
178 | contactSensor.should.have.property('accessory');
179 | contactSensor.accessory.getService(Service.AccessoryInformation).should.not.be.empty;
180 | contactSensor.accessory.getService(Service.ContactSensor).should.not.be.empty;
181 | });
182 |
183 | it('should have set the correct name', function () {
184 | let contactSensor = createContactSensorText();
185 | let accessory = contactSensor.accessory;
186 | accessory.getService(Service.AccessoryInformation)
187 | .getCharacteristic(Characteristic.Name).value.should.equal('Door');
188 | accessory.getService(Service.ContactSensor)
189 | .getCharacteristic(Characteristic.Name).value.should.equal('Door');
190 | });
191 |
192 | it('should have set the initial value', function () {
193 | let contactSensor = createContactSensorText();
194 | contactSensor.accessory.getService(Service.ContactSensor)
195 | .getCharacteristic(Characteristic.ContactSensorState).value.should
196 | .equal(Characteristic.ContactSensorState.CONTACT_NOT_DETECTED);
197 | });
198 |
199 | it('should make web socket connection to OpenHAB', function (done) {
200 | nock('http://openhab.test')
201 | .get('/rest/contactSensorName/state?type=json')
202 | .reply(200, function(uri, requestBody) {
203 | done();
204 | });
205 | createContactSensorText();
206 | });
207 |
208 | it('should update characteristics value when listener triggers', function () {
209 | let contactSensor = createContactSensorText();
210 | contactSensor.listener.callback('CLOSED');
211 |
212 | contactSensor.accessory.getService(Service.ContactSensor)
213 | .getCharacteristic(Characteristic.ContactSensorState).value.should
214 | .equal(Characteristic.ContactSensorState.CONTACT_DETECTED);
215 |
216 | contactSensor.listener.callback('OPEN');
217 |
218 | contactSensor.accessory.getService(Service.ContactSensor)
219 | .getCharacteristic(Characteristic.ContactSensorState).value.should
220 | .equal(Characteristic.ContactSensorState.CONTACT_NOT_DETECTED);
221 | });
222 |
223 | it('should read the openHAB value when homekit asks for updates', function(done) {
224 | let contactSensor = new Text('Door [OPEN]', undefined, 'OPEN', 'ContactItem');
225 | contactSensor.url = 'http://openhab.test/rest/contactSensorName';
226 |
227 | nock('http://openhab.test')
228 | .get('/rest/contactSensorName/state?type=json')
229 | .reply(200, 'Undefined');
230 |
231 | contactSensor.readOpenHabText(function(err, value) {
232 | err.should.be.false;
233 | value.should.be.equal(Characteristic.ContactSensorState.CONTACT_NOT_DETECTED);
234 | });
235 |
236 | nock('http://openhab.test')
237 | .get('/rest/contactSensorName/state?type=json')
238 | .reply(200, 'CLOSED');
239 |
240 | contactSensor.readOpenHabText(function(err, value) {
241 | err.should.be.false;
242 | value.should.be.equal(Characteristic.ContactSensorState.CONTACT_DETECTED);
243 | done();
244 | });
245 | });
246 |
247 | });
248 |
249 |
250 |
251 | });
252 |
--------------------------------------------------------------------------------
/test/resources/sitemap.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo",
3 | "label": "HomeKit",
4 | "link": "http://localhost:8080/rest/sitemaps/demo",
5 | "homepage": {
6 | "id": "demo",
7 | "title": "HomeKit",
8 | "link": "http://localhost:8080/rest/sitemaps/demo/demo",
9 | "leaf": "false",
10 | "widget": [
11 | {
12 | "widgetId": "demo_0",
13 | "type": "Switch",
14 | "label": "Toggle Switch",
15 | "icon": "switch",
16 | "item": {
17 | "type": "SwitchItem",
18 | "name": "DemoSwitch",
19 | "state": "ON",
20 | "link": "http://localhost:8080/rest/items/DemoSwitch"
21 | }
22 | },
23 | {
24 | "widgetId": "demo_1",
25 | "type": "Switch",
26 | "label": "Demo Switch",
27 | "icon": "switch",
28 | "item": {
29 | "type": "SwitchItem",
30 | "name": "DemoSwitch",
31 | "state": "ON",
32 | "link": "http://localhost:8080/rest/items/DemoSwitch"
33 | }
34 | },
35 | {
36 | "widgetId": "demo_2",
37 | "type": "Slider",
38 | "label": "Dimmed Light",
39 | "icon": "slider",
40 | "switchSupport": "true",
41 | "sendFrequency": "0",
42 | "item": {
43 | "type": "DimmerItem",
44 | "name": "DimmedLight",
45 | "state": "80",
46 | "link": "http://localhost:8080/rest/items/DimmedLight"
47 | }
48 | },
49 | {
50 | "widgetId": "demo_3",
51 | "type": "Colorpicker",
52 | "label": "RGB Light",
53 | "icon": "slider-70",
54 | "item": {
55 | "type": "ColorItem",
56 | "name": "RGBLight",
57 | "state": "144.32432432432432,41.340782122905026,70.19607843137254",
58 | "link": "http://localhost:8080/rest/items/RGBLight"
59 | }
60 | },
61 | {
62 | "widgetId": "demo_4",
63 | "type": "Switch",
64 | "label": "Livingroom",
65 | "icon": "rollershutter-0",
66 | "item": {
67 | "type": "RollershutterItem",
68 | "name": "Shutter_GF_Living",
69 | "state": "0",
70 | "link": "http://localhost:8080/rest/items/Shutter_GF_Living"
71 | }
72 | },
73 | {
74 | "widgetId": "demo_5",
75 | "type": "Text",
76 | "label": "Temperature [- °C]",
77 | "icon": "temperature",
78 | "item": {
79 | "type": "NumberItem",
80 | "name": "Temperature_GF_Living",
81 | "state": "Uninitialized",
82 | "link": "http://localhost:8080/rest/items/Temperature_GF_Living"
83 | }
84 | },
85 | {
86 | "widgetId": "demo_6",
87 | "type": "Frame",
88 | "label": "outlet",
89 | "icon": "outlet",
90 | "widget": {
91 | "widgetId": "demo_6_0",
92 | "type": "Switch",
93 | "label": "Outlet",
94 | "icon": "switch",
95 | "item": {
96 | "type": "SwitchItem",
97 | "name": "Outlet",
98 | "state": "ON",
99 | "link": "http://localhost:8080/rest/items/Outlet"
100 | }
101 | }
102 | }
103 | ]
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/test/resources/sitemap_single_item.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo",
3 | "label": "HomeKit",
4 | "link": "http://localhost:8080/rest/sitemaps/demo",
5 | "homepage": {
6 | "id": "demo",
7 | "title": "HomeKit",
8 | "link": "http://localhost:8080/rest/sitemaps/demo/demo",
9 | "leaf": "false",
10 | "widget": {
11 | "widgetId": "demo_0",
12 | "type": "Switch",
13 | "label": "Toggle Switch",
14 | "icon": "switch",
15 | "item": {
16 | "type": "SwitchItem",
17 | "name": "DemoSwitch",
18 | "state": "Uninitialized",
19 | "link": "http://localhost:8080/rest/items/DemoSwitch"
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------