Skip to content

Commit 19d161b

Browse files
committed
feat: create initial version
1 parent 06edcd9 commit 19d161b

40 files changed

+1168
-10319
lines changed

.angular-cli.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
"root": "src",
99
"outDir": "dist",
1010
"assets": [
11-
"assets",
12-
"favicon.ico"
11+
"assets"
1312
],
1413
"index": "index.html",
1514
"main": "main.ts",

.codeclimate.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
engines:
2+
eslint:
3+
enabled: true
4+
fixme:
5+
enabled: true
6+
markdownlint:
7+
enabled: true
8+
duplication:
9+
enabled: false
10+
exclude_paths:
11+
- "CHANGELOG.md"
12+
ratings:
13+
paths:
14+
- "lib/**/*"
15+
- "src/**/*"
16+
- "main.js"

.editorconfig

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
# Editor configuration, see http://editorconfig.org
1+
# http://editorconfig.org
22
root = true
33

44
[*]
5-
charset = utf-8
65
indent_style = space
76
indent_size = 2
8-
insert_final_newline = true
7+
end_of_line = lf
8+
charset = utf-8
99
trim_trailing_whitespace = true
10+
insert_final_newline = true
1011

1112
[*.md]
12-
max_line_length = off
1313
trim_trailing_whitespace = false

.travis.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
language: "node_js"
2+
node_js:
3+
- "7"
4+
- "6"
5+
- "5"
6+
- "4"
7+
8+
before_install:
9+
- "npm install coveralls"
10+
11+
script:
12+
- "commitlint-travis"
13+
- "npm run lint"
14+
- "npm run test"
15+
- "cat ./reports/coverage/lcov.info | ./node_modules/.bin/coveralls"
16+
17+
sudo: false

LICENSE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2017 fh1ch
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,81 @@
1-
# NodeBacstackBrowser
1+
# Node BACstack Browser
22

