node-red-contrib-yandex-station-management 0.2.5 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -7
- package/nodes/get.js +25 -18
- package/nodes/in.js +30 -18
- package/nodes/local-out.html +236 -134
- package/nodes/local-out.js +100 -11
- package/nodes/locales/en-US/local-out.html +45 -0
- package/nodes/locales/en-US/local-out.json +71 -0
- package/nodes/locales/ru-RU/local-out.html +42 -0
- package/nodes/locales/ru-RU/local-out.json +68 -0
- package/nodes/yandex-login.html +102 -9
- package/nodes/yandex-login.js +41 -14
- package/package.json +1 -1
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
|
|
|
@@ -13,7 +15,10 @@
|
|
|
13
15
|
- находятся в одной локальной сети с сервером Node-Red
|
|
14
16
|
|
|
15
17
|
|
|
16
|
-
Для работы требуется токен от Яндекс.Музыки.
|
|
18
|
+
Для работы требуется токен от Яндекс.Музыки.
|
|
19
|
+
В модуле в экспериментальном режиме реализована возможность получения токена из логин-пароля(Спасибо слать [сюда](https://github.com/twocolors)). Если получение токена не отрабатывает, то стоит попробовать включить и отключить двух-факторную аутентификацию в настройках Яндекса. [Источник](https://github.com/AlexxIT/YandexStation/issues/103). Убедиться в безопасности использования учетных данных можно, посмотрев [код](./nodes/yandex-login.html)
|
|
20
|
+
|
|
21
|
+
Второй из варинатов его получения описан в [FAQ](#faq)
|
|
17
22
|
|
|
18
23
|
Возможна работа с несколькими устройствами(протестировано) и несколькими учетными записями(протестировано).
|
|
19
24
|
|
|
@@ -33,7 +38,9 @@
|
|
|
33
38
|
## Первоначальная настройка.
|
|
34
39
|
После установки для начала работы добавить любую ноду, ввести учетные данные(токен) в раздел Login, сохранить и нажать Deploy(обязательно!). Как получить токен - написано в FAQ.
|
|
35
40
|
|
|
36
|
-
После деплоя в настройках ноды в поле Station должны появиться станции доступные для управления.
|
|
41
|
+
После деплоя в настройках ноды в поле Station должны появиться станции доступные для управления.
|
|
42
|
+
|
|
43
|
+
npmЕсли станция не появилась в списке, то можно подождать пару минут или перезапустить Node-Red.
|
|
37
44
|
|
|
38
45
|
## Описание возможностей и сценариев использования.
|
|
39
46
|
### Нода Station
|
|
@@ -106,13 +113,53 @@ Phrase to say - фраза, которую скажет Алиса вместо
|
|
|
106
113
|
Отправка команды, вместо того, чтобы говорить ее колонке голосом: "Включи свет", "Включи музыку", "Включи мой плейлист", "Отключись через 15 минут" и так далее.
|
|
107
114
|
|
|
108
115
|
#### TTS.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
-
|
|
112
|
-
-
|
|
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
|
|
113
130
|
Все опции комбинируемы между собой.
|
|
114
131
|
|
|
115
|
-
|
|
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
|
+
|
|
116
163
|
|
|
117
164
|
|
|
118
165
|
#### Homekit Formatted.
|
package/nodes/get.js
CHANGED
|
@@ -10,36 +10,43 @@ 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
|
-
|
|
22
|
+
inputMsg.payload = message;
|
|
23
23
|
} else if (node.output == 'homekit') {
|
|
24
24
|
if (node.homekitFormat == 'speaker') {
|
|
25
|
-
(message.playerState)?
|
|
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 = {
|
|
26
33
|
"CurrentMediaState": (message.playing) ? 0 : 1,
|
|
27
|
-
"ConfiguredName":
|
|
28
|
-
}
|
|
34
|
+
"ConfiguredName": ConfiguredName
|
|
35
|
+
} :inputMsg.payload = {
|
|
29
36
|
"CurrentMediaState": (message.playing) ? 0 : 1,
|
|
30
|
-
"ConfiguredName": `No Artist - No Track`
|
|
31
|
-
}
|
|
37
|
+
"ConfiguredName": `No Artist - No Track Name`
|
|
38
|
+
}
|
|
32
39
|
}else if (node.homekitFormat == 'tv') {
|
|
33
|
-
payload = {
|
|
40
|
+
inputMsg.payload = {
|
|
34
41
|
"Active": (message.playing) ? 1 : 0
|
|
35
|
-
}
|
|
36
|
-
|
|
42
|
+
}
|
|
43
|
+
|
|
37
44
|
}
|
|
38
45
|
}
|
|
39
|
-
return
|
|
46
|
+
return inputMsg;
|
|
40
47
|
|
|
41
48
|
}
|
|
42
|
-
|
|
49
|
+
|
|
43
50
|
|
|
44
51
|
node.onStatus = function(data) {
|
|
45
52
|
if (data) {
|
|
@@ -47,9 +54,9 @@ module.exports = function(RED) {
|
|
|
47
54
|
//node.log('new status ' + data)
|
|
48
55
|
}
|
|
49
56
|
}
|
|
50
|
-
node.onInput = function(){
|
|
51
|
-
debugMessage('
|
|
52
|
-
( 'aliceState' in node.lastState )?node.send(preparePayload(node.lastState)):node.send(
|
|
57
|
+
node.onInput = function(msg, send, done){
|
|
58
|
+
debugMessage('current state: ' + JSON.stringify(node.lastState));
|
|
59
|
+
( 'aliceState' in node.lastState )?node.send(preparePayload(node.lastState,msg)):node.send(msg)
|
|
53
60
|
}
|
|
54
61
|
node.onMessage = function(message){
|
|
55
62
|
node.lastState = message;
|
|
@@ -57,7 +64,7 @@ module.exports = function(RED) {
|
|
|
57
64
|
node.onClose = function(){
|
|
58
65
|
node.controller.removeListener(`message_${node.station}`, node.onMessage)
|
|
59
66
|
}
|
|
60
|
-
|
|
67
|
+
|
|
61
68
|
node.on('input', node.onInput);
|
|
62
69
|
|
|
63
70
|
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
|
-
|
|
30
|
-
(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|