node-red-contrib-yandex-station-management 0.3.0 → 0.3.4

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/README.md CHANGED
@@ -3,6 +3,7 @@
3
3
  - Яндекс Станция(протестировано)
4
4
  - Яндекс Станция мини(протестировано)
5
5
  - Яндекс Станция мини 2 с экраном(протестировано)
6
+ - Яндекс Станция лайт(протестировано)
6
7
  - Яндекс Станци Макс(протестировано)
7
8
  - Яндекс Модуль(не протестировано)
8
9
  - Яндекс Модуль - 2 (в процессе тестирования)
@@ -125,6 +126,7 @@ Phrase to say - фраза, которую скажет Алиса вместо
125
126
  -
126
127
  Есть ряд опций:
127
128
  - Volume. Позволяет произносить фразу заданной громкостью. Если не выбрано, то фраза произносится с текущим уровнем громкости. После произнесения уровень громкости вернется в изначальный. Может быть переопределено через msg.volume
129
+ - Whisper. Позволяет произнести фразу шептом.. Переопределяется через msg.whisper
128
130
  - Prevent listening. Если выбрано, то колонка после воспроизведения не "слушает", что ей ответят. Может быть переопределено через msg.prevent_listening
129
131
  - Pause while TTS. Ставит воспроизведение плеера на паузу на время речи. Воспроизведение будет продолжено, только если что-то играло на момент поступления команды. Может быть переопределено через msg.pause_music
130
132
  Все опции комбинируемы между собой.
@@ -235,6 +237,7 @@ Phrase to say - фраза, которую скажет Алиса вместо
235
237
  }
236
238
  ```
237
239
  10. Отправить "Текст" для TTS.
240
+ Больше не работает!
238
241
  ```json