3-
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.6.2.
3+
A BACnet browser using [Electron](https://electronjs.org/), [Angular](https://angular.io/)
4+
and [Node BACstack](https://github.com/fh1ch/node-bacstack).
45

5-
## Development server
6+
[![](https://travis-ci.org/fh1ch/node-bacstack-browser.svg?branch=master)](https://travis-ci.org/fh1ch/node-bacstack-browser)
7+
[![](https://coveralls.io/repos/fh1ch/node-bacstack-browser/badge.svg?branch=master)](https://coveralls.io/r/fh1ch/node-bacstack-browser?branch=master)
8+
[![](https://codeclimate.com/github/fh1ch/node-bacstack-browser/badges/gpa.svg)](https://codeclimate.com/github/fh1ch/node-bacstack-browser)
9+
[![](https://david-dm.org/fh1ch/node-bacstack-browser/status.svg)](https://david-dm.org/fh1ch/node-bacstack-browser)
610

7-
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
11+
> **Note:** This is an early prototype and shall not be considered as stable.
12+
> Use it with caution and at your own risk!
813
9-
## Code scaffolding
14+
![](images/overview.gif)
1015

11-
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
16+
## Usage
1217

13-
## Build
18+
Start Node BACstack Browser by using:
1419

15-
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
20+
``` sh
21+
git clone https://github.com/fh1ch/node-bacstack-browser.git
1622

17-
## Running unit tests
23+
cd node-bacstack-browser
24+
npm i
1825

19-
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
26+
npm start
27+
```
2028

21-
## Running end-to-end tests
29+
> **Note:** If the application window appears as a blank page, force refresh the
30+
> page using F5 (Windows) or CMD+R (OSX).
2231
23-
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
32+
## Contributing
2433

25-
## Further help
34+
Any help is appreciated, from creating issues, to contributing documentation,
35+
fixing issues and adding new features.
2636

27-
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
37+
Please follow the best-practice contribution guidelines as mentioned below when
38+
submitting any changes.
39+
40+
### Conventional Changelog
41+
42+
This module has a changelog which is automatically generated based on Git commit
43+
messages. This mechanism requires that all commit messages comply with the
44+
[Conventional Changelog](https://github.com/bcoe/conventional-changelog-standard/blob/master/convention.md).
45+
You can check if your commit messages complies with those guidelines by using:
46+
47+
``` sh
48+
npm run changelog
49+
```
50+
51+
### Code Style
52+
53+
This module uses [TSLint](https://palantir.github.io/tslint/) with the
54+
[Angular-CLI](https://github.com/angular/angular-cli) default settings for code
55+
linting. You can test if your changes comply with the code style by executing:
56+
57+
``` sh
58+
npm run lint
59+
```
60+
61+
### Testing and Coverage
62+
63+
Testing is done using [Karama](https://karma-runner.github.io/)
64+
and [Protractor](http://www.protractortest.org/), both provided by the
65+
[Angular-CLI](https://github.com/angular/angular-cli).
66+
67+
The test-coverage is automatically calculated using [Istanbul](https://istanbul.js.org/).
68+
Running the tests and calculating the coverage can be done locally by executing:
69+
70+
``` sh
71+
npm run test && npm run e2e
72+
```
73+
74+
It is expected that new features or fixes do not negatively impact the test
75+
results or the coverage.
76+
77+
## License
78+
79+
[The MIT License](http://opensource.org/licenses/MIT)
80+
81+
Copyright (c) 2017 Fabio Huser <[email protected]>

images/overview.gif

5.34 MB
Loading

lib/api.js

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
const os = require('os');
2+
const ip = require('ip');
3+
const async = require('async');
4+
const express = require('express');
5+
const bacnet = require('bacstack');
6+
const debug = require('debug')('bacstack-browser');
7+
8+
const utils = require('./utils');
9+
10+
const app = express();
11+
12+
// Local stores
13+
const settings = {
14+
port: 47808,
15+
nic: 12,
16+
timeout: 4000,
17+
language: 'en',
18+
analytics: true
19+
};
20+
21+
const devices = {};
22+
23+
// BACNET Stuff
24+
const client = new bacnet({adpuTimeout: 6000});
25+
26+
client.on('iAm', (device) => {
27+
const id = `${device.address}:${device.deviceId}`;
28+
devices[id] = device;
29+
devices[id].id = id;
30+
client.readPropertyMultiple(device.address, [
31+
{objectId: {type: 8, instance: 4194303}, properties: [{id: bacnet.enum.PropertyIds.PROP_OBJECT_NAME}, {id: bacnet.enum.PropertyIds.PROP_DESCRIPTION}]}
32+
], (err, value) => {
33+
if (err) return;
34+
if (value && value.values && value.values[0] && value.values[0].values) {
35+
const tmp = {};
36+
value.values[0].values.forEach(data => tmp[data.id] = data.value[0])
37+
devices[id].name = tmp[bacnet.enum.PropertyIds.PROP_OBJECT_NAME].value;
38+
devices[id].description = tmp[bacnet.enum.PropertyIds.PROP_DESCRIPTION].value;
39+
}
40+
});
41+
});
42+
43+
// Webserver Stuff
44+
app.use((req, res, next) => {
45+
res.header('Access-Control-Allow-Origin', '*');
46+
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
47+
res.header('Access-Control-Allow-Headers', 'Content-Type');
48+
next();
49+
});
50+
51+
// Settings
52+
app.get('/api/settings', (req, res) => {
53+
res.send(settings);
54+
});
55+
56+
app.post('/api/settings', (req, res) => {
57+
res.send(req.body);
58+
});
59+
60+
app.get('/api/settings/interfaces', (req, res) => {
61+
const nics = [];
62+
const osNics = os.networkInterfaces();
63+
Object.keys(osNics).forEach((ifname) => {
64+
osNics[ifname].forEach((iface) => {
65+
if (iface.interal === true) return;
66+
if (iface.family !== 'IPv4') return;
67+
nics.push({
68+
name: ifname,
69+
address: iface.address,
70+
broadcast: ip.subnet(iface.address, iface.netmask).broadcastAddress
71+
});
72+
});
73+
});
74+
res.send(nics);
75+
});
76+
77+
// Devices
78+
app.get('/api/devices', (req, res) => {
79+
res.send(Object.keys(devices).map((key) => devices[key]));
80+
});
81+
82+
app.get('/api/devices/search', (req, res) => {
83+
client.whoIs();
84+
res.sendStatus(204);
85+
});
86+
87+
// Objects
88+
app.get('/api/devices/:id/objects', (req, res) => {
89+
const device = devices[req.params.id];
90+
if (!device) return res.sendStatus(404);
91+
client.readPropertyMultiple(device.address, [
92+
{objectId: {type: 8, instance: 4194303}, properties: [{id: bacnet.enum.PropertyIds.PROP_ALL}]}
93+
], (err, value) => {
94+
if (err) return res.sendStatus(500);
95+
const tmp = {};
96+
if (!(value && value.values && value.values[0] && value.values[0].values)) return res.sendStatus(500);
97+
value.values[0].values.forEach(data => tmp[data.id] = data.value)
98+
async.mapSeries(tmp[bacnet.enum.PropertyIds.PROP_OBJECT_LIST], (item, next) => {
99+
console.log(item);
100+
client.readPropertyMultiple(device.address, [
101+
{objectId: {type: item.value.type, instance: item.value.instance}, properties: [{id: bacnet.enum.PropertyIds.PROP_ALL}]}
102+
], (err, value) => {
103+
if (err) return next(null, {});
104+
const tmp = {};
105+
if (!(value && value.values && value.values[0] && value.values[0].values)) return next(null, {});
106+
value.values[0].values.forEach(data => tmp[data.id] = data.value)
107+
next(null, {
108+
id: `${item.value.type}:${item.value.instance}`,
109+
type: item.value.type,
110+
name: tmp[bacnet.enum.PropertyIds.PROP_OBJECT_NAME] ? tmp[bacnet.enum.PropertyIds.PROP_OBJECT_NAME][0].value : '',
111+
description: tmp[bacnet.enum.PropertyIds.PROP_DESCRIPTION] ? tmp[bacnet.enum.PropertyIds.PROP_DESCRIPTION][0].value : '',
112+
value: tmp[bacnet.enum.PropertyIds.PROP_PRESENT_VALUE] ? tmp[bacnet.enum.PropertyIds.PROP_PRESENT_VALUE][0].value : null
113+
});
114+
});
115+
}, (err, values) => {
116+
res.send(values);
117+
});
118+
});
119+
});
120+
121+
app.get('/api/devices/:id/objects/:oid', (req, res) => {
122+
const device = devices[req.params.id];
123+
if (!device) return res.sendStatus(404);
124+
const oid = req.params.oid.split(':');
125+
if (!oid[0] || !oid[1]) return res.sendStatus(404);
126+
client.readPropertyMultiple(device.address, [
127+
{objectId: {type: oid[0], instance: oid[1]}, properties: [{id: bacnet.enum.PropertyIds.PROP_ALL}]}
128+
], (err, value) => {
129+
if (err) return res.sendStatus(500);
130+
if (!(value && value.values && value.values[0] && value.values[0].values)) return res.sendStatus(500);
131+
let properties = value.values[0].values;
132+
properties = properties.map(property => {return {
133+
id: property.id,
134+
name: utils.getPropertyName(property.id) || `Vendor Specific Property ${property.id}`,
135+
value: property.value
136+
}});
137+
res.send(properties);
138+
});
139+
});
140+
141+
app.listen(3000, '127.0.0.1', () => {
142+
console.log('Example app listening on port 3000!');
143+
});

lib/utils.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const _ = require('lodash');
2+
const bacnet = require('bacstack');
3+
4+
module.exports.getPropertyName = (id) => {
5+
let name = Object.keys(bacnet.enum.PropertyIds).find(key => bacnet.enum.PropertyIds[key] === id);
6+
if (!name) return;
7+
name = _.startCase(_.lowerCase(name.substring(5)));
8+
return name;
9+
};

main.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
const { app, BrowserWindow } = require('electron');
2+
3+
const api = require('./lib/api');
4+
5+
let win;
6+
7+
app.on('ready', () => {
8+
win = new BrowserWindow({width: 1000, height: 600});
9+
10+
win.loadURL('http://localhost:4200');
11+
12+
win.webContents.openDevTools();
13+
14+
win.on('closed', () => {
15+
win = null;
16+
});
17+
});
18+
19+
app.on('activate', () => {
20+
if (!win) createWindow();
21+
})
22+
23+
app.on('window-all-closed', () => {
24+
if (process.platform !== 'darwin') app.quit();
25+
});

0 commit comments

Comments
 (0)