Skip to content

Commit 02ea8f5

Browse files
authored
Refactor dummy IoT Sensor processing
* Move fireMotionSensor to separate file * Split ultralight from IoT Device state * Correct debug statement * Split Payload and listener * Refactoring - move IoT context-provider to sub-folder * Split Ultralight from North and South bound * Adds JSON IoT Agent support * Custom XML is a work in progress and just mirrors Ultralight for now.
1 parent b2af913 commit 02ea8f5

File tree

14 files changed

+1210
-579
lines changed

14 files changed

+1210
-579
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
//
2+
// This controller is a backdoor which allows a user to directly
3+
// interact with the IoT devices by pressing a button on screen.
4+
// The button press is converted to an NGSI call to the context
5+
// broker.
6+
//
7+
8+
const request = require('request');
9+
const debug = require('debug')('tutorial:command-listener');
10+
const Security = require('../security');
11+
const IoTDevices = require('./devices');
12+
13+
// Connect to the context broker and use fallback values if necessary
14+
const CONTEXT_BROKER = process.env.CONTEXT_BROKER || 'http://localhost:1026/v2';
15+
const NGSI_PREFIX =
16+
process.env.NGSI_LD_PREFIX !== undefined
17+
? process.env.NGSI_LD_PREFIX
18+
: 'urn:ngsi-ld:';
19+
const AUTHZFORCE_ENABLED = process.env.AUTHZFORCE_ENABLED || false;
20+
21+
// This function allows a Bell, Door or Lamp command to be sent to the Dummy IoT devices
22+
// via the Orion Context Broker and an IoT Agent.
23+
function sendCommand(req, res) {
24+
debug('sendCommand: ' + req.body.id + ' ' + req.body.action);
25+
let id = req.body.id.split(':').pop();
26+
const action = req.body.action;
27+
if (!res.locals.authorized) {
28+
// If the user is not authorized, return an error code.
29+
res.setHeader('Content-Type', 'application/json');
30+
return res.status(403).send({ message: 'Forbidden' });
31+
}
32+
33+
if (action === 'presence') {
34+
// The motion sensor does not accept commands,
35+
// Update the state of the device directly
36+
IoTDevices.fireMotionSensor('motion' + id);
37+
return res.status(204).send();
38+
}
39+
40+
if (action === 'ring') {
41+
id = 'Bell:' + id;
42+
} else if (action === 'on' || action === 'off') {
43+
id = 'Lamp:' + id;
44+
} else {
45+
id = 'Door:' + id;
46+
}
47+
48+
const payload = {};
49+
50+
payload[action] = {
51+
type: 'command',
52+
value: ''
53+
};
54+
55+
const options = {
56+
method: 'PATCH',
57+
url: CONTEXT_BROKER + '/entities/' + NGSI_PREFIX + id + '/attrs',
58+
headers: {
59+
'Content-Type': 'application/json',
60+
'fiware-servicepath': '/',
61+
'fiware-service': 'openiot'
62+
},
63+
body: payload,
64+
json: true
65+
};
66+
67+
if (req.session.access_token) {
68+
// If the system has been secured and we have logged in,
69+
// add the access token to the request to the PEP Proxy
70+
options.headers['X-Auth-Token'] = req.session.access_token;
71+
}
72+
73+
request(options, error => {
74+
if (error) {
75+
debug(error);
76+
}
77+
});
78+
79+
// Return a success code.
80+
return res.status(204).send();
81+
}
82+
83+
// Ringing the bell and unlocking the door are restricted actions, everything else
84+
// can be done by any user. This is a simple access control function to ensure
85+
// only users who are authorized can do certain things.
86+
function accessControl(req, res, next) {
87+
debug('accessControl');
88+
const action = req.body.action;
89+
if (action === 'ring') {
90+
// LEVEL 2: BASIC AUTHORIZATION - Resources are accessible on a User/Verb/Resource basis
91+
// LEVEL 3: ADVANCED AUTHORIZATION - Resources are accessible on XACML Rules
92+
return AUTHZFORCE_ENABLED
93+
? Security.authorizeAdvancedXACML(req, res, next, '/bell/ring')
94+
: Security.authorizeBasicPDP(req, res, next, '/bell/ring');
95+
} else if (action === 'unlock') {
96+
// LEVEL 2: BASIC AUTHORIZATION - Resources are accessible on a User/Verb/Resource basis
97+
// LEVEL 3: ADVANCED AUTHORIZATION - Resources are accessible on XACML Rules
98+
return AUTHZFORCE_ENABLED
99+
? Security.authorizeAdvancedXACML(req, res, next, '/door/unlock')
100+
: Security.authorizeBasicPDP(req, res, next, '/door/unlock');
101+
}
102+
// LEVEL 1: AUTHENTICATION ONLY - Every user is authorized, just ensure the user exists.
103+
return Security.authenticate(req, res, next);
104+
}
105+
106+
module.exports = {
107+
accessControl,
108+
sendCommand
109+
};
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Connect to an IoT Agent and use fallback values if necessary
2+
3+
4+
const IoTDevices = require('../devices');
5+
const DEVICE_API_KEY = process.env.DUMMY_DEVICES_API_KEY || '1234';
6+
7+
// A series of constants used by our set of devices
8+
const OK = 'OK';
9+
const NOT_OK = 'NOT OK';
10+
11+
12+
/* global MQTT_CLIENT */
13+
14+
//
15+
// Splits the deviceId from the command sent.
16+
//
17+
function getJSONCommand(string) {
18+
const obj = JSON.parse(string);
19+
return Object.keys(obj)[0];
20+
}
21+
22+
function getResult (cmd, status){
23+
const result = {};
24+
result[cmd] = status;
25+
return JSON.stringify(result);
26+
}
27+
28+
// This processor sends JSON payload northbound to
29+
// the southport of the IoT Agent and sends measures
30+
// for the motion sensor, door and lamp.
31+
32+
// A device can report new measures to the IoT Platform using an HTTP GET request to the /iot/d path with the following query parameters:
33+
//
34+
// i (device ID): Device ID (unique for the API Key).
35+
// k (API Key): API Key for the service the device is registered on.
36+
// t (timestamp): Timestamp of the measure. Will override the automatic IoTAgent timestamp (optional).
37+
// d (Data): JSON payload.
38+
//
39+
// At the moment the API key and timestamp are unused by the simulator.
40+
41+
class JSONCommand {
42+
// The bell will respond to the "ring" command.
43+
// this will briefly set the bell to on.
44+
// The bell is not a sensor - it will not report state northbound
45+
actuateBell(req, res) {
46+
47+
const command = getJSONCommand(req.body);
48+
const deviceId = 'bell' + req.params.id;
49+
50+
if (IoTDevices.notFound(deviceId)) {
51+
return res.status(404).send(getResult(command, NOT_OK));
52+
} else if (IoTDevices.isUnknownCommand('bell', command)) {
53+
return res.status(422).send(getResult(command, NOT_OK));
54+
}
55+
56+
// Update device state
57+
IoTDevices.actuateDevice(deviceId, command);
58+
return res.status(200).send(getResult(command, OK));
59+
}
60+
61+
// The door responds to "open", "close", "lock" and "unlock" commands
62+
// Each command alters the state of the door. When the door is unlocked
63+
// it can be opened and shut by external events.
64+
actuateDoor(req, res) {
65+
const command = getJSONCommand(req.body);
66+
const deviceId = 'door' + req.params.id;
67+
68+
69+
if (IoTDevices.notFound(deviceId)) {
70+
return res.status(404).send(getResult(command, NOT_OK));
71+
} else if (IoTDevices.isUnknownCommand('door', command)) {
72+
return res.status(422).send(getResult(command, NOT_OK));
73+
}
74+
75+
// Update device state
76+
IoTDevices.actuateDevice(deviceId, command);
77+
return res.status(200).send(getResult(command, OK));
78+
}
79+
80+
// The lamp can be "on" or "off" - it also registers luminosity.
81+
// It will slowly dim as time passes (provided no movement is detected)
82+
actuateLamp(req, res) {
83+
const command = getJSONCommand(req.body);
84+
const deviceId = 'lamp' + req.params.id;
85+
86+
if (IoTDevices.notFound(deviceId)) {
87+
return res.status(404).send(getResult(command, NOT_OK));
88+
} else if (IoTDevices.isUnknownCommand('lamp', command)) {
89+
return res.status(422).send(getResult(command, NOT_OK));
90+
}
91+
92+
// Update device state
93+
IoTDevices.actuateDevice(deviceId, command);
94+
return res.status(200).send(getResult(command, OK));
95+
}
96+
97+
// cmd topics are consumed by the actuators (bell, lamp and door)
98+
processMqttMessage(topic, message) {
99+
const path = topic.split('/');
100+
if (path.pop() === 'cmd') {
101+
const command = getJSONCommand(message);
102+
const deviceId = path.pop();
103+
104+
if (!IoTDevices.notFound(deviceId)) {
105+
IoTDevices.actuateDevice(deviceId, command);
106+
const topic = '/' + DEVICE_API_KEY + '/' + deviceId + '/cmdexe';
107+
MQTT_CLIENT.publish(topic, getResult(command, OK));
108+
}
109+
}
110+
}
111+
}
112+
113+
114+
115+
module.exports = JSONCommand;
116+
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// Connect to an IoT Agent and use fallback values if necessary
2+
3+
4+
const IoTDevices = require('../devices');
5+
const DEVICE_API_KEY = process.env.DUMMY_DEVICES_API_KEY || '1234';
6+
7+
8+
// A series of constants used by our set of devices
9+
const OK = ' OK';
10+
const NOT_OK = ' NOT OK';
11+
12+
/* global MQTT_CLIENT */
13+
14+
//
15+
// Splits the deviceId from the command sent.
16+
//
17+
function getUltralightCommand(string) {
18+
const command = string.split('@');
19+
if (command.length === 1) {
20+
command.push('');
21+
}
22+
return command[1];
23+
}
24+
25+
// This processor sends ultralight payload northbound to
26+
// the southport of the IoT Agent and sends measures
27+
// for the motion sensor, door and lamp.
28+
29+
// Ultralight 2.0 is a lightweight text based protocol aimed to constrained
30+
// devices and communications
31+
// where the bandwidth and device memory may be limited resources.
32+
//
33+
// A device can report new measures to the IoT Platform using an HTTP GET request to the /iot/d path with the following query parameters:
34+
//
35+
// i (device ID): Device ID (unique for the API Key).
36+
// k (API Key): API Key for the service the device is registered on.
37+
// t (timestamp): Timestamp of the measure. Will override the automatic IoTAgent timestamp (optional).
38+
// d (Data): Ultralight 2.0 payload.
39+
//
40+
// At the moment the API key and timestamp are unused by the simulator.
41+
42+
class UltralightCommand {
43+
// The bell will respond to the "ring" command.
44+
// this will briefly set the bell to on.
45+
// The bell is not a sensor - it will not report state northbound
46+
actuateBell(req, res) {
47+
const keyValuePairs = req.body.split('|') || [''];
48+
const command = getUltralightCommand(keyValuePairs[0]);
49+
const deviceId = 'bell' + req.params.id;
50+
const result = keyValuePairs[0] + '| ' + command;
51+
52+
if (IoTDevices.notFound(deviceId)) {
53+
return res.status(404).send(result + NOT_OK);
54+
} else if (IoTDevices.isUnknownCommand('bell', command)) {
55+
return res.status(422).send(result + NOT_OK);
56+
}
57+
58+
// Update device state
59+
IoTDevices.actuateDevice(deviceId, command);
60+
return res.status(200).send(result + OK);
61+
}
62+
63+
// The door responds to "open", "close", "lock" and "unlock" commands
64+
// Each command alters the state of the door. When the door is unlocked
65+
// it can be opened and shut by external events.
66+
actuateDoor(req, res) {
67+
const keyValuePairs = req.body.split('|') || [''];
68+
const command = getUltralightCommand(keyValuePairs[0]);
69+
const deviceId = 'door' + req.params.id;
70+
const result = keyValuePairs[0] + '| ' + command;
71+
72+
if (IoTDevices.notFound(deviceId)) {
73+
return res.status(404).send(result + NOT_OK);
74+
} else if (IoTDevices.isUnknownCommand('door', command)) {
75+
return res.status(422).send(result + NOT_OK);
76+
}
77+
78+
// Update device state
79+
IoTDevices.actuateDevice(deviceId, command);
80+
return res.status(200).send(result + OK);
81+
}
82+
83+
// The lamp can be "on" or "off" - it also registers luminosity.
84+
// It will slowly dim as time passes (provided no movement is detected)
85+
actuateLamp(req, res) {
86+
const keyValuePairs = req.body.split('|') || [''];
87+
const command = getUltralightCommand(keyValuePairs[0]);
88+
const deviceId = 'lamp' + req.params.id;
89+
const result = keyValuePairs[0] + '| ' + command;
90+
91+
if (IoTDevices.notFound(deviceId)) {
92+
return res.status(404).send(result + NOT_OK);
93+
} else if (IoTDevices.isUnknownCommand('lamp', command)) {
94+
return res.status(422).send(result + NOT_OK);
95+
}
96+
97+
// Update device state
98+
IoTDevices.actuateDevice(deviceId, command);
99+
return res.status(200).send(result + OK);
100+
}
101+
102+
// cmd topics are consumed by the actuators (bell, lamp and door)
103+
processMqttMessage(topic, message) {
104+
const path = topic.split('/');
105+
if (path.pop() === 'cmd') {
106+
const keyValuePairs = message.split('|') || [''];
107+
const command = getUltralightCommand(keyValuePairs[0]);
108+
const deviceId = path.pop();
109+
const result = keyValuePairs[0] + '| ' + command;
110+
111+
if (!IoTDevices.notFound(deviceId)) {
112+
IoTDevices.actuateDevice(deviceId, command);
113+
const topic = '/' + DEVICE_API_KEY + '/' + deviceId + '/cmdexe';
114+
MQTT_CLIENT.publish(topic, result + OK);
115+
}
116+
}
117+
}
118+
}
119+
120+
121+
122+
module.exports = UltralightCommand;
123+

0 commit comments

Comments
 (0)