node-red-contrib-yandex-station-management 0.3.2 → 0.3.5

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 (в процессе тестирования)
@@ -40,7 +41,7 @@
40
41
 
41
42
  После деплоя в настройках ноды в поле Station должны появиться станции доступные для управления.
42
43
 
43
- npmЕсли станция не появилась в списке, то можно подождать пару минут или перезапустить Node-Red.
44
+ Если станция не появилась в списке, то можно подождать пару минут или перезапустить Node-Red.
44
45
 
45
46
  ## Описание возможностей и сценариев использования.
46
47
  ### Нода Station
@@ -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,43 +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
- try {
25
- if (node.homekitFormat == 'speaker') {
26
- let ConfiguredName = `${(message.playerState.subtitle) ? message.playerState.subtitle : 'No Artist'} - ${(message.playerState.title) ? message.playerState.title : 'No Track Name'}`;
27
- let title = `${message.playerState.title}`;
28
- if (ConfiguredName.length > 64 && title.length > 0 && title.length <= 64) {
29
- ConfiguredName = title;
30
- } else {
31
- ConfiguredName = title.substr(0, 61) + `...`;
32
- }
33
- (message.playerState)? inputMsg.payload = {
34
- "CurrentMediaState": (message.playing) ? 0 : 1,
35
- "ConfiguredName": ConfiguredName
36
- } :inputMsg.payload = {
37
- "CurrentMediaState": (message.playing) ? 0 : 1,
38
- "ConfiguredName": `No Artist - No Track Name`
39
- }
40
- }else if (node.homekitFormat == 'tv') {
41
- inputMsg.payload = {
42
- "Active": (message.playing) ? 1 : 0
43
- }
44
-
45
- }
46
- } catch (error) {
47
- debugMessage(`Error while preparing payload: `+ e);
48
- }
49
21
 
22
+ function _preparePayload(message, inputMsg) {
23
+ let prepare = stationHelper.preparePayload(node, message);
24
+ if (typeof(prepare.payload) !== 'undefined') {
25
+ inputMsg.payload = prepare.payload;
50
26
  }
51
- return inputMsg;
52
-
27
+ return inputMsg;
53
28
  }
54
29
 
