thing-it-device-hibox
Version:
[thing-it-node] Device Plugin for HiBox Systems, TV Experts and Engineers.
676 lines (606 loc) • 20 kB
JavaScript
/**
* The module exports are used by the [thing-it-node].
* the first couple before state are mandatory for the device.
*
* State will be shown in many default screens and can very easily
* be accessed in the HTML UI and other places.
*
* Services will be exposed in UIs as invocable by the user
* and they will be exposed for orchestration.
*
* Configuration will be displayed when adding a device
* on www.thing-it.com and in allows the device to access
* the users' values during device creation on www.thing-it.com
*
*/
module.exports = {
metadata: {
family: "HiBoxTV",
plugin: "HiBoxTV",
label: "HiBox TV",
tangible: true,
discoverable: false,
state: [{
id: "on",
label: "On",
type: {
id: "boolean"
}
}, {
id: "volume",
label: "Volume",
type: {
id: "integer"
}
}, {
id: "channel",
label: "Channel",
type: {
id: "String"
}
}, {
id: "channelName",
label: "Channel Name",
type: {
id: "String"
}
}, {
id: "availableChannels",
label: "Available Channels",
type: {
id: "Object"
}
}],
actorTypes: [],
sensorTypes: [],
services: [{
id: "toggleStandby",
label: "Toggle Standby"
}, {
id: "setChannel1",
label: "Set Channel 1"
}, {
id: "setChannel2",
label: "Set Channel 2"
}, {
id: "setChannel3",
label: "Set Channel 3"
}, {
id: "setChannel4",
label: "Set Channel 4"
}, {
id: "setChannel5",
label: "Set Channel 5"
}, {
id: "changeVolume",
label: "Change Volume"
}],
configuration: [{
id: "user",
label: "API User ID",
type: {
id: "string"
}
}, {
id: "password",
label: "API User Password",
type: {
id: "string"
}
}, {
id: "baseURL",
label: "API Base URL",
type: {
id: "string"
}
}, {
id: "targetURL",
label: "Target TV URL",
type: {
id: "string"
}
}]
},
/**
* Invoked during start up to create the instance of
* HiBoxTV for this specific device.
*
*/
create: function (device) {
return new HiBoxTV();
},
/**
* Discovery is an advanced function we don't need
* for our Hello World example.
*
*/
discovery: function (options) {
var discovery = new HiBoxTVDiscovery();
discovery.options = options;
return discovery;
}
};
var q = require('q');
var request;
var https;
var requestPromise;
//var WorldConnectionAPI;
/**
* Discovery is an advanced function we don't need
* for our Hello World example.
*
*/
function HiBoxTVDiscovery() {
/**
*
*/
HiBoxTVDiscovery.prototype.start = function () {
};
/**
*
*/
HiBoxTVDiscovery.prototype.stop = function () {
};
}
/**
*
*/
function HiBoxTV() {
var accessToken;
HiBoxTV.prototype.start = function () {
if (!request) {
request = require('request');
}
// Initialize state values
this.state = {
on: true,
volume: 0,
channel: null,
channelName: null,
availableChannels: null
};
var promise = null;
accessToken = null;
if ((typeof this.configuration.user === undefined ) || !this.configuration.user || ("" == this.configuration.user)) {
promise = q.reject("A HiBox TV API user is required for this device to work.");
}
if ((typeof this.configuration.password === undefined ) || !this.configuration.password || ("" == this.configuration.password)) {
promise = q.reject("A HiBox TV API password is required for this device to work.");
}
if ((typeof this.configuration.baseURL === undefined ) || !this.configuration.baseURL || ("" == this.configuration.baseURL)) {
promise = q.reject("A HiBox TV API base URL is required for this device to work.");
}
if ((typeof this.configuration.targetURL === undefined ) || !this.configuration.targetURL || ("" == this.configuration.targetURL)) {
promise = q.reject("A HiBox TV API target URL is required for this device to work.");
}
if (this.isSimulated()) {
// ignore, all we need is an internet connection.
}
if (null === promise) {
this.logInfo("Starting up HiBox TV.");
//TODO - dynamically load channel options
this.state.availableChannels = [
{displayName: "Yle TV1", id: this.configuration.baseURL + "/bas/media/ch-Mjg1NDk="},
{displayName: "Yle TV2", id: this.configuration.baseURL + "/bas/media/ch-Mjg1NTI="},
{displayName: "MTV3", id: this.configuration.baseURL + "/bas/media/ch-Mjg1NTU="},
{displayName: "Nelonen", id: this.configuration.baseURL + "/bas/media/ch-Mjg1NTc="},
{displayName: "SUBTV", id: this.configuration.baseURL + "/bas/media/ch-MzA5OTI="},
];
//TODO - create actors for the TV
//TODO - identify URL to actors via navigation of BAS based on property, floor, room config
promise = this.retrieveAccessToken()
.then(function (token) {
this.logDebug("access token retrieved.", token);
accessToken = token;
return token;
}.bind(this))
.catch(function (error) {
this.logError(error);
return q.reject(error);
}.bind(this));
}
return promise;
};
HiBoxTV.prototype.stop = function () {
};
HiBoxTV.prototype.retrieveAccessToken = function () {
var deferred = q.defer();
this.logDebug("Authenticating");
var options = {
method: 'POST',
url: this.configuration.baseURL + "/authentication/user",
body: {
username: this.configuration.user,
password: this.configuration.password
},
headers: {
'Accept': 'application/hal+json',
'Content-Type': 'application/json'
},
json: true
};
this.logDebug("Request URL", options.url);
this.logDebug("Authenticating with user.", options.body.username);
request(options, function (error, response, authenticationBody) {
if (error) {
deferred.reject(error);
} else {
if ((typeof authenticationBody.access_token !== undefined) &&
(authenticationBody.access_token) && ("" != authenticationBody.access_token)) {
deferred.resolve(authenticationBody.access_token);
} else {
this.logDebug(authenticationBody);
deferred.reject("Could not retrieve acces token.");
}
}
}.bind(this));
return deferred.promise;
};
/**
*
*/
HiBoxTV.prototype.mute = function () {
var deferred = q.defer();
var options = {
method: 'POST',
url: this.configuration.baseURL + '/bas/intent',
headers: {
'Accept': 'application/hal+json',
'Content-Type': 'application/vnd.hbx.bas.intent+json',
'Authorization': 'HBX-TOKEN apikey="' + accessToken + '", realm="bas"'
},
json: true,
};
request(options, function (error, response, parsedBody) {
if (error) {
deferred.reject(error);
} else {
this.logDebug(parsedBody);
deferred.resolve(parsedBody);
}
}.bind(this));
return deferred.promise;
};
HiBoxTV.prototype.executeRequest = function (requestParameters) {
var deferred = q.defer();
var options = {
method: 'POST',
url: requestParameters.url,
body: requestParameters.body,
headers: {
'Accept': 'application/hal+json',
'Content-Type': 'application/vnd.hbx.bas.intent+json',
'Authorization': 'HBX-TOKEN apikey="' + accessToken + '", realm="bas"'
},
json: true,
};
request(options, function (error, response, parsedBody) {
if (error) {
deferred.reject(error);
} else {
//this.logDebug(parsedBody);
deferred.resolve(parsedBody);
}
}.bind(this));
return deferred.promise;
};
HiBoxTV.prototype.sendVolumeRequest = function (volume) {
return this.executeRequest({
url: this.configuration.baseURL + '/bas/intent',
body: {"target": [this.configuration.targetURL], "action": {"type": "set-volume", "value": volume}}
});
};
HiBoxTV.prototype.setVolume = function (volume) {
this.logDebug("Setting volume to " + volume);
return this.sendVolumeRequest(volume)
.then(function (parsedBody) {
this.logInfo("Volume set to " + volume + ".");
this.publishStateChange();
}.bind(this), function (err) {
this.logError(err);
}.bind(this));
};
HiBoxTV.prototype.changeVolume = function (parameters) {
this.logDebug("ChangeVolume called: ", parameters);
var volumeInteger;
if (typeof parameters.level === 'string' || volume instanceof String) {
volumeInteger = parseInt(parameters.level);
}
else
volumeInteger = parameters.level;
return this.setVolume(volumeInteger);
};
HiBoxTV.prototype.sendStandbyRequest = function (standby) {
return this.executeRequest({
url: this.configuration.baseURL + '/bas/intent',
body: {"target": [this.configuration.targetURL], "action": {"type": "set-standby", "value": standby}}
});
};
HiBoxTV.prototype.setStandby = function (standby) {
this.logDebug("Setting standby to " + standby);
return this.sendStandbyRequest(standby)
.then(function (parsedBody) {
this.state.on = !standby;
this.publishStateChange();
this.logInfo("Standby set to " + standby + ".");
}.bind(this));
};
HiBoxTV.prototype.on = function () {
this.state.on = true;
return this.setStandby(false);
};
HiBoxTV.prototype.off = function () {
this.state.on = false;
return this.setStandby(true);
};
HiBoxTV.prototype.toggleStandby = function () {
this.logDebug("TV on: ", this.state.on);
if (this.state.on) {
return this.off();
}
else {
return this.on();
}
};
HiBoxTV.prototype.sendTuneChannelRequest = function (channelUrl) {
return this.executeRequest({
url: this.configuration.baseURL + '/bas/intent',
body: {"target": [this.configuration.targetURL], "action": {"type": "tune-channel", "value": channelUrl}}
});
};
HiBoxTV.prototype.setChannel = function (channelUrl) {
var deferred = q.defer();
var promise = null;
this.logDebug("Setting channelUrl to " + channelUrl);
this.logDebug("Checking if " + channelUrl + " is in channel Array with length of "
+ this.state.availableChannels.length);
for (index = 0; index < this.state.availableChannels.length; index++) {
if (channelUrl == this.state.availableChannels[index].id) {
promise = this.sendTuneChannelRequest(this.state.availableChannels[index].id)
.then(function () {
this.state.channelName = this.state.availableChannels[index].displayName;
this.state.channel = this.state.availableChannels[index].id;
this.publishStateChange();
this.logDebug("Channel set to " + this.state.channelName + " (" + this.state.channel + ")");
return true;
}.bind(this));
deferred.resolve();
break;
}
}
if (!promise) {
var e = new Error("Channel Url (" + channelUrl + ") not found in list of available channels.");
this.logError(e);
deferred.reject(e);
promise = deferred.promise;
}
return promise;
};
HiBoxTV.prototype.changeChannel = function (parameters) {
this.logDebug("ChangeInput called: ", parameters);
return this.setChannel(parameters.channel);
};
HiBoxTV.prototype.setChannel1 = function () {
this.setChannel(this.configuration.baseURL + "/bas/media/ch-Mjg1NDk=");
}
HiBoxTV.prototype.setChannel2 = function () {
this.setChannel(this.configuration.baseURL + "/bas/media/ch-Mjg1NTI=");
}
HiBoxTV.prototype.setChannel3 = function () {
this.setChannel(this.configuration.baseURL + "/bas/media/ch-Mjg1NTU=");
}
HiBoxTV.prototype.setChannel4 = function () {
this.setChannel(this.configuration.baseURL + "/bas/media/ch-Mjg1NTc=");
}
HiBoxTV.prototype.setChannel5 = function () {
this.setChannel(this.configuration.baseURL + "/bas/media/ch-MzA5OTI=");
}
/**
*
*/
HiBoxTV.prototype.update = function () {
this.connect();
};
/**
*
*/
HiBoxTV.prototype.setState = function (state) {
this.state = state;
this.publishStateChange();
};
/**
*
*/
HiBoxTV.prototype.getState = function () {
return this.state;
};
}
HiBoxForm = {
"name": "generic",
"title": "Intent Request",
"href": "https://demo.hibox.fi/hbx/api/bas/intent",
"method": "POST",
"type": "application/vnd.hbx.bas.intent+json",
"schema": {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "BAS Intent Request",
"definitions": {
"booleanString": {
"type": "string",
"enum": [
"true",
"false"
]
},
"percentRangeString": {
"type": "string",
"pattern": "^(?:100|[1-9]?[0-9])$"
},
"actionMuteVolume": {
"properties": {
"type": {
"type": "string",
"enum": [
"mute-volume"
]
},
"value": {
"$ref": "#/definitions/booleanString"
}
},
"required": [
"type",
"value"
]
},
"actionSetVolume": {
"properties": {
"type": {
"type": "string",
"enum": [
"set-volume"
]
},
"value": {
"$ref": "#/definitions/percentRangeString"
}
},
"required": [
"type",
"value"
]
},
"actionSetStandby": {
"properties": {
"type": {
"type": "string",
"enum": [
"set-standby"
]
},
"value": {
"$ref": "#/definitions/booleanString"
}
},
"required": [
"type",
"value"
]
},
"actionTuneChannel": {
"properties": {
"type": {
"type": "string",
"enum": [
"tune-channel"
]
},
"value": {
"type": "string",
"format": "uri"
}
},
"required": [
"type",
"value"
]
},
"actionScreenSharing": {
"properties": {
"type": {
"type": "string",
"enum": [
"screen-sharing"
]
},
"value": {
"$ref": "#/definitions/booleanString"
}
},
"required": [
"type",
"value"
]
},
"actionNoOp": {
"properties": {
"type": {
"type": "string",
"enum": [
"no-op"
]
}
},
"required": [
"type"
]
}
},
"properties": {
"target": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
}
},
"action": {
"anyOf": [
{
"$ref": "#/definitions/actionMuteVolume"
},
{
"$ref": "#/definitions/actionSetVolume"
},
{
"$ref": "#/definitions/actionSetStandby"
},
{
"$ref": "#/definitions/actionTuneChannel"
},
{
"$ref": "#/definitions/actionScreenSharing"
},
{
"$ref": "#/definitions/actionNoOp"
}
]
}
},
"required": [
"target",
"action"
]
},
"intentOptions": [
{
"type": "input",
"field": "boolean",
"name": "mute-volume"
},
{
"type": "input",
"field": "boolean",
"name": "set-standby"
},
{
"type": "input",
"field": "number",
"name": "set-volume",
"max": "100",
"min": "0"
},
{
"type": "datalist",
"field": "url",
"name": "tune-channel",
"list": "bas:channel-options",
"listItemPath": "$._links.self.href"
},
{
"type": "input",
"field": "boolean",
"name": "screen-sharing"
}
]
}