homebridge-sharp-aquos-tv 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/InputsSample.json +30 -0
- package/LICENSE +21 -0
- package/README.md +41 -0
- package/build.sh +18 -0
- package/config.schema.json +100 -0
- package/index.js +660 -0
- package/package.json +36 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"inputs": [
|
|
2
|
+
{
|
|
3
|
+
"inputID": "1",
|
|
4
|
+
"name": "HDMI 1"
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
"inputID": "2",
|
|
8
|
+
"name": "HDMI 2"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"inputID": "3",
|
|
12
|
+
"name": "HDMI 3"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"inputID": "4",
|
|
16
|
+
"name": "HDMI 4"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"inputID": "5",
|
|
20
|
+
"name": "Component"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"inputID": "6",
|
|
24
|
+
"name": "Video 1"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"inputID": "7",
|
|
28
|
+
"name": "Video 2"
|
|
29
|
+
}
|
|
30
|
+
]
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Marcin
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# homebridge-sharp-aquos
|
|
2
|
+
Homebridge plugin for Sharp Aquos TVs.
|
|
3
|
+
Exposes TV accessories with power and input controls.
|
|
4
|
+
TV will be unbridged, so you will have to add it manually in Home via + > Add Accessory > More options...
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
Place the files in `node_modules/homenbridge-sharp-tv` and `npm install .`
|
|
8
|
+
Or package it and install with npm:
|
|
9
|
+
```
|
|
10
|
+
tar -zcvf ../homebridge-sharp-tv.tar.gz .
|
|
11
|
+
cd ..
|
|
12
|
+
npm install homebridge-sharp-tv.tar.gz
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Sample Config
|
|
16
|
+
```
|
|
17
|
+
{
|
|
18
|
+
"pollInterval": 60,
|
|
19
|
+
"debugToInfo": false,
|
|
20
|
+
"devices": [
|
|
21
|
+
{
|
|
22
|
+
"name": "Sharp TV",
|
|
23
|
+
"ip": "10.0.0.41",
|
|
24
|
+
"defaultInputID": "1",
|
|
25
|
+
"inputs": [
|
|
26
|
+
{
|
|
27
|
+
"inputID": "1",
|
|
28
|
+
"name": "HDMI 1"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"platform": "SharpTV"
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Control Center Remote
|
|
38
|
+
|
|
39
|
+
* Info maps to DISPLAY
|
|
40
|
+
* Play/Pause maps to MENU
|
|
41
|
+
* Back maps to RETURN
|
package/build.sh
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
PKG=`basename $PWD`
|
|
2
|
+
DEST=../build
|
|
3
|
+
rm -rf ../build
|
|
4
|
+
mkdir -p ../build || exit
|
|
5
|
+
for f in *; do
|
|
6
|
+
if [ "$f" != "node_modules" ] && \
|
|
7
|
+
[ "$f" != "build.sh" ] && \
|
|
8
|
+
[ "$f" != "nodemon.json" ] && \
|
|
9
|
+
[ "$f" != "src" ]
|
|
10
|
+
then
|
|
11
|
+
cp -rv $f ../build
|
|
12
|
+
fi
|
|
13
|
+
done
|
|
14
|
+
cd ../build || exit
|
|
15
|
+
npm pkg delete devDependencies || exit
|
|
16
|
+
cd ../$PKG || exit
|
|
17
|
+
tar -zcvf ../$PKG.tar.gz -C ../build . || exit
|
|
18
|
+
rm -rf ../build
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pluginAlias": "SharpTV",
|
|
3
|
+
"pluginType": "platform",
|
|
4
|
+
"footerDisplay": "Homebridge plugin for Sharp Aquos TVs.",
|
|
5
|
+
"schema": {
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"pollInterval": {
|
|
9
|
+
"title": "Polling interval",
|
|
10
|
+
"description": "The state of the TV is polled every 60 seconds. Change this interval with this value. The value is the interval in seconds.",
|
|
11
|
+
"type": "integer",
|
|
12
|
+
"placeholder": 60,
|
|
13
|
+
"required": false
|
|
14
|
+
},
|
|
15
|
+
"debugToInfo": {
|
|
16
|
+
"title": "Print debug log in info log.",
|
|
17
|
+
"description": "Set to true if you wan't to debug the plugin but you don't have access to the debug log.",
|
|
18
|
+
"type": "boolean",
|
|
19
|
+
"default": false,
|
|
20
|
+
"required": false
|
|
21
|
+
},
|
|
22
|
+
"devices": {
|
|
23
|
+
"description": "Name and IP of TVs to create accessories for",
|
|
24
|
+
"title": "Devices",
|
|
25
|
+
"type": "array",
|
|
26
|
+
"items": {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"properties": {
|
|
29
|
+
"name": {
|
|
30
|
+
"title": "Name",
|
|
31
|
+
"type": "string",
|
|
32
|
+
"default": "Sharp TV",
|
|
33
|
+
"required": true,
|
|
34
|
+
"minLength": 4
|
|
35
|
+
},
|
|
36
|
+
"ip": {
|
|
37
|
+
"title": "IP address",
|
|
38
|
+
"type": "string",
|
|
39
|
+
"placeholder": "192.168.xx.xx",
|
|
40
|
+
"format": "ipv4",
|
|
41
|
+
"required": true,
|
|
42
|
+
"minLength": 4
|
|
43
|
+
},
|
|
44
|
+
"defaultInputID": {
|
|
45
|
+
"title": "Default InputID",
|
|
46
|
+
"type": "string",
|
|
47
|
+
"placeholder": "None",
|
|
48
|
+
"required": false,
|
|
49
|
+
"typeahead": {
|
|
50
|
+
"source": [
|
|
51
|
+
"1",
|
|
52
|
+
"2",
|
|
53
|
+
"3",
|
|
54
|
+
"4",
|
|
55
|
+
"5",
|
|
56
|
+
"6",
|
|
57
|
+
"7"
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"inputs": {
|
|
62
|
+
"title": "List with selectable inputs",
|
|
63
|
+
"description": "The list with used inputs.",
|
|
64
|
+
"type": "array",
|
|
65
|
+
"items": {
|
|
66
|
+
"type": "object",
|
|
67
|
+
"properties": {
|
|
68
|
+
"inputID": {
|
|
69
|
+
"title": "Input ID",
|
|
70
|
+
"type": "string",
|
|
71
|
+
"placeholder": "1",
|
|
72
|
+
"required": true,
|
|
73
|
+
"typeahead": {
|
|
74
|
+
"source": [
|
|
75
|
+
"1",
|
|
76
|
+
"2",
|
|
77
|
+
"3",
|
|
78
|
+
"4",
|
|
79
|
+
"5",
|
|
80
|
+
"6",
|
|
81
|
+
"7"
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"name": {
|
|
86
|
+
"title": "Input name",
|
|
87
|
+
"type": "string",
|
|
88
|
+
"placeholder": "HDMI 1",
|
|
89
|
+
"required": true,
|
|
90
|
+
"minLength": 1
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
const { Telnet } = require('telnet-client');
|
|
2
|
+
|
|
3
|
+
const pluginName = 'homebridge-sharp-aquos-tv';
|
|
4
|
+
const platformName = 'SharpTV';
|
|
5
|
+
const pluginVersion = '3.0.0';
|
|
6
|
+
|
|
7
|
+
const defaultPollingInterval = 3;
|
|
8
|
+
const infoRetDelay = 250;
|
|
9
|
+
const defaultTrace = false;
|
|
10
|
+
const autoDiscoverTime = 3000;
|
|
11
|
+
|
|
12
|
+
let Service;
|
|
13
|
+
let Characteristic;
|
|
14
|
+
let Accessory;
|
|
15
|
+
let UUIDGen;
|
|
16
|
+
|
|
17
|
+
var traceOn;
|
|
18
|
+
var debugToInfo;
|
|
19
|
+
var discoverDev;
|
|
20
|
+
var g_log;
|
|
21
|
+
|
|
22
|
+
var cachedAccessories = [];
|
|
23
|
+
var didFinishLaunching = false;
|
|
24
|
+
|
|
25
|
+
/* Variables for telnet polling system */
|
|
26
|
+
var g_powerState = [false,false,false];
|
|
27
|
+
var g_volLevel = [0,0,0];
|
|
28
|
+
var g_muteState = [false,false,false];
|
|
29
|
+
var g_inputID = [null,null,null];
|
|
30
|
+
|
|
31
|
+
module.exports = (homebridge) => {
|
|
32
|
+
Service = homebridge.hap.Service;
|
|
33
|
+
Characteristic = homebridge.hap.Characteristic;
|
|
34
|
+
Accessory = homebridge.platformAccessory;
|
|
35
|
+
UUIDGen = homebridge.hap.uuid;
|
|
36
|
+
|
|
37
|
+
homebridge.registerPlatform(pluginName, platformName, sharpClient, true);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
exports.logDebug = function(string) {
|
|
41
|
+
if (!debugToInfo)
|
|
42
|
+
g_log.debug(string);
|
|
43
|
+
else
|
|
44
|
+
g_log.warn(string);
|
|
45
|
+
}
|
|
46
|
+
function logDebug(string) {
|
|
47
|
+
if (!debugToInfo)
|
|
48
|
+
g_log.debug(string);
|
|
49
|
+
else
|
|
50
|
+
g_log.warn(string);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
class sharpClient {
|
|
54
|
+
constructor(log, config, api) {
|
|
55
|
+
g_log = log;
|
|
56
|
+
this.api = api;
|
|
57
|
+
|
|
58
|
+
/* Stop loading if plugin is not configured */
|
|
59
|
+
if (!config || (!Array.isArray(config.devices) && !Array.isArray(config.volumeControl))) {
|
|
60
|
+
g_log.warn("WARNING: No config settings found for homebridge-sharp-tv plugin.")
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
traceOn = config.debugTrace || defaultTrace;
|
|
65
|
+
|
|
66
|
+
debugToInfo = config.debugToInfo || false;
|
|
67
|
+
|
|
68
|
+
/* Search for all available Denon tvs */
|
|
69
|
+
|
|
70
|
+
api.on('didFinishLaunching', function() {
|
|
71
|
+
logDebug("DidFinishLaunching");
|
|
72
|
+
didFinishLaunching = true;
|
|
73
|
+
|
|
74
|
+
this.configTVs = [];
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
for (let i in config.devices) {
|
|
78
|
+
if (config.devices[i].ip !== undefined && !this.configTVs[config.devices[i].ip])
|
|
79
|
+
this.configTVs[config.devices[i].ip] =
|
|
80
|
+
new tv(this, config, config.devices[i].ip);
|
|
81
|
+
}
|
|
82
|
+
} catch (error) {
|
|
83
|
+
g_log.error(error);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
setTimeout(this.removeCachedAccessory.bind(this), autoDiscoverTime+5000);
|
|
87
|
+
}.bind(this));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
configureAccessory(platformAccessory){
|
|
91
|
+
if (traceOn) {
|
|
92
|
+
logDebug('DEBUG: configureAccessory');
|
|
93
|
+
try {
|
|
94
|
+
logDebug(platformAccessory.displayName);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
g_log.error(error);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
cachedAccessories.push(platformAccessory);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
removeCachedAccessory(){
|
|
104
|
+
if (traceOn) {
|
|
105
|
+
logDebug('DEBUG: removeCachedAccessory');
|
|
106
|
+
try {
|
|
107
|
+
for (let i in cachedAccessories)
|
|
108
|
+
logDebug(cachedAccessories[0].displayName);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
g_log.error(error);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
this.api.unregisterPlatformAccessories(pluginName, platformName, cachedAccessories);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
g_log.error(error);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
class tv {
|
|
123
|
+
constructor(base, config, ip) {
|
|
124
|
+
this.port = 10002;
|
|
125
|
+
this.api = base.api;
|
|
126
|
+
this.ip = ip;
|
|
127
|
+
this.base = base;
|
|
128
|
+
this.queue = [];
|
|
129
|
+
this.last_command=null;
|
|
130
|
+
|
|
131
|
+
this.tvAccessories = [];
|
|
132
|
+
|
|
133
|
+
this.devices = config.devices;
|
|
134
|
+
|
|
135
|
+
this.devicesDuplicates = [];
|
|
136
|
+
|
|
137
|
+
g_log.info('Start TV with IP: ' + this.ip);
|
|
138
|
+
|
|
139
|
+
this.pollingInterval = config.pollInterval || defaultPollingInterval;
|
|
140
|
+
this.pollingInterval = this.pollingInterval * 1000;
|
|
141
|
+
|
|
142
|
+
this.telnetPort = 10002;
|
|
143
|
+
this.devInfoSet = false;
|
|
144
|
+
this.telnet = null;
|
|
145
|
+
|
|
146
|
+
this.manufacturer = 'Sharp';
|
|
147
|
+
this.modelName = pluginName;
|
|
148
|
+
this.serialNumber = 'Aquos';
|
|
149
|
+
this.firmwareRevision = pluginVersion;
|
|
150
|
+
|
|
151
|
+
this.disabletv = false;
|
|
152
|
+
this.pollingTimeout = false;
|
|
153
|
+
|
|
154
|
+
this.poweredOn = [false,false,false];
|
|
155
|
+
this.currentInputID = [null,null,null];
|
|
156
|
+
this.volDisp = [null,null,null];
|
|
157
|
+
this.volumeLevel = [30,30,30];
|
|
158
|
+
this.muteState = [false,false,false];
|
|
159
|
+
|
|
160
|
+
this.startConfiguration();
|
|
161
|
+
//start polling
|
|
162
|
+
setInterval(this.pollForUpdates.bind(this, 1), this.pollingInterval)
|
|
163
|
+
this.pollForUpdates(1); //initial poll to get state on startup
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
getDevInfoSet() {
|
|
167
|
+
return this.devInfoSet;
|
|
168
|
+
}
|
|
169
|
+
setDevInfoSet(set) {
|
|
170
|
+
this.devInfoSet = set;
|
|
171
|
+
}
|
|
172
|
+
returnIP() {
|
|
173
|
+
return this.ip;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/*
|
|
177
|
+
* Try configure the devices. Wait until tv discovery is finished.
|
|
178
|
+
*/
|
|
179
|
+
startConfiguration () {
|
|
180
|
+
|
|
181
|
+
if ( !didFinishLaunching) {
|
|
182
|
+
setTimeout(this.startConfiguration.bind(this), infoRetDelay);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* Configure devices */
|
|
187
|
+
for (let i in this.devices) {
|
|
188
|
+
if (this.devices[i].ip === this.ip) {
|
|
189
|
+
try {
|
|
190
|
+
if (this.devicesDuplicates[this.devices[i].name]) {
|
|
191
|
+
g_log.warn("WARNING: A Device with the name: %s and ip: %s is already added. It will be ignored.", this.devices[i].name, this.devices[i].ip);
|
|
192
|
+
continue;
|
|
193
|
+
} else {
|
|
194
|
+
this.devicesDuplicates[this.devices[i].name] = true;
|
|
195
|
+
this.tvAccessories.push(new tvClient(this, this.devices[i]));
|
|
196
|
+
}
|
|
197
|
+
} catch (error) {
|
|
198
|
+
g_log.error(error);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/*
|
|
206
|
+
* This will start a polling loop that goes on forever and updates
|
|
207
|
+
* the on characteristic periodically.
|
|
208
|
+
*/
|
|
209
|
+
pollForUpdates(zone) {
|
|
210
|
+
// if (traceOn)
|
|
211
|
+
// logDebug('DEBUG: pollForUpdates zone: ' + zone + ': ' + this.ip);
|
|
212
|
+
|
|
213
|
+
/* Make sure that no poll is happening just after switch in input/power */
|
|
214
|
+
if (this.pollingTimeout) {
|
|
215
|
+
this.pollingTimeout = false;
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
var that = this;
|
|
220
|
+
this.send('POWR? ');
|
|
221
|
+
this.send('IAVD? ');
|
|
222
|
+
this.send('RSPW2 '); //ensure TV can be turned on from IP control
|
|
223
|
+
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
/*
|
|
228
|
+
* Used to update the state of all. Disable polling for one poll.
|
|
229
|
+
*/
|
|
230
|
+
updateStates(that, stateInfo, curName) {
|
|
231
|
+
if (curName)
|
|
232
|
+
that.pollingTimeout = true;
|
|
233
|
+
|
|
234
|
+
if (traceOn)
|
|
235
|
+
logDebug(stateInfo);
|
|
236
|
+
|
|
237
|
+
if (stateInfo.power === true || stateInfo.power === false)
|
|
238
|
+
that.poweredOn[stateInfo.zone-1] = stateInfo.power;
|
|
239
|
+
|
|
240
|
+
if (stateInfo.inputID)
|
|
241
|
+
that.currentInputID[stateInfo.zone-1] = stateInfo.inputID;
|
|
242
|
+
|
|
243
|
+
for (let i in that.tvAccessories) {
|
|
244
|
+
if (that.tvAccessories[i].getName() != curName) {
|
|
245
|
+
that.tvAccessories[i].settvState(stateInfo);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/*
|
|
251
|
+
* Setup Telnet connection if no HTML control is possible.
|
|
252
|
+
*/
|
|
253
|
+
connect() {
|
|
254
|
+
this.telnet = new Telnet();
|
|
255
|
+
const params = {
|
|
256
|
+
host: this.ip,
|
|
257
|
+
port: this.port,
|
|
258
|
+
negotiationMandatory: false,
|
|
259
|
+
timeout: 1500,
|
|
260
|
+
ors: '\r',
|
|
261
|
+
irs: '\r',
|
|
262
|
+
shellPrompt: null
|
|
263
|
+
}
|
|
264
|
+
this.telnet.connect(params).then(
|
|
265
|
+
res => { logDebug('Connected to '+this.ip) }).catch(
|
|
266
|
+
error => { logDebug(error) }
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
send(cmd) {
|
|
271
|
+
if (this.telnet == null)
|
|
272
|
+
this.connect();
|
|
273
|
+
this.telnet.send(cmd).then(
|
|
274
|
+
res => { this.responseHandler(cmd,res.slice(0, -1)) }).catch(
|
|
275
|
+
error => {
|
|
276
|
+
logDebug(error);
|
|
277
|
+
this.telnet=null;
|
|
278
|
+
}
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
responseHandler(cmd,res) {
|
|
283
|
+
|
|
284
|
+
logDebug('Received response for ' + cmd + res);
|
|
285
|
+
res=res.split('\r')
|
|
286
|
+
switch (cmd) {
|
|
287
|
+
case 'POWR? ':
|
|
288
|
+
case 'IAVD? ':
|
|
289
|
+
if (res[0] === '1')
|
|
290
|
+
g_powerState[0] = true;
|
|
291
|
+
else if (res[0] === '0')
|
|
292
|
+
g_powerState[0] = false;
|
|
293
|
+
let stateInfo = {
|
|
294
|
+
zone: 1,
|
|
295
|
+
power: g_powerState[0],
|
|
296
|
+
inputID: res[1],
|
|
297
|
+
}
|
|
298
|
+
logDebug(stateInfo);
|
|
299
|
+
if (!this.pollingTimeout)
|
|
300
|
+
this.updateStates(this, stateInfo, null);
|
|
301
|
+
this.telnet.end().then( () => { this.telnet = null; } )
|
|
302
|
+
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
class tvClient {
|
|
309
|
+
constructor(tv, device) {
|
|
310
|
+
this.api = tv.api;
|
|
311
|
+
this.tv = tv;
|
|
312
|
+
|
|
313
|
+
this.manufacturer = tv.manufacturer;
|
|
314
|
+
this.modelName = tv.modelName;
|
|
315
|
+
this.serialNumber = tv.serialNumber;
|
|
316
|
+
this.firmwareRevision = tv.firmwareRevision;
|
|
317
|
+
|
|
318
|
+
// configuration
|
|
319
|
+
this.name = device.name || 'Sharp TV';
|
|
320
|
+
this.ip = device.ip;
|
|
321
|
+
this.inputs = device.inputs;
|
|
322
|
+
this.zone = device.zone || 1;
|
|
323
|
+
this.zone = 1;
|
|
324
|
+
|
|
325
|
+
this.iterator = this.zone - 1;
|
|
326
|
+
this.defaultInputID = device.defaultInputID;
|
|
327
|
+
|
|
328
|
+
/* setup variables */
|
|
329
|
+
this.inputIDSet = false;
|
|
330
|
+
this.inputIDs = new Array();
|
|
331
|
+
|
|
332
|
+
this.setDefaultInputTimeout;
|
|
333
|
+
|
|
334
|
+
/* Delay to wait for retrieve device info */
|
|
335
|
+
this.setupTvService();
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
/*****************************************
|
|
340
|
+
* Start of TV integration service
|
|
341
|
+
****************************************/
|
|
342
|
+
setupTvService() {
|
|
343
|
+
logDebug('setupTvService: ' + this.name);
|
|
344
|
+
|
|
345
|
+
this.tvAccesory = new Accessory(this.name, UUIDGen.generate(this.ip+this.name+"tvService"));
|
|
346
|
+
this.tvAccesory.category = this.api.hap.Categories.TELEVISION;
|
|
347
|
+
|
|
348
|
+
this.tvService = new Service.Television(this.name, 'tvService');
|
|
349
|
+
this.tvService
|
|
350
|
+
.setCharacteristic(Characteristic.ConfiguredName, this.name);
|
|
351
|
+
this.tvService
|
|
352
|
+
.setCharacteristic(Characteristic.SleepDiscoveryMode, Characteristic.SleepDiscoveryMode.ALWAYS_DISCOVERABLE);
|
|
353
|
+
this.tvService
|
|
354
|
+
.getCharacteristic(Characteristic.Active)
|
|
355
|
+
.on('get', this.getPowerState.bind(this))
|
|
356
|
+
.on('set', this.setPowerState.bind(this));
|
|
357
|
+
this.tvService
|
|
358
|
+
.getCharacteristic(Characteristic.ActiveIdentifier)
|
|
359
|
+
.on('set', (inputIdentifier, callback) => {
|
|
360
|
+
this.setAppSwitchState(true, callback, this.inputIDs[inputIdentifier]);
|
|
361
|
+
})
|
|
362
|
+
.on('get', this.getAppSwitchState.bind(this));
|
|
363
|
+
this.tvAccesory
|
|
364
|
+
.getService(Service.AccessoryInformation)
|
|
365
|
+
.setCharacteristic(Characteristic.Manufacturer, this.manufacturer)
|
|
366
|
+
.setCharacteristic(Characteristic.Model, this.modelName)
|
|
367
|
+
.setCharacteristic(Characteristic.SerialNumber, this.serialNumber)
|
|
368
|
+
.setCharacteristic(Characteristic.FirmwareRevision, this.firmwareRevision);
|
|
369
|
+
this.tvService
|
|
370
|
+
.getCharacteristic(Characteristic.RemoteKey)
|
|
371
|
+
.on('set', this.remoteKeyPress.bind(this));
|
|
372
|
+
|
|
373
|
+
this.tvAccesory.addService(this.tvService);
|
|
374
|
+
|
|
375
|
+
this.setupTvSpeakerService();
|
|
376
|
+
this.setupInputSourcesService();
|
|
377
|
+
|
|
378
|
+
logDebug('PublishExternalAccessories: '+ this.name);
|
|
379
|
+
this.api.publishExternalAccessories(pluginName, [this.tvAccesory]);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
setupTvSpeakerService() {
|
|
383
|
+
logDebug('setupTvSpeakerService: ' + this.name);
|
|
384
|
+
this.tvSpeakerService = new Service.TelevisionSpeaker(this.name + ' Volume', 'tvSpeakerService');
|
|
385
|
+
this.tvSpeakerService
|
|
386
|
+
.setCharacteristic(Characteristic.Active,
|
|
387
|
+
Characteristic.Active.ACTIVE)
|
|
388
|
+
.setCharacteristic(Characteristic.VolumeControlType,
|
|
389
|
+
Characteristic.VolumeControlType.RELATIVE);
|
|
390
|
+
this.tvSpeakerService
|
|
391
|
+
.getCharacteristic(Characteristic.VolumeSelector)
|
|
392
|
+
.on('set', this.setVolume.bind(this));
|
|
393
|
+
this.tvSpeakerService
|
|
394
|
+
.getCharacteristic(Characteristic.Mute)
|
|
395
|
+
.on('set', this.setMute.bind(this));
|
|
396
|
+
this.tvAccesory.addService(this.tvSpeakerService);
|
|
397
|
+
this.tvService.addLinkedService(this.tvSpeakerService);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
setupInputSourcesService() {
|
|
401
|
+
logDebug('setupInputSourcesService: ' + this.name);
|
|
402
|
+
if (this.inputs === undefined || this.inputs === null || this.inputs.length <= 0) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (Array.isArray(this.inputs) === false) {
|
|
407
|
+
this.inputs = [this.inputs];
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
let savedNames = {};
|
|
411
|
+
|
|
412
|
+
this.inputs.forEach((value, i) => {
|
|
413
|
+
|
|
414
|
+
// get inputID
|
|
415
|
+
let inputID = null;
|
|
416
|
+
|
|
417
|
+
if (value.inputID !== undefined) {
|
|
418
|
+
inputID = value.inputID;
|
|
419
|
+
} else {
|
|
420
|
+
inputID = value;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// get name
|
|
424
|
+
let inputName = inputID;
|
|
425
|
+
|
|
426
|
+
if (savedNames && savedNames[inputID]) {
|
|
427
|
+
inputName = savedNames[inputID];
|
|
428
|
+
} else if (value.name) {
|
|
429
|
+
inputName = value.name;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// if inputID not null or empty add the input
|
|
433
|
+
if (inputID !== undefined && inputID !== null && inputID !== '') {
|
|
434
|
+
inputID = inputID.replace(/\s/g, ''); // remove all white spaces from the string
|
|
435
|
+
|
|
436
|
+
let tempInput = new Service.InputSource(inputID, 'inputSource' + i);
|
|
437
|
+
tempInput
|
|
438
|
+
.setCharacteristic(Characteristic.Identifier, i)
|
|
439
|
+
.setCharacteristic(Characteristic.ConfiguredName, inputName)
|
|
440
|
+
.setCharacteristic(Characteristic.IsConfigured, Characteristic.IsConfigured.CONFIGURED)
|
|
441
|
+
.setCharacteristic(Characteristic.InputSourceType, Characteristic.InputSourceType.APPLICATION)
|
|
442
|
+
.setCharacteristic(Characteristic.CurrentVisibilityState,
|
|
443
|
+
Characteristic.CurrentVisibilityState.SHOWN);
|
|
444
|
+
|
|
445
|
+
tempInput
|
|
446
|
+
.getCharacteristic(Characteristic.ConfiguredName)
|
|
447
|
+
.on('set', (name, callback) => {
|
|
448
|
+
savedNames[inputID] = name;
|
|
449
|
+
callback()
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
this.tvAccesory.addService(tempInput);
|
|
453
|
+
if (!tempInput.linked)
|
|
454
|
+
this.tvService.addLinkedService(tempInput);
|
|
455
|
+
this.inputIDs.push(inputID);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
/*****************************************
|
|
461
|
+
* End of TV integration service
|
|
462
|
+
****************************************/
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
/*****************************************
|
|
466
|
+
* Start of helper methods
|
|
467
|
+
****************************************/
|
|
468
|
+
updatetvState(tvStatus) {
|
|
469
|
+
if (!tvStatus) {
|
|
470
|
+
if (this.tvService)
|
|
471
|
+
this.tvService
|
|
472
|
+
.getCharacteristic(Characteristic.Active)
|
|
473
|
+
.updateValue(false); //tv service
|
|
474
|
+
} else {
|
|
475
|
+
if (this.tvService)
|
|
476
|
+
this.tvService
|
|
477
|
+
.getCharacteristic(Characteristic.Active)
|
|
478
|
+
.updateValue(true); //tv service
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
settvState(stateInfo) {
|
|
483
|
+
if (this.zone != stateInfo.zone)
|
|
484
|
+
return;
|
|
485
|
+
|
|
486
|
+
if (stateInfo.power === true || stateInfo.power === false)
|
|
487
|
+
this.updatetvState(this.tv.poweredOn[this.iterator]);
|
|
488
|
+
|
|
489
|
+
if (stateInfo.inputID) {
|
|
490
|
+
if (this.tv.poweredOn[this.iterator]) {
|
|
491
|
+
let inputName = stateInfo.inputID;
|
|
492
|
+
for (let i = 0; i < this.inputIDs.length; i++) {
|
|
493
|
+
if (inputName === this.inputIDs[i]) {
|
|
494
|
+
if (this.inputIDSet === false)
|
|
495
|
+
this.tvService
|
|
496
|
+
.getCharacteristic(Characteristic.ActiveIdentifier)
|
|
497
|
+
.updateValue(i);
|
|
498
|
+
else
|
|
499
|
+
this.inputIDSet = false;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
/*****************************************
|
|
506
|
+
* End of helper methods
|
|
507
|
+
****************************************/
|
|
508
|
+
|
|
509
|
+
/*****************************************
|
|
510
|
+
* Start of Homebridge Setters/Getters
|
|
511
|
+
****************************************/
|
|
512
|
+
getPowerState(callback) {
|
|
513
|
+
if (traceOn)
|
|
514
|
+
logDebug('DEBUG: getPowerState: '+ this.name);
|
|
515
|
+
|
|
516
|
+
callback(null, this.tv.poweredOn[this.iterator] ? 1 : 0);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
setPowerState(state, callback) {
|
|
520
|
+
if (traceOn)
|
|
521
|
+
logDebug('DEBUG: setPowerState' + state + ': ' + this.name);
|
|
522
|
+
|
|
523
|
+
if (state === 0)
|
|
524
|
+
state = false;
|
|
525
|
+
else if (state === 1)
|
|
526
|
+
state = true;
|
|
527
|
+
var stateString;
|
|
528
|
+
if (this.zone == 1)
|
|
529
|
+
stateString = 'POWR' + (state ? '1 ' : '0 ');
|
|
530
|
+
this.tv.send(stateString);
|
|
531
|
+
/* Update possible other switches and accessories too */
|
|
532
|
+
let stateInfo = {
|
|
533
|
+
zone: this.zone,
|
|
534
|
+
power: state,
|
|
535
|
+
inputID: null,
|
|
536
|
+
masterVol: null,
|
|
537
|
+
mute: null
|
|
538
|
+
}
|
|
539
|
+
this.tv.updateStates(this.tv, stateInfo, this.name);
|
|
540
|
+
|
|
541
|
+
callback();
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
getAppSwitchState(callback) {
|
|
546
|
+
if (traceOn)
|
|
547
|
+
logDebug('DEBUG: getAppSwitchState: ' + this.name);
|
|
548
|
+
|
|
549
|
+
if (this.tv.poweredOn[this.iterator]) {
|
|
550
|
+
let inputName = this.tv.currentInputID[this.iterator];
|
|
551
|
+
for (let i = 0; i < this.inputIDs.length; i++) {
|
|
552
|
+
if (inputName === this.inputIDs[i]) {
|
|
553
|
+
this.tvService
|
|
554
|
+
.getCharacteristic(Characteristic.ActiveIdentifier)
|
|
555
|
+
.updateValue(i);
|
|
556
|
+
callback(null, i);
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
callback(null, 0);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
setAppSwitchState(state, callback, inputName) {
|
|
565
|
+
if (traceOn)
|
|
566
|
+
logDebug('DEBUG: setAppSwitchState zone: ' + this.name);
|
|
567
|
+
|
|
568
|
+
this.inputIDSet = true;
|
|
569
|
+
|
|
570
|
+
var level = this.defaultVolume[inputName];
|
|
571
|
+
|
|
572
|
+
var inputString;
|
|
573
|
+
let inputNameN = inputName.replace('/', '%2F');
|
|
574
|
+
if (this.zone == 1) {
|
|
575
|
+
inputString = 'IAVD' + inputNameN+' ';
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
var that = this;
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
that.tv.send(inputString);
|
|
583
|
+
|
|
584
|
+
/* Update possible other switches and accessories too */
|
|
585
|
+
let stateInfo = {
|
|
586
|
+
zone: that.zone,
|
|
587
|
+
power: that.tv.poweredOn[that.iterator],
|
|
588
|
+
inputID: inputName
|
|
589
|
+
}
|
|
590
|
+
that.tv.updateStates(that.tv, stateInfo, that.name);
|
|
591
|
+
|
|
592
|
+
callback();
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
setVolume(state, callback) {
|
|
596
|
+
logDebug('DEBUG: setVolume ' + state + ': ' + this.name);
|
|
597
|
+
|
|
598
|
+
var that = this;
|
|
599
|
+
if (this.tv.poweredOn[this.iterator]) {
|
|
600
|
+
var stateString = state ? 'RCKY32 ' : 'RCKY33 '; // UP : DOWN
|
|
601
|
+
that.tv.send(stateString);
|
|
602
|
+
}
|
|
603
|
+
callback();
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
setMute(callback) {
|
|
607
|
+
logDebug('DEBUG: setMute: ' + this.name);
|
|
608
|
+
|
|
609
|
+
var that = this;
|
|
610
|
+
if (this.tv.poweredOn[this.iterator])
|
|
611
|
+
that.tv.send('RCKY31 ');
|
|
612
|
+
callback();
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
remoteKeyPress(remoteKey, callback) {
|
|
616
|
+
var ctrlString = '';
|
|
617
|
+
switch (remoteKey) {
|
|
618
|
+
case Characteristic.RemoteKey.INFORMATION:
|
|
619
|
+
ctrlString = 'RCKY13';
|
|
620
|
+
break;
|
|
621
|
+
case Characteristic.RemoteKey.ARROW_UP:
|
|
622
|
+
ctrlString = 'RCKY41';
|
|
623
|
+
break;
|
|
624
|
+
case Characteristic.RemoteKey.ARROW_DOWN:
|
|
625
|
+
ctrlString = 'RCKY42';
|
|
626
|
+
break;
|
|
627
|
+
case Characteristic.RemoteKey.ARROW_LEFT:
|
|
628
|
+
ctrlString = 'RCKY43';
|
|
629
|
+
break;
|
|
630
|
+
case Characteristic.RemoteKey.ARROW_RIGHT:
|
|
631
|
+
ctrlString = 'RCKY44';
|
|
632
|
+
break;
|
|
633
|
+
case Characteristic.RemoteKey.SELECT:
|
|
634
|
+
ctrlString = 'RCKY40';
|
|
635
|
+
break;
|
|
636
|
+
case Characteristic.RemoteKey.BACK:
|
|
637
|
+
ctrlString = 'RCKY45';
|
|
638
|
+
break;
|
|
639
|
+
case Characteristic.RemoteKey.EXIT:
|
|
640
|
+
ctrlString = 'RCKY46';
|
|
641
|
+
break;
|
|
642
|
+
case Characteristic.RemoteKey.PLAY_PAUSE:
|
|
643
|
+
ctrlString = 'RCKY38';
|
|
644
|
+
break;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
if (ctrlString != '' && this.tv.poweredOn[this.iterator]) {
|
|
648
|
+
this.tv.send(ctrlString+' ');
|
|
649
|
+
}
|
|
650
|
+
callback();
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
getName() {
|
|
655
|
+
return this.name;
|
|
656
|
+
}
|
|
657
|
+
/*****************************************
|
|
658
|
+
* End of Homebridge Setters/Getters
|
|
659
|
+
****************************************/
|
|
660
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "homebridge-sharp-aquos-tv",
|
|
3
|
+
"version": "3.0.1",
|
|
4
|
+
"description": "Sharp Aquos TV control",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/traviswparker/homebridge-sharp-tv.git"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"homebridge",
|
|
15
|
+
"homebridge-plugin",
|
|
16
|
+
"HomeKit",
|
|
17
|
+
"homebridge-sharp-aquos",
|
|
18
|
+
"Sharp",
|
|
19
|
+
"Aquos",
|
|
20
|
+
"TV"
|
|
21
|
+
],
|
|
22
|
+
"author": "Travis Parker, Nicolas Neubauer, Mart van Vliet",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/traviswparker/homebridge-sharp-aquos/issues"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/traviswparker/homebridge-sharp-aquos#readme",
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=9.11.2",
|
|
30
|
+
"homebridge": ">=0.4.53",
|
|
31
|
+
"hap-nodejs": ">=0.4.13"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"telnet-client": ">=1.2.11"
|
|
35
|
+
}
|
|
36
|
+
}
|