55
-
56
30
  node.onStatus = function(data) {
57
31
  if (data) {
58
32
  node.status({fill: `${data.color}`,shape:"dot",text: `${data.text}`});
@@ -61,7 +35,7 @@ module.exports = function(RED) {
61
35
  }
62
36
  node.onInput = function(msg, send, done){
63
37
  debugMessage('current state: ' + JSON.stringify(node.lastState));
64
- ( '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)
65
39
  }
66
40
  node.onMessage = function(message){
67
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,42 +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
- try {
30
- if (node.homekitFormat == 'speaker') {
31
- let ConfiguredName = `${(message.playerState.subtitle) ? message.playerState.subtitle : 'No Artist'} - ${(message.playerState.title) ? message.playerState.title : 'No Track Name'}`;
32
- let title = `${message.playerState.title}`;
33
- if (ConfiguredName.length > 64 && title.length > 0 && title.length <= 64) {
34
- ConfiguredName = title;
35
- } else {
36
- ConfiguredName = title.substr(0, 61) + `...`;
37
- }
38
- (message.playerState)? payload = {'payload': {
39
- "CurrentMediaState": (message.playing) ? 0 : 1,
40
- "ConfiguredName": ConfiguredName
41
- } }:payload = {'payload': {
42
- "CurrentMediaState": (message.playing) ? 0 : 1,
43
- "ConfiguredName": `No Artists - No Track Name`
44
- } }
45
- }else if (node.homekitFormat == 'tv') {
46
- payload = {'payload': {
47
- "Active": (message.playing) ? 1 : 0
48
- }
49
- }
50
- }
51
- } catch(e) {
52
- debugMessage(`Error while preparing payload: `+ e)
53
- }
54
-
55
- }
56
- return payload;
57
-
58
- }
59
25
  function sendMessage(message){
60
26
  //debugMessage(JSON.stringify(message));
61
27
  if (node.uniqueFlag && node.output == 'homekit') {
@@ -71,7 +37,7 @@ module.exports = function(RED) {
71
37
  }
72
38
  node.onMessage = function(data){
73
39
  //debugMessage(JSON.stringify(data));
74
- sendMessage(preparePayload(data));
40
+ sendMessage(stationHelper.preparePayload(node, data));
75
41
  }
76
42
  node.onStatus = function(data) {
77
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
  }
@@ -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,19 +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);
286
299
  debugMessage(`readyState: ${device.ws.readyState}`)
287
300
  });
288
301
  device.ws.on('message', function incoming(data) {
289
302
  //debugMessage(`${device.id}: ${JSON.stringify(data)}`);
290
303
  let dataRecieved = JSON.parse(data);
291
- device.lastState = dataRecieved.state;
304
+ device.lastState = dataRecieved.state;
292
305
  device.fullMessage = JSON.stringify(dataRecieved);
293
306
  //debugMessage(checkSheduler(device, JSON.parse(data).sentTime));
294
307
  node.emit(`message_${device.id}`, device.lastState);
295
308
  if (device.lastState.aliceState == 'LISTENING' && device.waitForListening) {node.emit(`stopListening`, device)}
296
309
  if (device.lastState.aliceState == 'LISTENING' && device.playAfterTTS) {node.emit('startPlay', device)}
297
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)}
298
312
  // if (device.parameters.hasOwnProperty(sheduler)) {
299
313
  // let resultSheduler = checkSheduler(device, dataRecieved.sentTime)
300
314
  // device.canPlay = resultSheduler[0]
@@ -307,7 +321,7 @@ module.exports = function(RED) {
307
321
  if (device.lastState.playing && device.lastState.aliceState != 'LISTENING' && device.parameters.hasOwnProperty("sheduler")) {
308
322
  let res = checkSheduler(device, dataRecieved.sentTime)
309
323
  //debugMessage(`Result of cheking sheduler is ${res.toString}`);
310
- if (!res[0]) {
324
+ if (!res[0]) {
311
325
  if (device.shedulerFlag || device.shedulerFlag == undefined) {
312
326
  node.emit('stopPlay', device, res[1])
313
327
  device.shedulerFlag = false
@@ -319,7 +333,7 @@ module.exports = function(RED) {
319
333
  clearTimeout(device.watchDog);
320
334
  //debugMessage(`cleared timeout for ${device.id}`)
321
335
  device.watchDog = setTimeout(() => {device.ws.close()}, 10000);
322
- });
336
+ });
323
337
  //device.ws.on('ping', function);
324
338
  device.ws.on('close', function close(code, reason){
325
339
  statusUpdate({"color": "red", "text": "disconnected"}, device);
@@ -331,15 +345,15 @@ module.exports = function(RED) {
331
345
  debugMessage(`getting new token...`);
332
346
  connect(device);
333
347
  break;
334
- case 1000:
348
+ case 1000:
335
349
  debugMessage(`Closed connection code ${code} with reason ${reason}. Reconnecting...` );
336
350
  connect(device);
337
- break;
351
+ break;
338
352
  case 1006:
339
353
  debugMessage(`Lost server, reconnect in 60 seconds...${code} + ${reason}` );
340
354
  device.timer = setTimeout(connect, 60000, device);
341
355
  break;
342
-
356
+
343
357
  case 10000:
344
358
  debugMessage(`Reconnect device reason 10000 ${device.id}`);
345
359
  connect(device);
@@ -351,15 +365,13 @@ module.exports = function(RED) {
351
365
  }
352
366
 
353
367
 
354
- })
368
+ })
355
369
  device.ws.on('error', function error(data){
356
370
  //statusUpdate({"color": "red", "text": "disconnected"}, device);
357
371
  debugMessage(`error: ${data}`);
358
- device.ws.terminate();
359
- // if (device.localConnectionFlag) {
360
- // debugMessage(`Reconnecting in 60 seconds...` );
361
- // setTimeout(connect, 60000, device);
362
- // }
372
+ if (typeof(device) !== 'undefined' && typeof(device.ws) !== 'undefined' ) {
373
+ device.ws.terminate();
374
+ }
363
375
  });
364
376
  };
365
377
 
@@ -412,7 +424,7 @@ module.exports = function(RED) {
412
424
  return [{
413
425
  "command": "rewind",
414
426
  "position": 0
415
- }]
427
+ }]
416
428
  }
417
429
  } else if (message.payload == 'volumeup') {
418
430
  debugMessage(currentVolume);
@@ -420,7 +432,7 @@ module.exports = function(RED) {
420
432
  return [{
421
433
  "command": "setVolume",
422
434
  "volume": currentVolume + 0.1
423
- }]
435
+ }]
424
436
  }