239
242
  {
240
243
  "command" : "sendText",
@@ -303,7 +306,10 @@ Phrase to say - фраза, которую скажет Алиса вместо
303
306
  ```json
304
307
  "value": "<speaker audio='alice-sounds-game-win-1.opus'>У вас получилось!"
305
308
  ```
306
-
309
+ - Говорить шепотом
310
+ ```json
311
+ "value": "<speaker is_whisper="true"'>Я говорю тихо-тихо!"
312
+ ```
307
313
  - Совмещение эффектов
308
314
  ```json
309
315
  "value": "<speaker voice='kostya' audio='alice-sounds-game-win-1.opus' effect='megaphone'>добро пожаловать"
@@ -0,0 +1,52 @@
1
+ 'use strict';
2
+
3
+ class stationHelper {
4
+
5
+ static preparePayload(node, message) {
6
+ let payload = {};
7
+ if (node.output == 'status') {
8
+ payload = { 'payload': message }
9
+ } else if (node.output == 'homekit') {
10
+ let playing = false;
11
+ if (typeof(message.playing) !== 'undefined') {
12
+ playing = message.playing;
13
+ }
14
+ if (node.homekitFormat == 'speaker') {
15
+ let subtitle = 'No Artist';
16
+ let title = 'No Track Name';
17
+
18
+ if (typeof(message.playerState) !== 'undefined') {
19
+ let playerState = message.playerState;
20
+ if (typeof(playerState.subtitle) !== 'undefined') {
21
+ subtitle = playerState.subtitle;
22
+ }
23
+ if (typeof(playerState.title) !== 'undefined') {
24
+ title = playerState.title;
25
+ }
26
+ }
27
+
28
+ let ConfiguredName = `${subtitle} - ${title}`;
29
+ if (ConfiguredName.length > 64) {
30
+ ConfiguredName = title.length <= 64 ? title : title.substr(0, 61) + `...`;
31
+ }
32
+
33
+ payload = {
34
+ 'payload': {
35
+ 'CurrentMediaState': (playing) ? 0 : 1,
36
+ 'ConfiguredName': ConfiguredName
37
+ }
38
+ }
39
+ } else if (node.homekitFormat == 'tv') {
40
+ payload = {
41
+ 'payload': {
42
+ 'Active': (playing) ? 1 : 0
43
+ }
44
+ }
45
+ }
46
+ }
47
+ return payload;
48
+ }
49
+
50
+ }
51
+
52
+ module.exports = stationHelper;
package/nodes/get.js CHANGED
@@ -1,4 +1,6 @@
1
1
  module.exports = function(RED) {
2
+ const stationHelper = require('../lib/stationHelper.js');
3
+
2
4
  function AliceLocalGetNode(config) {
3
5
  RED.nodes.createNode(this,config);
4
6
  let node = this;
@@ -16,38 +18,15 @@ module.exports = function(RED) {
16
18
  node.log(text);
17
19
  }
18
20
  }
19
- function preparePayload(message,inputMsg){
20
- //let payload = {};
21
- if (node.output == 'status') {
22
- inputMsg.payload = message;
23
- } else if (node.output == 'homekit') {
24
- if (node.homekitFormat == 'speaker') {
25
- let ConfiguredName = `${(message.playerState.subtitle) ? message.playerState.subtitle : 'No Artist'} - ${(message.playerState.title) ? message.playerState.title : 'No Track Name'}`;
26
- let title = `${message.playerState.title}`;
27
- if (ConfiguredName.length > 64 && title.length > 0 && title.length <= 64) {
28
- ConfiguredName = title;
29
- } else {
30
- ConfiguredName = title.substr(0, 61) + `...`;
31
- }
32
- (message.playerState)? inputMsg.payload = {
33
- "CurrentMediaState": (message.playing) ? 0 : 1,
34
- "ConfiguredName": ConfiguredName
35
- } :inputMsg.payload = {
36
- "CurrentMediaState": (message.playing) ? 0 : 1,
37
- "ConfiguredName": `No Artist - No Track Name`
38
- }
39
- }else if (node.homekitFormat == 'tv') {
40
- inputMsg.payload = {
41
- "Active": (message.playing) ? 1 : 0
42
- }
43
21
 
44
- }
22
+ function _preparePayload(message, inputMsg) {
23
+ let prepare = stationHelper.preparePayload(node, message);
24
+ if (typeof(prepare.payload) !== 'undefined') {
25
+ inputMsg.payload = prepare.payload;
45
26
  }
46
- return inputMsg;
47
-
27
+ return inputMsg;
48
28
  }
49
29
 
50
-
51
30
  node.onStatus = function(data) {
52
31
  if (data) {
53
32
  node.status({fill: `${data.color}`,shape:"dot",text: `${data.text}`});
@@ -56,7 +35,7 @@ module.exports = function(RED) {
56
35
  }
57
36
  node.onInput = function(msg, send, done){
58
37
  debugMessage('current state: ' + JSON.stringify(node.lastState));
59
- ( 'aliceState' in node.lastState )?node.send(preparePayload(node.lastState,msg)):node.send(msg)
38
+ ( 'aliceState' in node.lastState )?node.send(_preparePayload(node.lastState,msg)):node.send(msg)
60
39
  }
61
40
  node.onMessage = function(message){
62
41
  node.lastState = message;
package/nodes/in.js CHANGED
@@ -1,4 +1,6 @@
1
1
  module.exports = function(RED) {
2
+ const stationHelper = require('../lib/stationHelper.js');
3
+
2
4
  function AliceLocalInNode(config) {
3
5
  RED.nodes.createNode(this,config);
4
6
  let node = this;
@@ -20,37 +22,6 @@ module.exports = function(RED) {
20
22
  }
21
23
  }
22
24
 
23
-
24
- function preparePayload(message){
25
- let payload = {};
26
- if (node.output == 'status') {
27
- payload = {'payload': message}
28
- } else if (node.output == 'homekit') {
29
- if (node.homekitFormat == 'speaker') {
30
- let ConfiguredName = `${(message.playerState.subtitle) ? message.playerState.subtitle : 'No Artist'} - ${(message.playerState.title) ? message.playerState.title : 'No Track Name'}`;
31
- let title = `${message.playerState.title}`;
32
- if (ConfiguredName.length > 64 && title.length > 0 && title.length <= 64) {
33
- ConfiguredName = title;
34
- } else {
35
- ConfiguredName = title.substr(0, 61) + `...`;
36
- }
37
- (message.playerState)? payload = {'payload': {
38
- "CurrentMediaState": (message.playing) ? 0 : 1,
39
- "ConfiguredName": ConfiguredName
40
- } }:payload = {'payload': {
41
- "CurrentMediaState": (message.playing) ? 0 : 1,
42
- "ConfiguredName": `No Artists - No Track Name`
43
- } }
44
- }else if (node.homekitFormat == 'tv') {
45
- payload = {'payload': {
46
- "Active": (message.playing) ? 1 : 0
47
- }
48
- }
49
- }
50
- }
51
- return payload;
52
-
53
- }
54
25
  function sendMessage(message){
55
26
  //debugMessage(JSON.stringify(message));
56
27
  if (node.uniqueFlag && node.output == 'homekit') {
@@ -66,7 +37,7 @@ module.exports = function(RED) {
66
37
  }
67
38
  node.onMessage = function(data){
68
39
  //debugMessage(JSON.stringify(data));
69
- sendMessage(preparePayload(data));
40
+ sendMessage(stationHelper.preparePayload(node, data));
70
41
  }
71
42
  node.onStatus = function(data) {
72
43
  if (data) {
@@ -36,6 +36,9 @@
36
36
  value: false,
37
37
  },
38
38
  noTrack: {},
39
+ whisper: {
40
+ value: false,
41
+ },
39
42
  ttsVoice: {
40
43
  value: null,
41
44
  },
@@ -247,6 +250,13 @@
247
250
  <input type="range" id="node-input-volume" name="volume" min="0" max="100" step="1" style="display: inline-block; width: 150px; margin-left: 10px; vertical-align: middle;">
248
251
  <div id="range-label" class='online'><span id="volume-level" class="online"></span><span class="online">%</span></div>
249
252
  </div>
253
+ <div class="form-row command_options command_options-tts">
254
+ <label for="node-input-whisper" class="label label-long">
255
+ <i class="fa fa-deaf"></i>&nbsp;<span data-i18n="label.whisper"></span>
256
+ <div class="red-ui-debug-msg-type-string" style="font-size: 10px;">msg.whisper</div></label>
257
+ </label>
258
+ <input type="checkbox" id="node-input-whisper" style="display: inline-block; width: auto; vertical-align: top;">
259
+ </div>
250
260
  <div class="form-row command_options command_options-tts">
251
261
  <label for="node-input-stopListening" class="label label-long">
252
262
  <i class="fa fa-deaf"></i>&nbsp;<span data-i18n="label.prevent_listening"></span>
@@ -15,6 +15,7 @@ module.exports = function(RED) {
15
15
  node.pauseMusic = config.pauseMusic;
16
16
  node.ttsVoice = config.ttsVoice;
17
17
  node.ttsEffect = config.ttsEffect;
18
+ node.whisper = config.whisper;
18
19
  node.status({});
19
20
 
20
21
  function debugMessage(text){
@@ -32,6 +33,7 @@ module.exports = function(RED) {
32
33
 
33
34
  //apply node's config
34
35
  if (node.volumeFlag) {data.volume = node.volume/100}
36
+ if (node.whisper) {data.whisper = node.whisper}
35
37
  if (node.stopListening) {data.stopListening = node.stopListening}
36
38
  if (node.noTrackPhrase) {data.noTrackPhrase = node.noTrackPhrase}
37
39
  if (node.pauseMusic) {data.pauseMusic = node.pauseMusic}
@@ -39,6 +41,7 @@ module.exports = function(RED) {
39
41
 
40
42
  //redefine options from input
41
43
  if ("volume" in input) {data.volume = input.volume/100}
44
+ if ("whisper" in input) {data.whisper = input.whisper?true:false}
42
45
  if ("voice" in input) {node.ttsVoice = input.voice}
43
46
  if ("effect" in input) {node.ttsEffect = input.effect}
44
47
  if ("prevent_listening" in input) {node.noTrackPhrase = input.prevent_listening}
@@ -100,6 +103,10 @@ module.exports = function(RED) {
100
103
  data.payload = "<speaker effect='" + effectsArr[ind] + "'>" + data.payload;
101
104
  }
102
105
  }
106
+
107
+ if (data.whisper) {
108
+ data.payload = "<speaker is_whisper='"+data.whisper+"'>" + data.payload;
109
+ }
103
110
  } else {
104
111
  data.payload = ""
105
112
  }
@@ -110,6 +117,7 @@ module.exports = function(RED) {
110
117
  }
111
118
  } else {
112
119
  data.payload = input.payload;
120
+ data.hap = input.hap;
113
121
  node.controller.sendMessage(node.station, node.input, data);
114
122
  debugMessage(`Sending data: station: ${node.station}, input type: ${node.input}, data: ${JSON.stringify(data)}`);
115
123
  }
@@ -6,9 +6,10 @@
6
6
  <p>Воспросизведение голосом отправленных фраз - Text to Speech. Не имеет ограничения по символам.</p>
7
7
  <dl class="message-properties">
8
8
  <dt>Текст</dt><dd>Откуда брать текст: msg.payload, flow, global или Json - выбрать сообщение случайным образом из массива вида <code>["один", "два", "три"]</code>.</dd>
9
- <dt class="optional">Голос</dt><dd>Изменить голос.</dd>
9
+ <dt class="optional">Whisper</dt><dd>Ответить шёпотом.</dd>
10
10
  <dt class="optional">Эффект</dt><dd>Наложить эффект на голос.</dd>
11
11
  <dt class="optional">Громкость</dt><dd>Позволяет произносить фразу заданной громкостью. Если не выбрано, то фраза произносится с текущим уровнем громкости. После произнесения, уровень громкости вернется в изначальный.</dd>
12
+ <dt class="optional">Voice</dt><dd>Say a phrase in a whisper.</dd>
12
13
  <dt class="optional">Не ждать ответ</dt><dd>Если выбрано, то колонка, после воспроизведения, не "слушает", что ей ответят.</dd>
13
14
  <dt class="optional">Плеер на паузу</dt><dd>Ставит воспроизведение плеера на паузу на время речи. Воспроизведение будет продолжено, только если что-то играло на момент поступления команды.</dd>
14
15
  </dl>
@@ -11,6 +11,7 @@
11
11
  "volume": "Volume",
12
12
  "prevent_listening": "Prevent listening",
13
13
  "pause_while_tts": "Pause while TTS",
14
+ "whisper": "Whisper",
14
15
  "default_command": "Default command"
15
16
  },
16
17
  "placeholder": {
@@ -8,6 +8,7 @@
8
8
  <dt class="optional">Голос</dt><dd>Изменить голос.</dd>
9
9
  <dt class="optional">Эффект</dt><dd>Наложить эффект на голос.</dd>
10
10
  <dt class="optional">Громкость</dt><dd>Позволяет произносить фразу заданной громкостью. Если не выбрано, то фраза произносится с текущим уровнем громкости. После произнесения, уровень громкости вернется в изначальный.</dd>
11
+ <dt class="optional">Шёпот</dt><dd>Ответить шёпотом.</dd>
11
12
  <dt class="optional">Не ждать ответ</dt><dd>Если выбрано, то колонка, после воспроизведения, не "слушает", что ей ответят.</dd>
12
13
  <dt class="optional">Плеер на паузу</dt><dd>Ставит воспроизведение плеера на паузу на время речи. Воспроизведение будет продолжено, только если что-то играло на момент поступления команды.</dd>
13
14
  </dl>
@@ -11,6 +11,7 @@
11
11
  "volume": "Громкость",
12
12
  "prevent_listening": "Не ждать ответ",
13
13
  "pause_while_tts": "Плеер на паузу",
14
+ "whisper": "Шёпот",
14
15
  "default_command": "Команда"
15
16
  },
16
17
  "placeholder": {
@@ -18,27 +18,32 @@ module.exports = function(RED) {
18
18
  node.readyList = [];
19
19
  node.activeStationList = [];
20
20
  //node.skipCloudDevices = false;
21
-
22
-
21
+
22
+
23
23
  node.on('stopListening', onStopListening);
24
24
  node.on('startPlay', onStartPlay);
25
25
  node.on('stopPlay', onStopPlay);
26
26
  node.on('setVolume', onSetVolume);
27
27
  node.on('deviceReady', onDeviceReady);
28
28
  node.setMaxListeners(0)
29
-
29
+
30
30
  function debugMessage(text){
31
31
  if (node.debugFlag) {
32
- node.log(text);
32
+ try {
33
+ node.log(text);
34
+ } catch (error) {
35
+ node.log(error)
36
+ }
37
+
33
38
  }
34
39
  }
35
-
40
+
36
41
  let registrationBuffer = [];
37
-
42
+
38
43
  function deviceListProcessing(deviceList) {
39
44
  deviceList.forEach(device => {
40
45
  if (device.address && device.port ) {
41
-
46
+
42
47
  if (node.readyList.find(item => item.id == device.id)){
43
48
  //debugMessage('skipping');
44
49
  } else {
@@ -54,23 +59,23 @@ module.exports = function(RED) {
54
59
 
55
60
  async function getDevices(token)
56
61
  {
57
- let options =
58
- {
62
+ let options =
63
+ {
59
64
  method: 'GET',
60
65
  url: 'https://quasar.yandex.net/glagol/device_list',
61
- headers:
62
- {
66
+ headers:
67
+ {
63
68
  'Content-Type': 'application/json',
64
- 'Authorization': 'Oauth ' + token
65
- }
69
+ 'Authorization': 'Oauth ' + token
70
+ }
66
71
  };
67
- // вариант для снижения частоты запросов на серверы ЯНдекса для обновления списка устройств. Требует тестирования.
72
+ // вариант для снижения частоты запросов на серверы ЯНдекса для обновления списка устройств. Требует тестирования.
68
73
  /* if (node.skipCloudDevices) {
69
74
  discoverDevices(node.deviceList)
70
75
  .then(() => {
71
76
  //debugMessage(`calling processing for ${node.deviceList.length} devices`);
72
77
  deviceListProcessing(node.deviceList)
73
-
78
+
74
79
  });
75
80
  return node.deviceList;
76
81
  } */
@@ -91,21 +96,25 @@ module.exports = function(RED) {
91
96
  if (result != 2 && result != undefined) {
92
97
  registrationBuffer.splice(registrationBuffer.indexOf(bufferStation,1));
93
98
  }
94
-
99
+
95
100
  }
96
101
  node.activeStationList.push({ 'name': device.name, 'id': device.id, 'platform': device.platform, 'address': device.address, 'port': device.port});
97
102
  });
98
103
  //node.emit('refreshHttp', node.activeStationList, node.readyList)
99
104
  deviceListProcessing(node.deviceList)
105
+ try {
106
+ discoverDevices(node.deviceList)
107
+ .then(() => {
108
+ //debugMessage(`calling processing for ${node.deviceList.length} devices`);
109
+ deviceListProcessing(node.deviceList)
110
+
111
+ });
112
+ //debugMessage(node.id);
113
+ return node.deviceList;
114
+ } catch (error) {
115
+ debugMessage(`Error while searching: ${error}`);
116
+ }
100
117
 
101
- discoverDevices(node.deviceList)
102
- .then(() => {
103
- //debugMessage(`calling processing for ${node.deviceList.length} devices`);
104
- deviceListProcessing(node.deviceList)
105
-
106
- });
107
- //debugMessage(node.id);
108
- return node.deviceList;
109
118
  })
110
119
  .catch(function (err) {
111
120
  //node.emit('refreshHttp', node.readyList);
@@ -122,7 +131,6 @@ module.exports = function(RED) {
122
131
  await mDnsSd.discover({
123
132
  name: '_yandexio._tcp.local'
124
133
  }).then((result) => {
125
- debugMessage(`MDNS. Found ${result.length} devices`);
126
134
  node.emit('refreshHttpDNS', result);
127
135
  if (result.length != 0){
128
136
  for (const device of deviceList) {
@@ -143,15 +151,15 @@ module.exports = function(RED) {
143
151
  } catch(e) {
144
152
  debugMessage(`Error searching hostname in mDNS answer`)
145
153
  }
146
-
154
+
147
155
  }
148
156
  }
149
- }
157
+ }
150
158
  })
151
-
159
+
152
160
  }
153
161
  }
154
-
162
+
155
163
  }).catch(function (err) {
156
164
  debugMessage(err);
157
165
  });
@@ -168,15 +176,15 @@ module.exports = function(RED) {
168
176
 
169
177
  async function getLocalToken(device) {
170
178
  let data;
171
- let options = {
179
+ let options = {
172
180
  method: 'GET',
173
181
  url: 'https://quasar.yandex.net/glagol/token',
174
182
  qs: { device_id: device.id, platform: device.platform },
175
- headers:
176
- {
183
+ headers:
184
+ {
177
185
  'Authorization': 'Oauth ' + node.token,
178
- 'Content-Type': 'application/json'
179
- }
186
+ 'Content-Type': 'application/json'
187
+ }
180
188
  };
181
189
  // debugMessage(JSON.stringify(options))
182
190
  statusUpdate({"color": "yellow", "text": "connecting..."}, device);
@@ -184,7 +192,8 @@ module.exports = function(RED) {
184
192
  .then(function(response)
185
193
  {
186
194
  data = JSON.parse(response);
187
- device.token = data.token
195
+ device.token = data.token;
196
+ debugMessage(`${device.id}: Recieved conversation new token`)
188
197
  })
189
198
  .catch(function (err) {
190
199
  removeDevice(node.readyList, device);
@@ -204,12 +213,12 @@ module.exports = function(RED) {
204
213
  connect(device)
205
214
  };
206
215
 
207
-
216
+
208
217
  function connect(device) {
209
218
  //connect only if !device.ws
210
219
  //debugMessage(`device.ws = ${JSON.stringify(device.ws)}`);
211
220
  if ( (device.connection == true || typeof(device.connection) == "undefined") && node.listenerCount(`statusUpdate_${device.id}`) > 0 ) {
212
- debugMessage(`Connecting to device ${device.id}. ws is ${JSON.stringify(device.ws)}. Listeners: ` + node.listenerCount(`statusUpdate_${device.id}`));
221
+ debugMessage(`Connecting to device ${device.id}. ws is ${device.ws}. Listeners: ` + node.listenerCount(`statusUpdate_${device.id}`));
213
222
  if (!device.ws) {
214
223
  debugMessage('recieving conversation token...');
215
224
  getLocalToken(device)
@@ -222,7 +231,7 @@ module.exports = function(RED) {
222
231
  })
223
232
  .catch(function (err) {
224
233
  debugMessage('Error while getting token: ' + err);
225
-
234
+
226
235
  });
227
236
  } else {
228
237
  if (device.ws.readyState == 3) {
@@ -254,16 +263,16 @@ module.exports = function(RED) {
254
263
  //}
255
264
  }
256
265
  } else {
257
- debugMessage(`${device.id} connection is disabled by settings in manager node ${device.manager} or you have not use any node for this station`)
258
- statusUpdate({"color": "red", "text": "disconnected"}, device);
266
+ //debugMessage(`${device.id} connection is disabled by settings in manager node ${device.manager} or you have not use any node for this station`)
267
+ //statusUpdate({"color": "red", "text": "disconnected"}, device);
259
268
  device.timer = setTimeout(connect, 60000, device);
260
269
 
261
270
  }
262
271
 
263
272
  }
264
-
273
+
265
274
  async function makeConn(device) {
266
-
275
+
267
276
  let options = {
268
277
  key: device.glagol.security.server_private_key,
269
278
  cert: device.glagol.security.server_certificate,
@@ -272,7 +281,9 @@ module.exports = function(RED) {
272
281
  device.lastState = {};
273
282
  debugMessage(`Connecting to wss://${device.address}:${device.port}`);
274
283
  device.ws = new WebSocket(`wss://${device.address}:${device.port}`, options);
275
- device.ws.on('open', function open(data) {
284
+ debugMessage(`${device.id}: Fire connection watchdog for 60 seconds`);
285
+ device.watchDogConn = setTimeout(() => {reconnect(device)}, 60000);
286
+ device.ws.on('open', function open(data) {
276
287
  debugMessage(`Connected to ${device.address}, data: ${data}`);
277
288
  sendMessage(device.id, 'command', {payload: 'ping'});
278
289
  statusUpdate({"color": "green", "text": "connected"}, device);
@@ -282,17 +293,22 @@ module.exports = function(RED) {
282
293
  device.waitForIdle = false;
283
294
  device.watchDog = setTimeout(() => device.ws.close(), 10000);
284
295
  device.pingInterval = setInterval(onPing,300,device);
296
+ debugMessage(`${device.id}: Kill connection watchdog`);
297
+ clearTimeout(device.watchDogConn);
285
298
  clearTimeout(device.timer);
299
+ debugMessage(`readyState: ${device.ws.readyState}`)
286
300
  });
287
301
  device.ws.on('message', function incoming(data) {
288
302
  //debugMessage(`${device.id}: ${JSON.stringify(data)}`);
289
303
  let dataRecieved = JSON.parse(data);
290
- device.lastState = dataRecieved.state;
304
+ device.lastState = dataRecieved.state;
305
+ device.fullMessage = JSON.stringify(dataRecieved);
291
306
  //debugMessage(checkSheduler(device, JSON.parse(data).sentTime));
292
307
  node.emit(`message_${device.id}`, device.lastState);
293
308
  if (device.lastState.aliceState == 'LISTENING' && device.waitForListening) {node.emit(`stopListening`, device)}
294
309
  if (device.lastState.aliceState == 'LISTENING' && device.playAfterTTS) {node.emit('startPlay', device)}
295
310
  if (device.lastState.aliceState == 'LISTENING' && device.waitForIdle) {node.emit('setVolume', device)}
311
+ //if (device.lastState.aliceState != 'SPEAKING' && device.ttsBuffer.length > 0) {node.emit('nextTts', device)}
296
312
  // if (device.parameters.hasOwnProperty(sheduler)) {
297
313
  // let resultSheduler = checkSheduler(device, dataRecieved.sentTime)
298
314
  // device.canPlay = resultSheduler[0]
@@ -305,7 +321,7 @@ module.exports = function(RED) {
305
321
  if (device.lastState.playing && device.lastState.aliceState != 'LISTENING' && device.parameters.hasOwnProperty("sheduler")) {
306
322
  let res = checkSheduler(device, dataRecieved.sentTime)
307
323
  //debugMessage(`Result of cheking sheduler is ${res.toString}`);
308
- if (!res[0]) {
324
+ if (!res[0]) {
309
325
  if (device.shedulerFlag || device.shedulerFlag == undefined) {
310
326
  node.emit('stopPlay', device, res[1])
311
327
  device.shedulerFlag = false
@@ -317,10 +333,11 @@ module.exports = function(RED) {
317
333
  clearTimeout(device.watchDog);
318
334
  //debugMessage(`cleared timeout for ${device.id}`)
319
335
  device.watchDog = setTimeout(() => {device.ws.close()}, 10000);
320
- });
336
+ });
321
337
  //device.ws.on('ping', function);
322
338
  device.ws.on('close', function close(code, reason){
323
339
  statusUpdate({"color": "red", "text": "disconnected"}, device);
340
+ //debugMessage(`readyState: ${device.ws.readyState}`)
324
341
  device.lastState = {};
325
342
  clearTimeout(device.watchDog);
326
343
  switch(code) {
@@ -328,15 +345,15 @@ module.exports = function(RED) {
328
345
  debugMessage(`getting new token...`);
329
346
  connect(device);
330
347
  break;
331
- case 1000:
348
+ case 1000:
332
349
  debugMessage(`Closed connection code ${code} with reason ${reason}. Reconnecting...` );
333
350
  connect(device);
334
- break;
351
+ break;
335
352
  case 1006:
336
353
  debugMessage(`Lost server, reconnect in 60 seconds...${code} + ${reason}` );
337
354
  device.timer = setTimeout(connect, 60000, device);
338
355
  break;
339
-
356
+
340
357
  case 10000:
341
358
  debugMessage(`Reconnect device reason 10000 ${device.id}`);
342
359
  connect(device);
@@ -348,7 +365,7 @@ module.exports = function(RED) {
348
365
  }
349
366
 
350
367
 
351
- })
368
+ })
352
369
  device.ws.on('error', function error(data){
353
370
  //statusUpdate({"color": "red", "text": "disconnected"}, device);
354
371
  debugMessage(`error: ${data}`);
@@ -409,7 +426,7 @@ module.exports = function(RED) {
409
426
  return [{
410
427
  "command": "rewind",
411
428
  "position": 0
412
- }]
429
+ }]
413
430
  }
414
431
  } else if (message.payload == 'volumeup') {
415
432
  debugMessage(currentVolume);
@@ -417,7 +434,7 @@ module.exports = function(RED) {
417
434
  return [{
418
435
  "command": "setVolume",
419
436
  "volume": currentVolume + 0.1
420
- }]
437
+ }]
421
438
  }
422
439
  } else if (message.payload == 'volumedown') {
423
440
  debugMessage(currentVolume);
@@ -425,13 +442,13 @@ module.exports = function(RED) {
425
442
  return [{
426
443
  "command": "setVolume",
427
444
  "volume": currentVolume - 0.1
428
- }]
445
+ }]
429
446
  }
430
447
  } else if (message.payload == 'volume') {
431
448
  return [{
432
449
  "command": "setVolume",
433
450
  "volume": parseFloat(message.level)
434
- }]
451
+ }]
435
452
  }
436
453
 
437
454
  } else {
@@ -439,7 +456,7 @@ module.exports = function(RED) {
439
456
  //node.error(`You can send commands in msg.payload from list as String ${commands + extraCommands}`);
440
457
  return [{"command": "ping"}];
441
458
  }
442
- case 'voice':
459
+ case 'voice':
443
460
  debugMessage(`Message Voice command: ${message}`);
444
461
  return [{
445
462
  "command" : "sendText",
@@ -507,55 +524,84 @@ module.exports = function(RED) {
507
524
  return result;
508
525
  break;
509
526
  case 'homekit':
510
- debugMessage('HAP: ' + JSON.stringify(message.hap.context) + ' PL: ' + JSON.stringify(message.payload) );
511
- if (message.hap.context != undefined) {
512
- switch(JSON.stringify(message.payload)){
513
- case '{"TargetMediaState":1}':
514
- case '{"Active":0}':
515
- return messageConstructor('command', {'payload': 'stop'})
516
- case '{"TargetMediaState":0}':
517
- case '{"Active":1}':
518
- if (!device.lastState.playerState && !device.playAfterTTS && message.noTrackPhrase) {
519
- return messageConstructor('voice', {'payload': message.noTrackPhrase})
520
- } else if (device.lastState.playerState.title != "" && !device.playAfterTTS){
521
- return messageConstructor('command', {'payload': 'play'})
522
- } else if (message.noTrackPhrase && !device.playAfterTTS) {
523
- return messageConstructor('voice', {'payload': message.noTrackPhrase})
524
- } else {
525
- return messageConstructor('command', {'payload': 'ping'})
527
+ debugMessage('HAP: ' + JSON.stringify(message) + ' PL: ' + JSON.stringify(message.payload) );
528
+ if ("session" in message.hap) {
529
+ let playing = false;
530
+ let id = null;
531
+ let noTrackPhrase = message.noTrackPhrase;
532
+
533
+ if (typeof (device.lastState) !== 'undefined') {
534
+ let lastState = device.lastState;
535
+ if (typeof (lastState.playing) !== 'undefined') {
536
+ playing = lastState.playing;
537
+ }
538
+ if (typeof (lastState.playerState) !== 'undefined') {
539
+ if (typeof (lastState.playerState.id) !== 'undefined') {
540
+ id = lastState.playerState.id;
526
541
  }
527
- case '{"RemoteKey":7}':
528
- return messageConstructor('command', {'payload': 'forward'}, device)
529
- case '{"RemoteKey":6}':
530
- return messageConstructor('command', {'payload': 'backward'}, device)
531
- case '{"RemoteKey":4}':
532
- return messageConstructor('command', {'payload': 'next'})
533
- case '{"RemoteKey":5}':
534
- return messageConstructor('command', {'payload': 'prev'})
535
- case '{"RemoteKey":11}':
536
- if (typeof device.lastState.playing !== "undefined") {
537
- if (device.lastState.playing){
538
- return messageConstructor('command', {'payload': 'stop'})
542
+ }
543
+ }
544
+
545
+ // speaker
546
+ if ("TargetMediaState" in message.payload) {
547
+ let TargetMediaState = message.payload.TargetMediaState;
548
+ if (id) {
549
+ return messageConstructor('command', { 'payload': (TargetMediaState ? 'stop' : 'play') })
550
+ } else if (!id && !TargetMediaState && noTrackPhrase) {
551
+ return messageConstructor('voice', { 'payload': noTrackPhrase })
552
+ }
553
+ }
554
+
555
+ // tv
556
+ if ("Active" in message.payload) {
557
+ let Active = message.payload.Active;
558
+ if (id) {
559
+ return messageConstructor('command', { 'payload': (Active ? 'play' : 'stop') })
560
+ } else if (!id && Active && noTrackPhrase) {
561
+ return messageConstructor('voice', { 'payload': noTrackPhrase })
562
+ }
563
+ }
564
+
565
+ // tv + RemoteKey
566
+ if ("RemoteKey" in message.payload) {
567
+ let RemoteKey = message.payload.RemoteKey;
568
+ switch (RemoteKey) {
569
+ case '7':
570
+ return messageConstructor('command', { 'payload': 'forward' }, device)
571
+ case '6':
572
+ return messageConstructor('command', { 'payload': 'backward' }, device)
573
+ case '4':
574
+ return messageConstructor('command', { 'payload': 'next' })
575
+ case '5':
576
+ return messageConstructor('command', { 'payload': 'prev' })
577
+ case '11':
578
+ if (playing) {
579
+ return messageConstructor('command', { 'payload': 'stop' })
539
580
  } else {
540
- return messageConstructor('command', {'payload': 'play'})
581
+ if (id) {
582
+ return messageConstructor('command', { 'payload': 'play' })
583
+ } else if (!id && noTrackPhrase) {
584
+ return messageConstructor('voice', { 'payload': noTrackPhrase })
585
+ }
541
586
  }
542
- }
543
- case '{"VolumeSelector":1}':
544
- return messageConstructor('command', {'payload': 'volumedown'}, device)
545
- case '{"VolumeSelector":0}':
546
- return messageConstructor('command', {'payload': 'volumeup'}, device)
547
- default:
548
- debugMessage('unknown command')
549
- return messageConstructor('command', {'payload': 'ping'})
587
+ }
588
+ }
589
+
590
+ // tv + VolumeSelector
591
+ if ("VolumeSelector" in message.payload) {
592
+ let VolumeSelector = message.payload.VolumeSelector;
593
+ return messageConstructor('command', { 'payload': (VolumeSelector ? 'volumedown' : 'volumeup') }, device)
550
594
  }
551
-
595
+
596
+ debugMessage('unknown command')
597
+ return messageConstructor('command', { 'payload': 'ping' })
552
598
  } else {
553
- return messageConstructor('command', {'payload': 'ping'})
599
+ return messageConstructor('command', { 'payload': 'ping' })
554
600
  }
555
- case 'raw':
601
+ case 'raw':
556
602
  if (Array.isArray(message.payload)) { return message.payload }
557
603
  return [message.payload];
558
- case 'stopListening':
604
+ case 'stopListening':
559
605
  return [{
560
606
  "command": "serverAction",
561
607
  "serverActionEventPayload": {
@@ -567,7 +613,7 @@ module.exports = function(RED) {
567
613
 
568
614
  }
569
615
  function sendMessage(deviceId, messageType, message) {
570
-
616
+
571
617
  try {
572
618
  let device = searchDeviceByID(deviceId);
573
619
  //debugMessage(`deviceId: ${searchDeviceByID(deviceId)}`);
@@ -608,19 +654,19 @@ module.exports = function(RED) {
608
654
  //debugMessage(`timeCur: ${timeCurrent} timeMin: ${timeMin} timeMax: ${timeMax}`);
609
655
  return [false, daySheduler.phrase];
610
656
  }
611
-
657
+
612
658
 
613
659
  }
614
-
660
+
615
661
  function searchDeviceByID(id) {
616
662
  if (node.deviceList) {
617
663
  return node.deviceList.find(device => device.id == id)
618
- }
664
+ }
619
665
  }
620
666
  function onPing(device) {
621
667
  if (device) {sendMessage(device.id, 'command', {payload: 'ping'});}
622
668
  }
623
-
669
+
624
670
  function onPing(device) {
625
671
  sendMessage(device.id, 'command', {payload: 'ping'});
626
672
  }
@@ -629,19 +675,19 @@ module.exports = function(RED) {
629
675
  if (device) {
630
676
  if (device.ws) {
631
677
  switch(device.ws.readyState){
632
- case 0:
678
+ case 0:
633
679
  return {"color": "yellow", "text": "connecting..."}
634
- case 1:
680
+ case 1:
635
681
  return {"color": "green", "text": "connected"}
636
- case 2:
682
+ case 2:
637
683
  return {"color": "red", "text": "disconnecting"}
638
- case 3:
684
+ case 3:
639
685
  return {"color": "red", "text": "disconnected"}
640
686
  default:
641
687
  return {"color": "red", "text": "disconnected"}
642
688
  }
643
-
644
- }
689
+
690
+ }
645
691
  } else {
646
692
  return {"color": "red", "text": "disconnected"}
647
693
  }
@@ -649,7 +695,7 @@ module.exports = function(RED) {
649
695
  }
650
696
 
651
697
 
652
-
698
+
653
699
  function registerDevice(deviceId, nodeId, parameters) {
654
700
  let device = searchDeviceByID(deviceId);
655
701
  debugMessage(`Recieved parameters ${JSON.stringify(parameters)} for station id ${deviceId}`);
@@ -661,7 +707,7 @@ module.exports = function(RED) {
661
707
  debugMessage(`Device ${device.id} already registered with manager id ${device.manager}. Updating parameters and restart...`);
662
708
  reconnect(device);
663
709
  return 1;
664
-
710
+
665
711
  }
666
712
  //новый и первый запрос на регистрацию для устройства
667
713
  if (typeof(device.manager) == 'undefined') {
@@ -690,7 +736,7 @@ module.exports = function(RED) {
690
736
  registrationBuffer.splice(registrationBuffer.indexOf(currentBuffer), 1)
691
737
  debugMessage(`Element from registration buffer was deleted. Current buffer size is ${registrationBuffer.length}`)
692
738
  }
693
- reconnect(device);
739
+ reconnect(device);
694
740
  return 0;
695
741
  }
696
742
  //новый запрос на регистрацию при наличии уже зарегистрированной ноды
@@ -708,9 +754,9 @@ module.exports = function(RED) {
708
754
  registrationBuffer.push({"id": deviceId, "manager": nodeId, "parameters": parameters});
709
755
  debugMessage(`New element in registration buffer. Current buffer size is ${registrationBuffer.length}`)
710
756
  }
711
-
757
+
712
758
  }
713
-
759
+
714
760
 
715
761
  }
716
762
  function unregisterDevice(deviceId, nodeId){
@@ -721,7 +767,7 @@ module.exports = function(RED) {
721
767
  device.parameters = {};
722
768
  debugMessage(`For device ${deviceId} was succesfully unregistred managment node whith id ${device.manager}`);
723
769
  debugMessage(`device is: ${device}`);
724
- return 0;
770
+ return 0;
725
771
  } else {
726
772
  return 2;
727
773
  }
@@ -742,7 +788,7 @@ module.exports = function(RED) {
742
788
  if (device.savedVolumeLevel) { sendMessage(device.id, 'raw',{payload: {
743
789
  "command": "setVolume",
744
790
  "volume": parseFloat(device.savedVolumeLevel)
745
- }
791
+ }
746
792
  });
747
793
  }
748
794
  device.waitForIdle = false;
@@ -751,13 +797,13 @@ module.exports = function(RED) {
751
797
  function onStopPlay(device, phrase) {
752
798
  sendMessage(device.id, 'command', {payload: 'stop'});
753
799
  if (phrase.length > 0 && device.lastState.aliceState != 'SPEAKING') {sendMessage(device.id, 'tts', {payload: phrase, stopListening: true});}
754
- }
800
+ }
755
801
  function onClose() {
756
802
  clearInterval(node.interval);
757
803
  node.deviceList = [];
758
804
  node.removeListener('deviceReady', onDeviceReady)
759
805
  }
760
-
806
+
761
807
  node.on('refreshHttp', function(activeList, readyList) {
762
808
  RED.httpAdmin.get("/yandexdevices_"+node.id, RED.auth.needsPermission('yandex-login.read'), function(req,res) {
763
809
  res.json({"devices": readyList});
@@ -766,8 +812,8 @@ module.exports = function(RED) {
766
812
  res.json({"devices": activeList});
767
813
  });
768
814
  });
769
-
770
- node.on('refreshHttpDNS', function(dnsList) {
815
+
816
+ node.on('refreshHttpDNS', function(dnsList) {
771
817
  RED.httpAdmin.get("/mdns/"+node.id, RED.auth.needsPermission('yandex-login.read'), function(req,res) {
772
818
  res.json({"SearchResult": dnsList});
773
819
  });
@@ -786,12 +832,12 @@ module.exports = function(RED) {
786
832
  let device = searchDeviceByID(id);
787
833
  if (device) {
788
834
 
789
- res.json({"id": device.id,"name": device.name, "platform": device.platform, "address": device.address, "port": device.port, "manager": device.manager, "ws": device.ws, "parameters": device.parameters});
835
+ res.json({"id": device.id,"name": device.name, "platform": device.platform, "address": device.address, "port": device.port, "manager": device.manager, "ws": device.ws, "parameters": device.parameters, "fullMessage": device.fullMessage });
790
836
  } else {
791
837
  res.json({"error": 'no device found'});
792
838
  }
793
839
  });
794
-
840
+
795
841
  // main init
796
842
  if (typeof(node.token) !== 'undefined') {
797
843
  debugMessage(`Starting server with id ${node.id}`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-yandex-station-management",
3
- "version": "0.3.0",
3
+ "version": "0.3.4",
4
4
  "description": "Local management of YandexStation using API on websockets",
5
5
  "main": "index.js",
6
6
  "scripts": {