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

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
@@ -2,8 +2,10 @@
2
2
  С помощью модуля можно через локальное API управлять вопсроизведением на устройсвах Яндекса:
3
3
  - Яндекс Станция(протестировано)
4
4
  - Яндекс Станция мини(протестировано)
5
+ - Яндекс Станция мини 2 с экраном(протестировано)
5
6
  - Яндекс Станци Макс(протестировано)
6
7
  - Яндекс Модуль(не протестировано)
8
+ - Яндекс Модуль - 2 (в процессе тестирования)
7
9
  - JBL Link Music(не протестировано)
8
10
  - JBL Link Portable(протестировано)
9
11
 
@@ -14,7 +16,7 @@
14
16
 
15
17
 
16
18
  Для работы требуется токен от Яндекс.Музыки.
17
- В модуле в экспериментальном режиме реализована возможность получения токена из логин-пароля. Если получение токена не отрабатывает, то стоит попробовать включить и отключить двух-факторную аутентификацию в настройках Яндекса. [Источник](https://github.com/AlexxIT/YandexStation/issues/103). Убедиться в безопасности использования учетных данных можно посмотрев [код](./nodes/yandex-login.html)
19
+ В модуле в экспериментальном режиме реализована возможность получения токена из логин-пароля(Спасибо слать [сюда](https://github.com/twocolors)). Если получение токена не отрабатывает, то стоит попробовать включить и отключить двух-факторную аутентификацию в настройках Яндекса. [Источник](https://github.com/AlexxIT/YandexStation/issues/103). Убедиться в безопасности использования учетных данных можно, посмотрев [код](./nodes/yandex-login.html)
18
20
 
19
21
  Второй из варинатов его получения описан в [FAQ](#faq)
20
22
 
@@ -36,7 +38,9 @@
36
38
  ## Первоначальная настройка.
37
39
  После установки для начала работы добавить любую ноду, ввести учетные данные(токен) в раздел Login, сохранить и нажать Deploy(обязательно!). Как получить токен - написано в FAQ.
38
40
 
39
- После деплоя в настройках ноды в поле Station должны появиться станции доступные для управления. Если станция не появилась в списке, то можно подождать пару минут или перезапустить Node-Red.
41
+ После деплоя в настройках ноды в поле Station должны появиться станции доступные для управления.
42
+
43
+ npmЕсли станция не появилась в списке, то можно подождать пару минут или перезапустить Node-Red.
40
44
 
41
45
  ## Описание возможностей и сценариев использования.
42
46
  ### Нода Station
@@ -109,13 +113,53 @@ Phrase to say - фраза, которую скажет Алиса вместо
109
113
  Отправка команды, вместо того, чтобы говорить ее колонке голосом: "Включи свет", "Включи музыку", "Включи мой плейлист", "Отключись через 15 минут" и так далее.
110
114
 
111
115
  #### TTS.
112
- Воспросизведение голосом отправленных фраз - Text to Speech. Не имеет ограничения по символам. Есть ряд опций:
113
- - Fixed volume level. Позволяет произносить фразу заданной громкостью. Если не выбрано, то фраза произносится с текущим уровнем громкости. После произнесения уровень громкости вернется в изначальный.
114
- - Prevent listening. Если выбрано, то колонка после воспроизведения не "слушает", что ей ответят.
115
- - Pause while TTS. Ставит воспроизведение плеера на паузу на время речи. Воспроизведение будет продолжено, только если что-то играло на момент поступления команды.
116
+ Воспроизведение голосом отправленных фраз - Text to Speech. Не имеет ограничения по символам.
117
+ Параметры для TTS могут задаваться как в настройках, так и некоторые из них могут быть переопределены входящим сообщением.
118
+ - Text. Откуда и какой текст проговорить.
119
+ - из входящего сообщения (msg.payload по-умолчанию)
120
+ - строго заданную строку
121
+ - переменную из flow или global context
122
+ - JSON: выбрать сообщение случайным образом из массива вида ["один", "два", "три"].
123
+ - Voice. Выбор голоса для генерации. Может быть переопределено через msg.voice
124
+ - Effect. Эффекты для генерации голоса. Может быть переопределено через msg.effect
125
+ -
126
+ Есть ряд опций:
127
+ - Volume. Позволяет произносить фразу заданной громкостью. Если не выбрано, то фраза произносится с текущим уровнем громкости. После произнесения уровень громкости вернется в изначальный. Может быть переопределено через msg.volume
128
+ - Prevent listening. Если выбрано, то колонка после воспроизведения не "слушает", что ей ответят. Может быть переопределено через msg.prevent_listening
129
+ - Pause while TTS. Ставит воспроизведение плеера на паузу на время речи. Воспроизведение будет продолжено, только если что-то играло на момент поступления команды. Может быть переопределено через msg.pause_music
116
130
  Все опции комбинируемы между собой.
117
131
 
118
- Работают голосовые [спецэффекты](https://yandex.ru/dev/dialogs/alice/doc/speech-effects-docpage/),[дополнительные голоса](https://yandex.ru/dev/dialogs/alice/doc/speech-effects-docpage/) и [звуки](https://yandex.ru/dev/dialogs/alice/doc/sounds-docpage/), при этом отправка производится в ввиде строки. Например, "смелость sil <[500]> город+а берёт"
132
+ ##### Добавление голосу жизни и красок.
133
+ ###### Раставляйте ударения
134
+ При необходимости ударные гласные в словах следует отмечать знаком «+», например:
135
+
136
+ остр+ота
137
+ м+ука
138
+
139
+ ##### Разделяйте слова
140
+ Длинные слова можно разбить на слова покороче и проставить ударения для каждого из этих коротких слов, например:
141
+
142
+ мн+ого пр+офильный
143
+ с+еми пал+атинск
144
+
145
+ ##### Меняйте написание слов
146
+ Некоторые слова можно попробовать писать так, как они слышатся:
147
+
148
+ «ненастный» — нен+асный
149
+ «пожалуйста» — пож+алуста
150
+
151
+ ###### Добавляйте паузы
152
+ Чтобы задать паузу между словами, используйте синтаксис sil <[ количество_миллисекунд ]>. Например:
153
+
154
+ смелость sil <[500]> город+а берёт
155
+
156
+ Каждый отделенный пробелами пунктуационный знак обозначается паузой в 50-100 мс.
157
+
158
+ ###### Добавляйте [звуки из библиотеки](https://yandex.ru/dev/dialogs/alice/doc/sounds-docpage/)
159
+
160
+ <speaker audio=\"alice-sounds-game-win-1.opus\"> У вас получилось!
161
+
162
+
119
163
 
120
164
 
121
165
  #### Homekit Formatted.
package/nodes/get.js CHANGED
@@ -10,36 +10,48 @@ module.exports = function(RED) {
10
10
  node.lastState = {};
11
11
  node.status({});
12
12
 
13
-
13
+
14
14
  function debugMessage(text){
15
15
  if (node.debugFlag) {
16
16
  node.log(text);
17
17
  }
18
18
  }
19
- function preparePayload(message){
20
- let payload = {};
19
+ function preparePayload(message,inputMsg){
20
+ //let payload = {};
21
21
  if (node.output == 'status') {
22
- payload = {'payload': message}
22
+ inputMsg.payload = message;
23
23
  } else if (node.output == 'homekit') {
24
- if (node.homekitFormat == 'speaker') {
25
- (message.playerState)? payload = {'payload': {
26
- "CurrentMediaState": (message.playing) ? 0 : 1,
27
- "ConfiguredName": `${(message.playerState.subtitle) ? message.playerState.subtitle : 'No Artist'} - ${(message.playerState.title) ? message.playerState.title : 'No Track Name'}`
28
- } }:payload = {'payload': {
29
- "CurrentMediaState": (message.playing) ? 0 : 1,
30
- "ConfiguredName": `No Artist - No Track`
31
- } }
32
- }else if (node.homekitFormat == 'tv') {
33
- payload = {'payload': {
34
- "Active": (message.playing) ? 1 : 0
35
- }
36
- }
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);
37
48
  }
49
+
38
50
  }
39
- return payload;
51
+ return inputMsg;
40
52
 
41
53
  }
42
-
54
+
43
55
 
44
56
  node.onStatus = function(data) {
45
57
  if (data) {
@@ -47,9 +59,9 @@ module.exports = function(RED) {
47
59
  //node.log('new status ' + data)
48
60
  }
49
61
  }
50
- node.onInput = function(){
51
- debugMessage('input message');
52
- ( 'aliceState' in node.lastState )?node.send(preparePayload(node.lastState)):node.send({'payload': {}})
62
+ node.onInput = function(msg, send, done){
63
+ debugMessage('current state: ' + JSON.stringify(node.lastState));
64
+ ( 'aliceState' in node.lastState )?node.send(preparePayload(node.lastState,msg)):node.send(msg)
53
65
  }
54
66
  node.onMessage = function(message){
55
67
  node.lastState = message;
@@ -57,7 +69,7 @@ module.exports = function(RED) {
57
69
  node.onClose = function(){
58
70
  node.controller.removeListener(`message_${node.station}`, node.onMessage)
59
71
  }
60
-
72
+
61
73
  node.on('input', node.onInput);
62
74
 
63
75
  node.on('close', node.onClose)
package/nodes/in.js CHANGED
@@ -10,9 +10,9 @@ module.exports = function(RED) {
10
10
  node.homekitFormat = config.homekitFormat;
11
11
  node.lastMessage = {};
12
12
  node.status({});
13
-
14
13
 
15
-
14
+
15
+
16
16
  debugMessage(`Node settings: ID: ${node.station}, Output Format: ${node.output}, HK: ${node.homekitFormat}`);
17
17
  function debugMessage(text){
18
18
  if (node.debugFlag) {
@@ -26,20 +26,32 @@ module.exports = function(RED) {
26
26
  if (node.output == 'status') {
27
27
  payload = {'payload': message}
28
28
  } else if (node.output == 'homekit') {
29
- if (node.homekitFormat == 'speaker') {
30
- (message.playerState)? payload = {'payload': {
31
- "CurrentMediaState": (message.playing) ? 0 : 1,
32
- "ConfiguredName": `${(message.playerState.subtitle) ? message.playerState.subtitle : 'No Artist'} - ${(message.playerState.title) ? message.playerState.title : 'No Track Name'}`
33
- } }:payload = {'payload': {
34
- "CurrentMediaState": (message.playing) ? 0 : 1,
35
- "ConfiguredName": `No Artists - No Track`
36
- } }
37
- }else if (node.homekitFormat == 'tv') {
38
- payload = {'payload': {
39
- "Active": (message.playing) ? 1 : 0
40
- }
41
- }
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)
42
53
  }
54
+
43
55
  }
44
56
  return payload;
45
57
 
@@ -50,8 +62,8 @@ module.exports = function(RED) {
50
62
  if ((JSON.stringify(node.lastMessage.payload) != JSON.stringify(message.payload))) {
51
63
  node.send(message)
52
64
  node.lastMessage = message
53
- debugMessage(`Sended message to HK: ${JSON.stringify(message)}`);
54
-
65
+ debugMessage(`Sended message to HK: ${JSON.stringify(message)}`);
66
+
55
67
  }
56
68
  } else {
57
69
  node.send(message);
@@ -77,7 +89,7 @@ module.exports = function(RED) {
77
89
  node.controller.on(`message_${node.station}`, node.onMessage);
78
90
  node.controller.on(`statusUpdate_${node.station}`, node.onStatus);
79
91
  }
80
-
92
+
81
93
  node.on('close', node.onClose);
82
94
 
83
95
 
@@ -1,169 +1,271 @@
1
1
  <script type="text/javascript">
2
-
3
- RED.nodes.registerType('alice-local-out',{
4
- category: 'Yandex Station',
5
- color: '#b89fcc',
6
- defaults: {
7
- name:{value: ""},
8
- token: {
9
- type: "yandex-login",
10
- required: true
11
- },
12
- station_id: {
13
- required: true
14
- },
15
- debugFlag: {
16
- value: false
17
- },
18
- input: {
19
- value: "command",
20
- required: true
21
- },
22
- volume: {},
23
- volumeFlag: {
24
- value: false
25
- },
26
- stopListening: {
27
- value: true
28
- },
29
- pauseMusic: {
30
- value: false
31
- },
32
- noTrack: {
33
- }
34
- },
35
- inputs:1,
36
- outputs:0,
37
- icon: "station.png",
38
- label: function() {
39
- return this.name||this.station_id
40
- },
41
- paletteLabel: "Yandex OUT",
42
- oneditprepare: onOpen
2
+
3
+ RED.nodes.registerType('alice-local-out', {
4
+ category: 'Yandex Station',
5
+ color: '#b89fcc',
6
+ defaults: {
7
+ name: {value: ''},
8
+ token: {
9
+ type: 'yandex-login',
10
+ required: true,
11
+ },
12
+ station_id: {
13
+ required: true,
14
+ },
15
+ debugFlag: {
16
+ value: false,
17
+ },
18
+ input: {
19
+ value: 'command',
20
+ required: true,
21
+ },
22
+ payload: {
23
+ value: 'payload',
24
+ },
25
+ payloadType: {
26
+ value: 'msg',
27
+ },
28
+ volume: {},
29
+ volumeFlag: {
30
+ value: false,
31
+ },
32
+ stopListening: {
33
+ value: true,
34
+ },
35
+ pauseMusic: {
36
+ value: false,
37
+ },
38
+ noTrack: {},
39
+ ttsVoice: {
40
+ value: null,
41
+ },
42
+ ttsEffect: {
43
+ value: null,
44
+ required: false,
45
+ },
46
+ },
47
+ inputs: 1,
48
+ outputs: 0,
49
+ icon: 'station.png',
50
+ label: function() {
51
+ return this.name || this.station_id;
52
+ },
53
+ paletteLabel: 'Yandex OUT',
54
+ oneditprepare: onOpen,
55
+ });
56
+
57
+ function onOpen() {
58
+ var command = $('#node-input-input').val();
59
+
60
+ $('#node-input-payload').typedInput({
61
+ types: ['msg', 'str', 'flow', 'global', 'json'],
62
+ default: 'msg',
63
+ value: 'payload',
64
+ typeField: $('#node-input-payloadType'),
43
65
  });
44
- function onOpen() {
45
- let config = RED.nodes.node($('#node-input-token').val());
46
- let selector = $('#node-input-station_id');
47
- selector.empty();
48
- let currentId = this.station_id;
49
- $.getJSON('yandexdevices_'+config.id,function(data) {
50
- data.devices.forEach(device => {
51
- selector.append(`<option value="${device.id}">
66
+
67
+
68
+
69
+ $("#node-input-ttsEffect").typedInput({type:"ttsEffect", types:[{
70
+ value: 'ttsEffect',
71
+ multiple: false,
72
+ options: [
73
+ { value: "", label: RED._("node-red-contrib-yandex-station-management/alice-local-out:effect.none")},
74
+ { value: "behind_the_wall", label: RED._("node-red-contrib-yandex-station-management/alice-local-out:effect.behind_the_wall")},
75
+ { value: "hamster", label: RED._("node-red-contrib-yandex-station-management/alice-local-out:effect.hamster")},
76
+ { value: "megaphone", label: RED._("node-red-contrib-yandex-station-management/alice-local-out:effect.megaphone")},
77
+ { value: "pitch_down", label: RED._("node-red-contrib-yandex-station-management/alice-local-out:effect.pitch_down")},
78
+ { value: "psychodelic", label: RED._("node-red-contrib-yandex-station-management/alice-local-out:effect.psychodelic")},
79
+ { value: "pulse", label: RED._("node-red-contrib-yandex-station-management/alice-local-out:effect.pulse")},
80
+ { value: "train_announce", label: RED._("node-red-contrib-yandex-station-management/alice-local-out:effect.train_announce")}
81
+ ]
82
+ }]})
83
+
84
+ let config = RED.nodes.node($('#node-input-token').val());
85
+ let selector = $('#node-input-station_id');
86
+ selector.empty();
87
+ let currentId = this.station_id;
88
+ $.getJSON('yandexdevices_' + config.id, function(data) {
89
+ data.devices.forEach(device => {
90
+ selector.append(`<option value="${device.id}">
52
91
  ${device.name}(${device.id})
53
- </option>`)
54
- $(`#node-input-station_id :contains(${currentId})`).attr("selected", "selected");
55
- });
56
- });
57
- $('#node-input-input').on('change', function(type, value) {
58
- //var val = $(this).val();
59
- //alert(val);
60
- if ($(this).val() == 'tts') {
61
- $('#volume-range').show();
62
- $('#listening-checkbox').show();
63
- $('#pause-checkbox').show();
64
- if ($('#node-input-volumeFlag').prop("checked")){$('#node-input-volume').show();$('#range-label').show();}
65
-
66
- } else {
67
- $('#volume-range').hide();
68
- $('#listening-checkbox').hide();
69
- $('#pause-checkbox').hide();
70
-
71
- }
72
- if ($(this).val() == 'homekit') {
73
- $('#default-command').show();
74
- } else {
75
- $('#default-command').hide();
76
- }
77
- });
78
- //$('#node-input-volume').hide();
79
- $('#node-input-volumeFlag').on('change', function(type, value) {
80
- let val = $(this).val();
81
- //alert($(this).val());
82
- if ($('#node-input-input').val() == 'tts' && $(this).prop("checked")) {
83
- $('#node-input-volume').show();
84
- $('#range-label').show();
85
-
86
- } else if ($('#node-input-input').val() == 'tts' && !$(this).prop("checked") ) {
87
- $('#node-input-volume').hide();
88
- $('#range-label').hide();
89
-
90
- }
91
- })
92
- $('#node-input-volume').on('change', function() {
93
- $('#volume-level').text(`${parseFloat($('#node-input-volume').val())*100}`)
94
- })
95
- }
96
- function updateDevices() {
97
-
98
-
99
- }
92
+ </option>`);
93
+ $(`#node-input-station_id :contains(${currentId})`).attr('selected', 'selected');
94
+ });
95
+ });
96
+
97
+ $('.command_options').hide();
98
+ $('.command_options-' + command).show();
99
+
100
+ $('#node-input-input').on('change', function(type, value) {
101
+ $('.command_options').hide();
102
+ $('.command_options-' + $(this).val()).show();
103
+
104
+ if ($(this).val() == 'tts') {
105
+ if ($('#node-input-volumeFlag').prop('checked')) {
106
+ $('#node-input-volume').show();
107
+ $('#range-label').show();
108
+ }
109
+ }
110
+ });
111
+
112
+ $('#node-input-volumeFlag').on('change', function(type, value) {
113
+ let val = $(this).val();
114
+ if ($('#node-input-input').val() == 'tts' && $(this).prop('checked')) {
115
+ $('#node-input-volume').show();
116
+ $('#range-label').show();
117
+
118
+ } else if ($('#node-input-input').val() == 'tts' && !$(this).prop('checked')) {
119
+ $('#node-input-volume').hide();
120
+ $('#range-label').hide();
121
+
122
+ }
123
+ });
124
+ $('#node-input-volume').on('change', function() {
125
+ $('#volume-level').text(`${parseFloat($('#node-input-volume').val())}`);
126
+ });
127
+ }
100
128
  </script>
101
129
 
102
130
  <script type="text/html" data-template-name="alice-local-out">
103
131
  <style>
104
- .label-long {min-width: 150px;width: 20%;}
105
- .online {display: inline-block; width: auto; vertical-align: middle;}
106
- </style>
132
+ label.label {
133
+ line-height: 1em;
134
+ }
135
+ label.label-long {
136
+ min-width: 150px;
137
+ width: 20%;
138
+ }
139
+ .online {
140
+ display: inline-block;
141
+ width: auto;
142
+ vertical-align: middle;
143
+ }
144
+ </style>
107
145
 
108
146
  <div class="form-row">
109
- <label for="node-input-name"><i class="fa fa-bookmark"></i>Name</label>
110
- <input type="text" id="node-input-name" placeholder="">
147
+ <label for="node-input-name"><i class="fa fa-bookmark"></i>&nbsp;<span data-i18n="label.name"></span></label>
148
+ <input type="text" id="node-input-name">
111
149
  </div>
112
150
  <div class="form-row">
113
- <label for="node-input-token"><i class="fa fa-globe"></i>Login</label>
114
- <input type="text" id="node-input-token" placeholder="token">
151
+ <label for="node-input-token"><i class="fa fa-globe"></i>&nbsp;<span data-i18n="label.login"></span></label>
152
+ <input type="text" id="node-input-token" placeholder="">
115
153
  </div>
116
154
  <div class="form-row">
117
- <label for="node-input-station_id"><i class="fa fa-database">Station</i></label>
155
+ <label for="node-input-station_id"><i class="fa fa-database"></i>&nbsp;<span data-i18n="label.station"></span></label>
118
156
  <div style="display: inline-block;position: relative;width: 70%;height: 20px;">
119
- <div style="position: absolute;left: 0px; right: 40px;">
157
+ <div style="position: absolute;left: 0; right: 40px;">
120
158
  <select id="node-input-station_id" data-single="true" style="width: 100%"></select>
121
- </div>
159
+ </div>
122
160
  <div style="text-align: end; display: inline; float: right">
123
- <button onclick="onOpen()" class="red-ui-button" style="position: absolute;right: 0px;top: 0px;"><i class="fa fa-refresh"></i></button>
161
+ <button onclick="onOpen()" class="red-ui-button" style="position: absolute;right: 0px;top: 0px;">
162
+ <i class="fa fa-refresh"></i></button>
124
163
  </div>
125
164
  </div>
126
165
  </div>
127
166
  <div class="form-row">
128
- <label for="node-input-input"><i class="fa fa-arrow-circle-o-right">Node Input</i></label>
167
+ <label for="node-input-input"><i class="fa fa-arrow-circle-o-right"></i>&nbsp;<span data-i18n="label.command"></span></label>
129
168
  <div style="display: inline-block;position: relative;width: 70%;height: 20px;">
130
169
  <div style="position: absolute;left: 0px; right: 0px;">
131
170
  <select id="node-input-input" data-single="true" style="width: 100%">
132
- <option value="command">Player Command</option>
133
- <option value="voice">Voice Command</option>
134
- <option value="tts">TTS</option>
135
- <option value="homekit">Homekit Formatted</option>
136
- <option value="raw">Raw Command</option>
137
- <option value="stopListening">Stop Listening</option>
171
+ <option value="command" data-i18n="command.player"></option>
172
+ <option value="voice" data-i18n="command.voice"></option>
173
+ <option value="tts" data-i18n="command.tts"></option>
174
+ <option value="homekit" data-i18n="command.homekit"></option>
175
+ <option value="stopListening" data-i18n="command.stop_listening"></option>
176
+ <option value="raw" data-i18n="command.raw"></option>
138
177
  </select>
139
- </div>
178
+ </div>
140
179
  </div>
141
180
  </div>
142
181
 
143
- <div class="form-row" id="volume-range">
144
- <label for="node-input-volume" class="label-long"><i class="fa fa-volume-up"></i>Fixed volume Level</label>
145
- <input type="checkbox" id="node-input-volumeFlag" style="display: inline-block; width: auto; vertical-align: baseline;">
146
- <input type="range" id="node-input-volume" name="volume" min="0" max="1" step="0.1" style="display: inline-block; width: 150px; margin-left: 10px; vertical-align: middle;">
147
- <div id="range-label" class='online'><span id="volume-level" class="online"></span><span class="online">%</span></div>
182
+ <!--for homekit-->
183
+ <div class="form-row command_options command_options-homekit">
184
+ <label for="node-input-noTrack" class="label-long" style="display: inline-block"><i class="fa fa-podcast"></i>&nbsp;<span data-i18n="label.default_command"></span></label>
185
+ <input type="text" id="node-input-noTrack" data-i18n="[placeholder]placeholder.play_music" style="display: inline-block;left: 0px; right: 0px; width: 60%; vertical-align: middle;">
148
186
  </div>
149
- <div class="form-row" id="listening-checkbox">
150
- <label for="node-input-stopListening" class="label-long"><i class="fa fa-deaf"></i>Prevent Listening</label>
151
- <input type="checkbox" id="node-input-stopListening" style="display: inline-block; width: auto; vertical-align: baseline;">
187
+
188
+ <!--for tts-->
189
+ <div class="form-row command_options command_options-tts">
190
+ <label for="node-input-payload" class="l-width"><i class="fa fa-envelope"></i> <span data-i18n="label.text"></span></label>
191
+ <input type="text" id="node-input-payload" style="width:70%">
192
+ <input type="hidden" id="node-input-payloadType">
152
193
  </div>
153
- <div class="form-row" id="pause-checkbox">
154
- <label for="node-input-pauseMusic" class="label-long"><i class="fa fa-pause"></i>Pause while TTS</label>
155
- <input type="checkbox" id="node-input-pauseMusic" style="display: inline-block; width: auto; vertical-align: baseline;">
194
+ <div class="form-row command_options command_options-tts">
195
+ <label class="label" for='node-input-ttsVoice'>
196
+ <i class='fa fa-user'></i>&nbsp;<span data-i18n="label.voice"></span>
197
+ <div class="red-ui-debug-msg-type-string" style="font-size: 10px;">msg.voice</div></label>
198
+ </label>
199
+ <div style="display: inline-block;position: relative;width: 70%;height: 30px;">
200
+ <div style="position: absolute;left: 0px; right: 0px;">
201
+ <select id="node-input-ttsVoice" style="width: 100%;vertical-align: top;">
202
+ <option value="" data-i18n="voice.default"></option>
203
+ <!-- <option value="alena" data-i18n="voice.alena"></option>-->
204
+ <option value="alyss" data-i18n="voice.alyss"></option>
205
+ <option value="anton_samokhvalov" data-i18n="voice.anton_samokhvalov"></option>
206
+ <option value="dude" data-i18n="voice.dude"></option>
207
+ <option value="ermil" data-i18n="voice.ermil"></option>
208
+ <option value="ermilov" data-i18n="voice.ermilov"></option>
209
+ <option value="ermil_with_tuning" data-i18n="voice.ermil_with_tuning"></option>
210
+ <option value="erkanyavas" data-i18n="voice.erkanyavas"></option>
211
+ <!-- <option value="filipp" data-i18n="voice.filipp"></option>-->
212
+ <option value="jane" data-i18n="voice.jane"></option>
213
+ <option value="kolya" data-i18n="voice.kolya"></option>
214
+ <option value="kostya" data-i18n="voice.kostya"></option>
215
+ <option value="levitan" data-i18n="voice.levitan"></option>
216
+ <option value="nastya" data-i18n="voice.nastya"></option>
217
+ <option value="nick" data-i18n="voice.nick"></option>
218
+ <option value="oksana" data-i18n="voice.oksana"></option>
219
+ <option value="omazh" data-i18n="voice.omazh"></option>
220
+ <option value="robot" data-i18n="voice.robot"></option>
221
+ <option value="sasha" data-i18n="voice.sasha"></option>
222
+ <option value="silaerkan" data-i18n="voice.silaerkan"></option>
223
+ <option value="smoky" data-i18n="voice.smoky"></option>
224
+ <option value="tanya" data-i18n="voice.tanya"></option>
225
+ <option value="tatyana_abramova" data-i18n="voice.tatyana_abramova"></option>
226
+ <option value="zahar" data-i18n="voice.zahar"></option>
227
+ <option value="zhenya" data-i18n="voice.zhenya"></option>
228
+ <option value="zombie" data-i18n="voice.zombie"></option>
229
+ <option value="voicesearch" data-i18n="voice.voicesearch"></option>
230
+ </select>
231
+ </div>
232
+ </div>
233
+ </div>
234
+ <div class="form-row command_options command_options-tts">
235
+ <label for="node-input-ttsEffect" class="label l-width" style="vertical-align: bottom;">
236
+ <i class="fa fa-magic"></i> <span data-i18n="label.effect"></span>
237
+ <div class="red-ui-debug-msg-type-string" style="font-size: 10px;">msg.effect</div></label>
238
+ </label>
239
+ <input type="text" id="node-input-ttsEffect">
240
+ </div>
241
+ <div class="form-row command_options command_options-tts">
242
+ <label for="node-input-volume" class="label label-long">
243
+ <i class="fa fa-volume-up"></i>&nbsp;<span data-i18n="label.volume"></span>
244
+ <div class="red-ui-debug-msg-type-string" style="font-size: 10px;">msg.volume</div></label>
245
+ </label>
246
+ <input type="checkbox" id="node-input-volumeFlag" style="display: inline-block; width: auto; vertical-align: top;">
247
+ <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
+ <div id="range-label" class='online'><span id="volume-level" class="online"></span><span class="online">%</span></div>
156
249
  </div>
157
- <div class="form-row" id="default-command">
158
- <label for="node-input-noTrack" class="label-long" style="display: inline-block"><i class="fa fa-podcast"></i>Default command</label>
159
- <input type="text" id="node-input-noTrack" placeholder="Включи музыку" style="display: inline-block;left: 0px; right: 0px; width: 60%; vertical-align: middle;">
250
+ <div class="form-row command_options command_options-tts">
251
+ <label for="node-input-stopListening" class="label label-long">
252
+ <i class="fa fa-deaf"></i>&nbsp;<span data-i18n="label.prevent_listening"></span>
253
+ <div class="red-ui-debug-msg-type-string" style="font-size: 10px;">msg.prevent_listening</div></label>
254
+ </label>
255
+ <input type="checkbox" id="node-input-stopListening" style="display: inline-block; width: auto; vertical-align: top;">
160
256
  </div>
257
+ <div class="form-row command_options command_options-tts">
258
+ <label for="node-input-pauseMusic" class="label label-long">
259
+ <i class="fa fa-pause"></i>&nbsp;<span data-i18n="label.pause_while_tts"></span>
260
+ <div class="red-ui-debug-msg-type-string" style="font-size: 10px;">msg.pause_music</div></label>
261
+ </label>
262
+ <input type="checkbox" id="node-input-pauseMusic" style="display: inline-block; width: auto; vertical-align: top;">
263
+ </div>
264
+
265
+
161
266
  <div class="form-row" style="vertical-align:bottom;">
162
- <label for='node-input-debugFlag'><i class='fa fa-share-square'>Debug</i></label>
163
- <input type="checkbox" id="node-input-debugFlag" checked="checked" style="display: inline-block; width: auto; vertical-align: top;">
267
+ <label for='node-input-debugFlag' class="label-long"><i class='fa fa-share-square'></i>&nbsp;<span data-i18n="label.debug"></span></label>
268
+ <input type="checkbox" id="node-input-debugFlag" checked="checked" style="display: inline-block; width: auto; vertical-align: top;">
164
269
  </div>
165
270
  </script>
166
271
 
167
- <script type="text/html" data-help-name="alice-local-out">
168
- <p> Output node for Yandex Station local management</p>
169
- </script>
@@ -2,7 +2,9 @@ module.exports = function(RED) {
2
2
  function AliceLocalOutNode(config) {
3
3
  RED.nodes.createNode(this,config);
4
4
  let node = this;
5
+ node.config = config;
5
6
  node.controller = RED.nodes.getNode(config.token);
7
+
6
8
  node.input = config.input;
7
9
  node.station = config.station_id;
8
10
  node.debugFlag = config.debugFlag;
@@ -11,6 +13,8 @@ module.exports = function(RED) {
11
13
  node.stopListening = config.stopListening;
12
14
  node.noTrackPhrase = config.noTrack;
13
15
  node.pauseMusic = config.pauseMusic;
16
+ node.ttsVoice = config.ttsVoice;
17
+ node.ttsEffect = config.ttsEffect;
14
18
  node.status({});
15
19
 
16
20
  function debugMessage(text){
@@ -19,15 +23,100 @@ module.exports = function(RED) {
19
23
  }
20
24
  }
21
25
  debugMessage(node.station);
22
- node.on('input', (data) => {
23
- debugMessage(`input: ${JSON.stringify(data)}`)
24
- if (node.volumeFlag) {data.volume = node.volume}
25
- if (node.stopListening) {data.stopListening = node.stopListening}
26
- if (node.noTrackPhrase) {data.noTrackPhrase = node.noTrackPhrase}
27
- if (node.pauseMusic) {data.pauseMusic = node.pauseMusic}
26
+ node.on('input', (input) => {
27
+ debugMessage(`input: ${JSON.stringify(input)}`)
28
+
28
29
  if (node.station) {
29
- node.controller.sendMessage(node.station, node.input, data);
30
- debugMessage(`Sending data: station: ${node.station}, input type: ${node.input}, data: ${JSON.stringify(data)}`);
30
+ var data = {};
31
+ //data.payload = input.payload;
32
+
33
+ //apply node's config
34
+ if (node.volumeFlag) {data.volume = node.volume/100}
35
+ if (node.stopListening) {data.stopListening = node.stopListening}
36
+ if (node.noTrackPhrase) {data.noTrackPhrase = node.noTrackPhrase}
37
+ if (node.pauseMusic) {data.pauseMusic = node.pauseMusic}
38
+
39
+
40
+ //redefine options from input
41
+ if ("volume" in input) {data.volume = input.volume/100}
42
+ if ("voice" in input) {node.ttsVoice = input.voice}
43
+ if ("effect" in input) {node.ttsEffect = input.effect}
44
+ if ("prevent_listening" in input) {node.noTrackPhrase = input.prevent_listening}
45
+ if ("pause_music" in input) {data.pauseMusic = input.pause_music}
46
+
47
+
48
+ if ('tts' === node.input) {
49
+ let payload;
50
+ switch (node.config.payloadType) {
51
+ case 'flow': {
52
+ if (typeof(node.context().flow.get(node.config.payload)) != "undefined") {
53
+ payload = node.context().flow.get(node.config.payload)
54
+ } else {
55
+ debugMessage('Empty flow context with key '+ node.config.payload)
56
+ }
57
+ break;
58
+ }
59
+ case 'global': {
60
+ if (typeof(node.context().global.get(node.config.payload)) != "undefined") {
61
+ payload = node.context().global.get(node.config.payload)
62
+ } else {
63
+ debugMessage('Empty global context with key '+ node.config.payload)
64
+ }
65
+ break;
66
+
67
+ }
68
+ case 'str': {
69
+ payload = node.config.payload;
70
+ break;
71
+ }
72
+ case 'json': {
73
+ let arr = [];
74
+
75
+ try {
76
+ arr = JSON.parse(node.config.payload)
77
+ payload = arr[(Math.random() * arr.length) | 0];
78
+ } catch (e) {
79
+ debugMessage("Error on parsing input JSON: "+ e);
80
+ }
81
+
82
+ break;
83
+ }
84
+ case 'msg': {
85
+ payload = node.input[node.config.payload]
86
+ }
87
+ default: {
88
+ payload = input[node.config.payload];
89
+ break;
90
+ }
91
+ }
92
+ if (typeof(payload) != "undefined" ) {
93
+ data.payload = payload;
94
+ if (node.ttsVoice) {
95
+ data.payload = "<speaker voice='" + node.ttsVoice + "'>" + data.payload;
96
+ }
97
+ if (node.ttsEffect) {
98
+ let effectsArr = node.ttsEffect.split(',');
99
+ for (let ind in effectsArr) {
100
+ data.payload = "<speaker effect='" + effectsArr[ind] + "'>" + data.payload;
101
+ }
102
+ }
103
+ } else {
104
+ data.payload = ""
105
+ }
106
+ if (data.payload.length > 0) {node.controller.sendMessage(node.station, node.input, data)
107
+ debugMessage(`Sending data: station: ${node.station}, input type: ${node.input}, data: ${JSON.stringify(data)}`);
108
+ } else {
109
+ debugMessage("Nothing to send. Check input and parameters")
110
+ }
111
+ } else {
112
+ data.payload = input.payload;
113
+ data.hap = input.hap;
114
+ node.controller.sendMessage(node.station, node.input, data);
115
+ debugMessage(`Sending data: station: ${node.station}, input type: ${node.input}, data: ${JSON.stringify(data)}`);
116
+ }
117
+
118
+ } else {
119
+ debugMessage('node.station is empty');
31
120
  }
32
121
  });
33
122
 
@@ -37,10 +126,10 @@ module.exports = function(RED) {
37
126
  node.status({fill: data.color,shape:"dot",text: data.text});
38
127
  }
39
128
  }
40
-
129
+
41
130
 
42
131
  node.on('close', () => {
43
- node.controller.removeListener(`statusUpdate_${node.station}`, node.onStatus)
132
+ node.controller.removeListener(`statusUpdate_${node.station}`, node.onStatus)
44
133
  });
45
134
 
46
135
  if (node.controller) {
@@ -49,4 +138,4 @@ module.exports = function(RED) {
49
138
  }
50
139
  }
51
140
  RED.nodes.registerType("alice-local-out",AliceLocalOutNode);
52
- }
141
+ }
@@ -0,0 +1,45 @@
1
+ <script type="text/html" data-help-name="alice-local-out">
2
+ <p>Output node for Yandex Station local management</p>
3
+
4
+
5
+ <h3>TTS</h3>
6
+ <p>Воспросизведение голосом отправленных фраз - Text to Speech. Не имеет ограничения по символам.</p>
7
+ <dl class="message-properties">
8
+ <dt>Текст</dt><dd>Откуда брать текст: msg.payload, flow, global или Json - выбрать сообщение случайным образом из массива вида <code>["один", "два", "три"]</code>.</dd>
9
+ <dt class="optional">Голос</dt><dd>Изменить голос.</dd>
10
+ <dt class="optional">Эффект</dt><dd>Наложить эффект на голос.</dd>
11
+ <dt class="optional">Громкость</dt><dd>Позволяет произносить фразу заданной громкостью. Если не выбрано, то фраза произносится с текущим уровнем громкости. После произнесения, уровень громкости вернется в изначальный.</dd>
12
+ <dt class="optional">Не ждать ответ</dt><dd>Если выбрано, то колонка, после воспроизведения, не "слушает", что ей ответят.</dd>
13
+ <dt class="optional">Плеер на паузу</dt><dd>Ставит воспроизведение плеера на паузу на время речи. Воспроизведение будет продолжено, только если что-то играло на момент поступления команды.</dd>
14
+ </dl>
15
+
16
+ <h4>Отмечайте ударения</h4>
17
+ <p>При необходимости ударные гласные в словах следует отмечать знаком «+», например:</p>
18
+ <ul>
19
+ <li><code>остр+ота</code></li>
20
+ <li><code>м+ука</code></li>
21
+ </ul>
22
+
23
+ <h4>Разделяйте слова</h4>
24
+ <p>Длинные слова можно разбить на слова покороче и проставить ударения для каждого из этих коротких слов, например:</p>
25
+ <ul>
26
+ <li><code>мн+ого пр+офильный</code></li>
27
+ <li><code>с+еми пал+атинск</code></li>
28
+ </ul>
29
+
30
+ <h4>Меняйте написание слов</h4>
31
+ <p>Некоторые слова можно попробовать писать так, как они слышатся:</p>
32
+ <ul>
33
+ <li><code>«ненастный» — нен+асный</code></li>
34
+ <li><code>«пожалуйста» — пож+алуста</code></li>
35
+ </ul>
36
+
37
+ <h4>Добавляйте паузы</h4>
38
+ <p>Чтобы задать паузу между словами, используйте синтаксис sil <[ количество_миллисекунд ]>. Например:</p>
39
+ <ul>
40
+ <li><code>смелость sil <[500]> город+а берёт</code></li>
41
+ </ul>
42
+ <p>Каждый отделенный пробелами пунктуационный знак обозначается паузой в 50-100 мс.</p>
43
+ </script>
44
+
45
+
@@ -0,0 +1,71 @@
1
+ {
2
+ "label": {
3
+ "name": "Name",
4
+ "text": "Text",
5
+ "effect": "Effect",
6
+ "login": "Login",
7
+ "station": "Station",
8
+ "voice": "Voice",
9
+ "debug": "Debug",
10
+ "command": "Command",
11
+ "volume": "Volume",
12
+ "prevent_listening": "Prevent listening",
13
+ "pause_while_tts": "Pause while TTS",
14
+ "default_command": "Default command"
15
+ },
16
+ "placeholder": {
17
+ "name": "Name",
18
+ "play_music": "Play music"
19
+ },
20
+ "command": {
21
+ "player": "Player",
22
+ "tts": "Text-to-Speech",
23
+ "voice": "Voice",
24
+ "raw": "Raw",
25
+ "stop_listening": "Stop listening",
26
+ "homekit": "Homekit"
27
+ },
28
+ "voice": {
29
+ "default": "Default",
30
+ "alena": "Alena",
31
+ "alyss": "Alyss",
32
+ "anton_samokhvalov": "Anton Samokhvalov",
33
+ "dude": "Dude",
34
+ "ermil": "Ermil",
35
+ "ermilov": "Ermilov",
36
+ "ermil_with_tuning": "Ermil (tuning)",
37
+ "erkanyavas": "Erkanyavas",
38
+ "filipp": "Filipp",
39
+ "jane": "Jane",
40
+ "kolya": "Kolya",
41
+ "kostya": "Kostya",
42
+ "levitan": "Levitan",
43
+ "nastya": "Nastya",
44
+ "nick": "Nick",
45
+ "oksana": "Oksana",
46
+ "omazh": "Omazh",
47
+ "robot": "Robot",
48
+ "sasha": "Sasha",
49
+ "silaerkan": "Silaerkan",
50
+ "smoky": "Smoky",
51
+ "tanya": "Tanya",
52
+ "tatyana_abramova": "Tatyana Abramova",
53
+ "zahar": "Zahar",
54
+ "zhenya": "Zhenya",
55
+ "zombie": "Zombie",
56
+ "voicesearch": "Voicesearch"
57
+ },
58
+ "effect": {
59
+ "none": "None",
60
+ "behind_the_wall": "behind_the_wall",
61
+ "hamster": "hamster",
62
+ "megaphone": "megaphone",
63
+ "pitch_down": "pitch_down",
64
+ "psychodelic": "psychodelic",
65
+ "pulse": "pulse",
66
+ "train_announce": "train_announce"
67
+ }
68
+ }
69
+
70
+
71
+
@@ -0,0 +1,42 @@
1
+ <script type="text/html" data-help-name="alice-local-out">
2
+ <p>Команда для яндекс станции</p>
3
+
4
+ <h3>TTS</h3>
5
+ <p>Воспросизведение голосом отправленных фраз - Text to Speech. Не имеет ограничения по символам.</p>
6
+ <dl class="message-properties">
7
+ <dt>Текст</dt><dd>Откуда брать текст: msg.payload, flow, global или Json - выбрать сообщение случайным образом из массива вида <code>["один", "два", "три"]</code>.</dd>
8
+ <dt class="optional">Голос</dt><dd>Изменить голос.</dd>
9
+ <dt class="optional">Эффект</dt><dd>Наложить эффект на голос.</dd>
10
+ <dt class="optional">Громкость</dt><dd>Позволяет произносить фразу заданной громкостью. Если не выбрано, то фраза произносится с текущим уровнем громкости. После произнесения, уровень громкости вернется в изначальный.</dd>
11
+ <dt class="optional">Не ждать ответ</dt><dd>Если выбрано, то колонка, после воспроизведения, не "слушает", что ей ответят.</dd>
12
+ <dt class="optional">Плеер на паузу</dt><dd>Ставит воспроизведение плеера на паузу на время речи. Воспроизведение будет продолжено, только если что-то играло на момент поступления команды.</dd>
13
+ </dl>
14
+
15
+ <h4>Отмечайте ударения</h4>
16
+ <p>При необходимости ударные гласные в словах следует отмечать знаком «+», например:</p>
17
+ <ul>
18
+ <li><code>остр+ота</code></li>
19
+ <li><code>м+ука</code></li>
20
+ </ul>
21
+
22
+ <h4>Разделяйте слова</h4>
23
+ <p>Длинные слова можно разбить на слова покороче и проставить ударения для каждого из этих коротких слов, например:</p>
24
+ <ul>
25
+ <li><code>мн+ого пр+офильный</code></li>
26
+ <li><code>с+еми пал+атинск</code></li>
27
+ </ul>
28
+
29
+ <h4>Меняйте написание слов</h4>
30
+ <p>Некоторые слова можно попробовать писать так, как они слышатся:</p>
31
+ <ul>
32
+ <li><code>«ненастный» — нен+асный</code></li>
33
+ <li><code>«пожалуйста» — пож+алуста</code></li>
34
+ </ul>
35
+
36
+ <h4>Добавляйте паузы</h4>
37
+ <p>Чтобы задать паузу между словами, используйте синтаксис sil <[ количество_миллисекунд ]>. Например:</p>
38
+ <ul>
39
+ <li><code>смелость sil <[500]> город+а берёт</code></li>
40
+ </ul>
41
+ <p>Каждый отделенный пробелами пунктуационный знак обозначается паузой в 50-100 мс.</p>
42
+ </script>
@@ -0,0 +1,68 @@
1
+ {
2
+ "label": {
3
+ "name": "Название",
4
+ "text": "Текст",
5
+ "effect": "Эффект",
6
+ "login": "Логин",
7
+ "station": "Станция",
8
+ "voice": "Голос",
9
+ "debug": "Дебаг",
10
+ "command": "Команда",
11
+ "volume": "Громкость",
12
+ "prevent_listening": "Не ждать ответ",
13
+ "pause_while_tts": "Плеер на паузу",
14
+ "default_command": "Команда"
15
+ },
16
+ "placeholder": {
17
+ "name": "Название",
18
+ "play_music": "Включи музыку"
19
+ },
20
+ "command": {
21
+ "player": "Плеер",
22
+ "tts": "Синтез речи из текста",
23
+ "voice": "Голосовая команда",
24
+ "stop_listening": "Перестать слушать",
25
+ "homekit": "Homekit",
26
+ "raw": "Сырая команда"
27
+ },
28
+ "voice": {
29
+ "default": "Алиса (стандартный)",
30
+ "alena": "Алёна (alena)",
31
+ "alyss": "Элис (alyss)",
32
+ "anton_samokhvalov": "Антон Самохвалов (anton_samokhvalov)",
33
+ "dude": "Чувак (dude)",
34
+ "ermil": "Ермил (ermil)",
35
+ "ermilov": "Ermilov (ermilov)",
36
+ "ermil_with_tuning": "Ермил Т (ermil_with_tuning)",
37
+ "erkanyavas": "Ерканявас (erkanyavas)",
38
+ "filipp": "Филипп (filipp)",
39
+ "jane": "Джейн (jane)",
40
+ "kolya": "Коля (kolya)",
41
+ "kostya": "Костя (kostya)",
42
+ "levitan": "Левитан (levitan)",
43
+ "nastya": "Настя (nastya)",
44
+ "nick": "Ник (nick)",
45
+ "oksana": "Оксана (oksana)",
46
+ "omazh": "Омаж (omazh)",
47
+ "robot": "Робот (robot)",
48
+ "sasha": "Саша (sasha)",
49
+ "silaerkan": "Силаеркан (silaerkan)",
50
+ "smoky": "Смоки (smoky)",
51
+ "tanya": "Таня (tanya)",
52
+ "tatyana_abramova": "Татьяна Абрамова (tatyana_abramova)",
53
+ "zahar": "Захар (zahar)",
54
+ "zhenya": "Женя (zhenya)",
55
+ "zombie": "Зомби (zombie)",
56
+ "voicesearch": "voicesearch"
57
+ },
58
+ "effect": {
59
+ "none": "Без эффекта",
60
+ "behind_the_wall": "Из-за стены (behind_the_wall)",
61
+ "hamster": "Хомяк (hamster)",
62
+ "megaphone": "Мегафон (megaphone)",
63
+ "pitch_down": "Низкий (pitch_down)",
64
+ "psychodelic": "Психоделический (psychodelic)",
65
+ "pulse": "С прерыванием (pulse)",
66
+ "train_announce": "Громкоговоритель (train_announce)"
67
+ }
68
+ }
@@ -45,7 +45,7 @@ module.exports = function(RED) {
45
45
  debugMessage(`Ready event fo ${device.id}`);
46
46
  node.emit(`deviceReady`, device);
47
47
  node.readyList.push({ 'name': device.name, 'id': device.id, 'platform': device.platform, 'address': device.address, 'port': device.port, 'host': device.host, 'parameters': device.parameters});
48
- node.emit('refreshHttp', node.activeStationList, node.readyList)
48
+ node.emit('refreshHttp', node.activeStationList, node.readyList);
49
49
  statusUpdate({"color": "yellow", "text": "connecting..."}, device);
50
50
  }
51
51
  }
@@ -122,20 +122,28 @@ module.exports = function(RED) {
122
122
  await mDnsSd.discover({
123
123
  name: '_yandexio._tcp.local'
124
124
  }).then((result) => {
125
- //debugMessage(`Found ${result.length} devices`);
125
+ debugMessage(`MDNS. Found ${result.length} devices`);
126
+ node.emit('refreshHttpDNS', result);
126
127
  if (result.length != 0){
127
128
  for (const device of deviceList) {
128
129
  result.forEach(element => {
129
130
  let checkConnType = device.parameters.network || {}
130
131
  //если режим автоопределения адреса или набор параметров пустой, то записывать значния из результатов mdns поиска
131
132
  if (checkConnType.mode == "auto" || JSON.stringify(checkConnType) == "{}") {
132
- let srvEls = element.packet.answers.find(el => el.type == "SRV");
133
- let txtEls = element.packet.answers.find(el => el.type == "TXT");
133
+ //need remove to REGEXP!
134
+ //let mdnsId = element.fqdn.split("-")[1].split(".")[0];
135
+ let srvEls = element.packet.answers.find(el => el.type == "SRV") || element.packet.additionals.find(el => el.type == "SRV");
136
+ let txtEls = element.packet.answers.find(el => el.type == "TXT") || element.packet.additionals.find(el => el.type == "TXT");
134
137
  if (typeof(txtEls) !== 'undefined' ) {
135
138
  if (txtEls.rdata.deviceId == device.id) {
136
139
  device.address = element.address;
137
140
  device.port = element.service.port;
138
- device.host = srvEls.rdata.target;
141
+ try {
142
+ device.host = srvEls.rdata.target;
143
+ } catch(e) {
144
+ debugMessage(`Error searching hostname in mDNS answer`)
145
+ }
146
+
139
147
  }
140
148
  }
141
149
  }
@@ -200,8 +208,8 @@ module.exports = function(RED) {
200
208
  function connect(device) {
201
209
  //connect only if !device.ws
202
210
  //debugMessage(`device.ws = ${JSON.stringify(device.ws)}`);
203
- if (device.connection == true || typeof(device.connection) == "undefined") {
204
- debugMessage(`Connecting to device ${device.id}. ws is ${JSON.stringify(device.ws)}`)
211
+ 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}`));
205
213
  if (!device.ws) {
206
214
  debugMessage('recieving conversation token...');
207
215
  getLocalToken(device)
@@ -246,7 +254,7 @@ module.exports = function(RED) {
246
254
  //}
247
255
  }
248
256
  } else {
249
- debugMessage(`${device.id} connection is disabled by settings in manager node ${device.manager}`)
257
+ debugMessage(`${device.id} connection is disabled by settings in manager node ${device.manager} or you have not use any node for this station`)
250
258
  statusUpdate({"color": "red", "text": "disconnected"}, device);
251
259
  device.timer = setTimeout(connect, 60000, device);
252
260
 
@@ -275,11 +283,13 @@ module.exports = function(RED) {
275
283
  device.watchDog = setTimeout(() => device.ws.close(), 10000);
276
284
  device.pingInterval = setInterval(onPing,300,device);
277
285
  clearTimeout(device.timer);
286
+ debugMessage(`readyState: ${device.ws.readyState}`)
278
287
  });
279
288
  device.ws.on('message', function incoming(data) {
280
289
  //debugMessage(`${device.id}: ${JSON.stringify(data)}`);
281
290
  let dataRecieved = JSON.parse(data);
282
291
  device.lastState = dataRecieved.state;
292
+ device.fullMessage = JSON.stringify(dataRecieved);
283
293
  //debugMessage(checkSheduler(device, JSON.parse(data).sentTime));
284
294
  node.emit(`message_${device.id}`, device.lastState);
285
295
  if (device.lastState.aliceState == 'LISTENING' && device.waitForListening) {node.emit(`stopListening`, device)}
@@ -313,6 +323,7 @@ module.exports = function(RED) {
313
323
  //device.ws.on('ping', function);
314
324
  device.ws.on('close', function close(code, reason){
315
325
  statusUpdate({"color": "red", "text": "disconnected"}, device);
326
+ //debugMessage(`readyState: ${device.ws.readyState}`)
316
327
  device.lastState = {};
317
328
  clearTimeout(device.watchDog);
318
329
  switch(code) {
@@ -499,8 +510,8 @@ module.exports = function(RED) {
499
510
  return result;
500
511
  break;
501
512
  case 'homekit':
502
- debugMessage('HAP: ' + JSON.stringify(message.hap.context) + ' PL: ' + JSON.stringify(message.payload) );
503
- if (message.hap.context != undefined) {
513
+ debugMessage('HAP: ' + JSON.stringify(message) + ' PL: ' + JSON.stringify(message.payload) );
514
+ if ("session" in message.hap) {
504
515
  switch(JSON.stringify(message.payload)){
505
516
  case '{"TargetMediaState":1}':
506
517
  case '{"Active":0}':
@@ -758,6 +769,12 @@ module.exports = function(RED) {
758
769
  res.json({"devices": activeList});
759
770
  });
760
771
  });
772
+
773
+ node.on('refreshHttpDNS', function(dnsList) {
774
+ RED.httpAdmin.get("/mdns/"+node.id, RED.auth.needsPermission('yandex-login.read'), function(req,res) {
775
+ res.json({"SearchResult": dnsList});
776
+ });
777
+ });
761
778
  node.on('close', onClose)
762
779
 
763
780
 
@@ -772,7 +789,7 @@ module.exports = function(RED) {
772
789
  let device = searchDeviceByID(id);
773
790
  if (device) {
774
791
 
775
- 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});
792
+ 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 });
776
793
  } else {
777
794
  res.json({"error": 'no device found'});
778
795
  }
@@ -780,8 +797,9 @@ module.exports = function(RED) {
780
797
 
781
798
  // main init
782
799
  if (typeof(node.token) !== 'undefined') {
800
+ debugMessage(`Starting server with id ${node.id}`)
783
801
  getDevices(node.token);
784
- node.interval = setInterval(getDevices, 300000, node.token);
802
+ node.interval = setInterval(getDevices, 60000, node.token);
785
803
  }
786
804
 
787
805
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-yandex-station-management",
3
- "version": "0.2.6",
3
+ "version": "0.3.2",
4
4
  "description": "Local management of YandexStation using API on websockets",
5
5
  "main": "index.js",
6
6
  "scripts": {