425
437
  } else if (message.payload == 'volumedown') {
426
438
  debugMessage(currentVolume);
@@ -428,13 +440,13 @@ module.exports = function(RED) {
428
440
  return [{
429
441
  "command": "setVolume",
430
442
  "volume": currentVolume - 0.1
431
- }]
443
+ }]
432
444
  }
433
445
  } else if (message.payload == 'volume') {
434
446
  return [{
435
447
  "command": "setVolume",
436
448
  "volume": parseFloat(message.level)
437
- }]
449
+ }]
438
450
  }
439
451
 
440
452
  } else {
@@ -442,7 +454,7 @@ module.exports = function(RED) {
442
454
  //node.error(`You can send commands in msg.payload from list as String ${commands + extraCommands}`);
443
455
  return [{"command": "ping"}];
444
456
  }
445
- case 'voice':
457
+ case 'voice':
446
458
  debugMessage(`Message Voice command: ${message}`);
447
459
  return [{
448
460
  "command" : "sendText",
@@ -510,55 +522,84 @@ module.exports = function(RED) {
510
522
  return result;
511
523
  break;
512
524
  case 'homekit':
513
- debugMessage('HAP: ' + JSON.stringify(message) + ' PL: ' + JSON.stringify(message.payload) );
525
+ debugMessage('HAP: ' + JSON.stringify(message) + ' PL: ' + JSON.stringify(message.payload) );
514
526
  if ("session" in message.hap) {
515
- switch(JSON.stringify(message.payload)){
516
- case '{"TargetMediaState":1}':
517
- case '{"Active":0}':
518
- return messageConstructor('command', {'payload': 'stop'})
519
- case '{"TargetMediaState":0}':
520
- case '{"Active":1}':
521
- if (!device.lastState.playerState && !device.playAfterTTS && message.noTrackPhrase) {
522
- return messageConstructor('voice', {'payload': message.noTrackPhrase})
523
- } else if (device.lastState.playerState.title != "" && !device.playAfterTTS){
524
- return messageConstructor('command', {'payload': 'play'})
525
- } else if (message.noTrackPhrase && !device.playAfterTTS) {
526
- return messageConstructor('voice', {'payload': message.noTrackPhrase})
527
- } else {
528
- return messageConstructor('command', {'payload': 'ping'})
527
+ let playing = false;
528
+ let id = null;
529
+ let noTrackPhrase = message.noTrackPhrase;
530
+
531
+ if (typeof (device.lastState) !== 'undefined') {
532
+ let lastState = device.lastState;
533
+ if (typeof (lastState.playing) !== 'undefined') {
534
+ playing = lastState.playing;
535
+ }
536
+ if (typeof (lastState.playerState) !== 'undefined') {
537
+ if (typeof (lastState.playerState.id) !== 'undefined') {
538
+ id = lastState.playerState.id;
529
539
  }
530
- case '{"RemoteKey":7}':
531
- return messageConstructor('command', {'payload': 'forward'}, device)
532
- case '{"RemoteKey":6}':
533
- return messageConstructor('command', {'payload': 'backward'}, device)
534
- case '{"RemoteKey":4}':
535
- return messageConstructor('command', {'payload': 'next'})
536
- case '{"RemoteKey":5}':
537
- return messageConstructor('command', {'payload': 'prev'})
538
- case '{"RemoteKey":11}':
539
- if (typeof device.lastState.playing !== "undefined") {
540
- if (device.lastState.playing){
541
- return messageConstructor('command', {'payload': 'stop'})
540
+ }
541
+ }
542
+
543
+ // speaker
544
+ if ("TargetMediaState" in message.payload) {
545
+ let TargetMediaState = message.payload.TargetMediaState;
546
+ if (id) {
547
+ return messageConstructor('command', { 'payload': (TargetMediaState ? 'stop' : 'play') })
548
+ } else if (!id && !TargetMediaState && noTrackPhrase) {
549
+ return messageConstructor('voice', { 'payload': noTrackPhrase })
550
+ }
551
+ }
552
+
553
+ // tv
554
+ if ("Active" in message.payload) {
555
+ let Active = message.payload.Active;
556
+ if (id) {
557
+ return messageConstructor('command', { 'payload': (Active ? 'play' : 'stop') })
558
+ } else if (!id && Active && noTrackPhrase) {
559
+ return messageConstructor('voice', { 'payload': noTrackPhrase })
560
+ }
561
+ }
562
+
563
+ // tv + RemoteKey
564
+ if ("RemoteKey" in message.payload) {
565
+ let RemoteKey = message.payload.RemoteKey;
566
+ switch (RemoteKey) {
567
+ case '7':
568
+ return messageConstructor('command', { 'payload': 'forward' }, device)
569
+ case '6':
570
+ return messageConstructor('command', { 'payload': 'backward' }, device)
571
+ case '4':
572
+ return messageConstructor('command', { 'payload': 'next' })
573
+ case '5':
574
+ return messageConstructor('command', { 'payload': 'prev' })
575
+ case '11':
576
+ if (playing) {
577
+ return messageConstructor('command', { 'payload': 'stop' })
542
578
  } else {
543
- return messageConstructor('command', {'payload': 'play'})
579
+ if (id) {
580
+ return messageConstructor('command', { 'payload': 'play' })
581
+ } else if (!id && noTrackPhrase) {
582
+ return messageConstructor('voice', { 'payload': noTrackPhrase })
583
+ }
544
584
  }
545
- }
546
- case '{"VolumeSelector":1}':
547
- return messageConstructor('command', {'payload': 'volumedown'}, device)
548
- case '{"VolumeSelector":0}':
549
- return messageConstructor('command', {'payload': 'volumeup'}, device)
550
- default:
551
- debugMessage('unknown command')
552
- return messageConstructor('command', {'payload': 'ping'})
585
+ }
586
+ }
587
+
588
+ // tv + VolumeSelector
589
+ if ("VolumeSelector" in message.payload) {
590
+ let VolumeSelector = message.payload.VolumeSelector;
591
+ return messageConstructor('command', { 'payload': (VolumeSelector ? 'volumedown' : 'volumeup') }, device)
553
592
  }
554
-
593
+
594
+ debugMessage('unknown command')
595
+ return messageConstructor('command', { 'payload': 'ping' })
555
596
  } else {
556
- return messageConstructor('command', {'payload': 'ping'})
597
+ return messageConstructor('command', { 'payload': 'ping' })
557
598
  }
558
- case 'raw':
599
+ case 'raw':
559
600
  if (Array.isArray(message.payload)) { return message.payload }
560
601
  return [message.payload];
561
- case 'stopListening':
602
+ case 'stopListening':
562
603
  return [{
563
604
  "command": "serverAction",
564
605
  "serverActionEventPayload": {
@@ -570,7 +611,7 @@ module.exports = function(RED) {
570
611
 
571
612
  }
572
613
  function sendMessage(deviceId, messageType, message) {
573
-
614
+
574
615
  try {
575
616
  let device = searchDeviceByID(deviceId);
576
617
  //debugMessage(`deviceId: ${searchDeviceByID(deviceId)}`);
@@ -611,19 +652,19 @@ module.exports = function(RED) {
611
652
  //debugMessage(`timeCur: ${timeCurrent} timeMin: ${timeMin} timeMax: ${timeMax}`);
612
653
  return [false, daySheduler.phrase];
613
654
  }
614
-
655
+
615
656
 
616
657
  }
617
-
658
+
618
659
  function searchDeviceByID(id) {
619
660
  if (node.deviceList) {
620
661
  return node.deviceList.find(device => device.id == id)
621
- }
662
+ }
622
663
  }
623
664
  function onPing(device) {
624
665
  if (device) {sendMessage(device.id, 'command', {payload: 'ping'});}
625
666
  }
626
-
667
+
627
668
  function onPing(device) {
628
669
  sendMessage(device.id, 'command', {payload: 'ping'});
629
670
  }
@@ -632,19 +673,19 @@ module.exports = function(RED) {
632
673
  if (device) {
633
674
  if (device.ws) {
634
675
  switch(device.ws.readyState){
635
- case 0:
676
+ case 0:
636
677
  return {"color": "yellow", "text": "connecting..."}
637
- case 1:
678
+ case 1:
638
679
  return {"color": "green", "text": "connected"}
639
- case 2:
680
+ case 2:
640
681
  return {"color": "red", "text": "disconnecting"}
641
- case 3:
682
+ case 3:
642
683
  return {"color": "red", "text": "disconnected"}
643
684
  default:
644
685
  return {"color": "red", "text": "disconnected"}
645
686
  }
646
-
647
- }
687
+
688
+ }
648
689
  } else {
649
690
  return {"color": "red", "text": "disconnected"}
650
691
  }
@@ -652,7 +693,7 @@ module.exports = function(RED) {
652
693
  }
653
694
 
654
695
 
655
-
696
+
656
697
  function registerDevice(deviceId, nodeId, parameters) {
657
698
  let device = searchDeviceByID(deviceId);
658
699
  debugMessage(`Recieved parameters ${JSON.stringify(parameters)} for station id ${deviceId}`);
@@ -664,7 +705,7 @@ module.exports = function(RED) {
664
705
  debugMessage(`Device ${device.id} already registered with manager id ${device.manager}. Updating parameters and restart...`);
665
706
  reconnect(device);
666
707
  return 1;
667
-
708
+
668
709
  }
669
710
  //новый и первый запрос на регистрацию для устройства
670
711
  if (typeof(device.manager) == 'undefined') {
@@ -693,7 +734,7 @@ module.exports = function(RED) {
693
734
  registrationBuffer.splice(registrationBuffer.indexOf(currentBuffer), 1)
694
735
  debugMessage(`Element from registration buffer was deleted. Current buffer size is ${registrationBuffer.length}`)
695
736
  }
696
- reconnect(device);
737
+ reconnect(device);
697
738
  return 0;
698
739
  }
699
740
  //новый запрос на регистрацию при наличии уже зарегистрированной ноды
@@ -711,9 +752,9 @@ module.exports = function(RED) {
711
752
  registrationBuffer.push({"id": deviceId, "manager": nodeId, "parameters": parameters});
712
753
  debugMessage(`New element in registration buffer. Current buffer size is ${registrationBuffer.length}`)
713
754
  }
714
-
755
+
715
756
  }
716
-
757
+
717
758
 
718
759
  }
719
760
  function unregisterDevice(deviceId, nodeId){
@@ -724,7 +765,7 @@ module.exports = function(RED) {
724
765
  device.parameters = {};
725
766
  debugMessage(`For device ${deviceId} was succesfully unregistred managment node whith id ${device.manager}`);
726
767
  debugMessage(`device is: ${device}`);
727
- return 0;
768
+ return 0;
728
769
  } else {
729
770
  return 2;
730
771
  }
@@ -745,7 +786,7 @@ module.exports = function(RED) {
745
786
  if (device.savedVolumeLevel) { sendMessage(device.id, 'raw',{payload: {
746
787
  "command": "setVolume",
747
788
  "volume": parseFloat(device.savedVolumeLevel)
748
- }
789
+ }
749
790
  });
750
791
  }
751
792
  device.waitForIdle = false;
@@ -754,13 +795,13 @@ module.exports = function(RED) {
754
795
  function onStopPlay(device, phrase) {
755
796
  sendMessage(device.id, 'command', {payload: 'stop'});
756
797
  if (phrase.length > 0 && device.lastState.aliceState != 'SPEAKING') {sendMessage(device.id, 'tts', {payload: phrase, stopListening: true});}
757
- }
798
+ }
758
799
  function onClose() {
759
800
  clearInterval(node.interval);
760
801
  node.deviceList = [];
761
802
  node.removeListener('deviceReady', onDeviceReady)
762
803
  }
763
-
804
+
764
805
  node.on('refreshHttp', function(activeList, readyList) {
765
806
  RED.httpAdmin.get("/yandexdevices_"+node.id, RED.auth.needsPermission('yandex-login.read'), function(req,res) {
766
807
  res.json({"devices": readyList});
@@ -769,8 +810,8 @@ module.exports = function(RED) {
769
810
  res.json({"devices": activeList});
770
811
  });
771
812
  });
772
-
773
- node.on('refreshHttpDNS', function(dnsList) {
813
+
814
+ node.on('refreshHttpDNS', function(dnsList) {
774
815
  RED.httpAdmin.get("/mdns/"+node.id, RED.auth.needsPermission('yandex-login.read'), function(req,res) {
775
816
  res.json({"SearchResult": dnsList});
776
817
  });
@@ -794,7 +835,7 @@ module.exports = function(RED) {
794
835
  res.json({"error": 'no device found'});
795
836
  }
796
837
  });
797
-
838
+
798
839
  // main init
799
840
  if (typeof(node.token) !== 'undefined') {
800
841
  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.2",
3
+ "version": "0.3.5",
4
4
  "description": "Local management of YandexStation using API on websockets",
5
5
  "main": "index.js",
6
6
  "scripts": {