node-red-contrib-yandex-station-management 0.3.6 → 0.3.8

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
@@ -5,8 +5,8 @@
5
5
  - Яндекс Станция мини 2 с экраном(протестировано)
6
6
  - Яндекс Станция лайт(протестировано)
7
7
  - Яндекс Станци Макс(протестировано)
8
- - Яндекс Модуль(не протестировано)
9
- - Яндекс Модуль - 2 (в процессе тестирования)
8
+ - Яндекс Модуль( протестировано)
9
+ - Яндекс Модуль - 2 (протестировано)
10
10
  - JBL Link Music(не протестировано)
11
11
  - JBL Link Portable(протестировано)
12
12
 
@@ -16,7 +16,7 @@
16
16
  - находятся в одной локальной сети с сервером Node-Red
17
17
 
18
18
 
19
- Для работы требуется токен от Яндекс.Музыки.
19
+ Для работы требуется токен от Яндекс.Музыки.
20
20
  В модуле в экспериментальном режиме реализована возможность получения токена из логин-пароля(Спасибо слать [сюда](https://github.com/twocolors)). Если получение токена не отрабатывает, то стоит попробовать включить и отключить двух-факторную аутентификацию в настройках Яндекса. [Источник](https://github.com/AlexxIT/YandexStation/issues/103). Убедиться в безопасности использования учетных данных можно, посмотрев [код](./nodes/yandex-login.html)
21
21
 
22
22
  Второй из варинатов его получения описан в [FAQ](#faq)
@@ -39,7 +39,7 @@
39
39
  ## Первоначальная настройка.
40
40
  После установки для начала работы добавить любую ноду, ввести учетные данные(токен) в раздел Login, сохранить и нажать Deploy(обязательно!). Как получить токен - написано в FAQ.
41
41
 
42
- После деплоя в настройках ноды в поле Station должны появиться станции доступные для управления.
42
+ После деплоя в настройках ноды в поле Station должны появиться станции доступные для управления.
43
43
 
44
44
  Если станция не появилась в списке, то можно подождать пару минут или перезапустить Node-Red.
45
45
 
@@ -114,7 +114,7 @@ Phrase to say - фраза, которую скажет Алиса вместо
114
114
  Отправка команды, вместо того, чтобы говорить ее колонке голосом: "Включи свет", "Включи музыку", "Включи мой плейлист", "Отключись через 15 минут" и так далее.
115
115
 
116
116
  #### TTS.
117
- Воспроизведение голосом отправленных фраз - Text to Speech. Не имеет ограничения по символам.
117
+ Воспроизведение голосом отправленных фраз - Text to Speech. Не имеет ограничения по символам.
118
118
  Параметры для TTS могут задаваться как в настройках, так и некоторые из них могут быть переопределены входящим сообщением.
119
119
  - Text. Откуда и какой текст проговорить.
120
120
  - из входящего сообщения (msg.payload по-умолчанию)
@@ -123,7 +123,7 @@ Phrase to say - фраза, которую скажет Алиса вместо
123
123
  - JSON: выбрать сообщение случайным образом из массива вида ["один", "два", "три"].
124
124
  - Voice. Выбор голоса для генерации. Может быть переопределено через msg.voice
125
125
  - Effect. Эффекты для генерации голоса. Может быть переопределено через msg.effect
126
- -
126
+ -
127
127
  Есть ряд опций:
128
128
  - Volume. Позволяет произносить фразу заданной громкостью. Если не выбрано, то фраза произносится с текущим уровнем громкости. После произнесения уровень громкости вернется в изначальный. Может быть переопределено через msg.volume
129
129
  - Whisper. Позволяет произнести фразу шептом.. Переопределяется через msg.whisper
@@ -236,7 +236,36 @@ Phrase to say - фраза, которую скажет Алиса вместо
236
236
  "volume" : 0.2
237
237
  }
238
238
  ```
239
- 10. Отправить "Текст" для TTS.
239
+ 10. Включить радио
240
+ ```json
241
+ {
242
+ "command": "playRadio",
243
+ "id": "detskoe"
244
+ }
245
+ ```
246
+ 11. Режим повтора. "One"/"All"/"None"
247
+ ```json
248
+ {
249
+ "command": "repeat",
250
+ "mode": "One"
251
+ }
252
+ ```
253
+ 12. Режим вразброс. Срабатывает, когда есть очередь треков(включен плейоист, альбом, артист) true/false
254
+ ```json
255
+ {
256
+ "command": "shuffle",
257
+ "enable": true
258
+ }
259
+ ```
260
+ 13. Принудительно включить режим индикации Алисы - занято, слушаю, простой "LISTENING"/"BUSY"/"IDLE"
261
+ ```json
262
+ {
263
+ "command": "showAliceVisualState",
264
+ "aliceStateName": "LISTENING",
265
+ "recognizedPhrase": ""
266
+ }
267
+ ```
268
+ 14. Отправить "Текст" для TTS.
240
269
  Больше не работает!
241
270
  ```json
242
271
  {
@@ -244,14 +273,14 @@ Phrase to say - фраза, которую скажет Алиса вместо
244
273
  "text" : "Повторяй за мной 'Текст'"
245
274
  }
246
275
  ```
247
- 11. Отправить голосовую команду.
276
+ 15. Отправить голосовую команду.
248
277
  ```json
249
278
  {
250
279
  "command" : "sendText",
251
280
  "text" : "Включи музыку"
252
281
  }
253
282
  ```
254
- 12. Прервать "слушание" после TTS и не только:
283
+ 16. Прервать "слушание" после TTS и не только:
255
284
  ```json
256
285
  {
257
286
  "command": "serverAction",
@@ -262,7 +291,7 @@ Phrase to say - фраза, которую скажет Алиса вместо
262
291
  }
263
292
  ```
264
293
 
265
- 13. Отправить "Текст" для TTS со спецэффектами (**raw режим**):
294
+ 17. Отправить "Текст" для TTS со спецэффектами (**raw режим**):
266
295
  ```json
267
296
  {
268
297
  "command": "serverAction",
@@ -314,7 +343,53 @@ Phrase to say - фраза, которую скажет Алиса вместо
314
343
  ```json
315
344
  "value": "<speaker voice='kostya' audio='alice-sounds-game-win-1.opus' effect='megaphone'>добро пожаловать"
316
345
  ```
317
-
346
+ 18. Приветствие как в автомобиле. Кратко скажет погоду и пробки
347
+ ```json
348
+ {
349
+ "command": "serverAction",
350
+ "serverActionEventPayload": {
351
+ "type": "server_action",
352
+ "name": "update_form",
353
+ "payload": {
354
+ "form_update": {
355
+ "name": "personal_assistant.automotive.greeting"
356
+ },
357
+ "resubmit": true
358
+ }
359
+ }
360
+ }
361
+ ```
362
+ 19. Включить и выключить блютуз
363
+ ```json
364
+ {
365
+ "command": "serverAction",
366
+ "serverActionEventPayload": {
367
+ "type": "server_action",
368
+ "name": "update_form",
369
+ "payload": {
370
+ "form_update": {
371
+ "name": "personal_assistant.scenarios.bluetooth_on"
372
+ },
373
+ "resubmit": true
374
+ }
375
+ }
376
+ }
377
+ ```
378
+ ```json
379
+ {
380
+ "command": "serverAction",
381
+ "serverActionEventPayload": {
382
+ "type": "server_action",
383
+ "name": "update_form",
384
+ "payload": {
385
+ "form_update": {
386
+ "name": "personal_assistant.scenarios.bluetooth_off"
387
+ },
388
+ "resubmit": true
389
+ }
390
+ }
391
+ }
392
+ ```
318
393
 
319
394
  #### Stop listening.
320
395
 
@@ -370,7 +445,7 @@ Phrase to say - фраза, которую скажет Алиса вместо
370
445
  ![alt text](/readme_images/dashboardPlayer.png "player")
371
446
  ![alt text](/readme_images/dashboardPlayerFlow.png "player")
372
447
 
373
- Есть еще один вариант от сообщества, который надо самостоятельно импортировать [со страницы автора](https://github.com/twocolors/node-red-dashboard-template/blob/main/alice_v2.json)
448
+ Есть еще один вариант от [@twocolors](https://github.com/twocolors), в примерах.
374
449
 
375
450
  Добавляется простым flow и выглядит отлично)
376
451
 
@@ -0,0 +1,45 @@
1
+ [
2
+ {
3
+ "id": "db7d25d3.9110a8",
4
+ "type": "ui_template",
5
+ "z": "0c069b6a05278757",
6
+ "group": "8865f91.bf77188",
7
+ "name": "",
8
+ "order": 1,
9
+ "width": "8",
10
+ "height": "8",
11
+ "format": "<style>\n .yandex-player {\n height: 100%;\n position: relative;\n min-height: 128px;\n z-index: 1;\n background: url(\"//avatars.mds.yandex.net/get-music-misc/29541/img.5e6a1c5b38be6e3bae26558a/600x600\");\n background-repeat: no-repeat;\n background-position: center;\n background-size: cover;\n }\n\n .yandex-player .gradient {\n background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.75));\n position: absolute;\n bottom: 0;\n left: 0;\n }\n\n .yandex-player .controls {\n margin: 24px 32px;\n color: rgba(255, 255, 255, 0.75);\n font-family: Roboto, Noto, sans-serif;\n -moz-osx-font-smoothing: grayscale;\n -webkit-font-smoothing: antialiased;\n font-weight: lighter;\n }\n\n .yandex-player .title {\n font-size: larger;\n }\n\n .yandex-player .subtitle {\n font-size: medium;\n }\n\n .yandex-player .ui {\n padding: 16px 0 24px 0;\n }\n\n .yandex-player .ui i {\n cursor: pointer;\n }\n\n .yandex-player .ui i:hover {\n color: rgba(255, 255, 255, 1);\n }\n\n .yandex-player .ui-btn {\n float: left;\n display: flex;\n justify-content: space-between;\n width: 20%;\n }\n\n .yandex-player .ui-volume {\n float: right;\n display: flex;\n justify-content: space-between;\n width: 80%;\n vertical-align: middle;\n }\n\n .yandex-player .slider {\n position: relative;\n cursor: pointer;\n width: 100%;\n margin: 0 16px;\n opacity: 1;\n display: inline-block;\n }\n\n .yandex-player .slider_base {\n width: 100%;\n height: 2px;\n background-color: rgba(255, 255, 255, 0.75);\n border-radius: 2px;\n position: absolute;\n top: 8px;\n }\n\n .yandex-player .slider_progress {\n height: 4px;\n background-color: rgba(255, 255, 255, 1);\n border-radius: 2px;\n position: absolute;\n top: 7px;\n }\n\n .yandex-player .bar {\n position: absolute;\n cursor: pointer;\n width: 100%;\n opacity: 1;\n bottom: 0;\n height: 6px;\n background-color: rgba(255, 255, 255, 0.75);\n }\n\n .yandex-player .bar_progress {\n width: 0;\n height: 100%;\n background-color: #fc0;\n }\n\n .yandex-player .red {\n color: red;\n }\n</style>\n<div class=\"yandex-player\">\n <div class=\"gradient\" style=\"min-height: 128px; width: 100%;\">\n <div class=\"controls\">\n <div class=\"title\"></div>\n <div class=\"subtitle\"></div>\n <div class=\"ui\">\n <div class=\"ui-btn\">\n <i class=\"fa fa-step-backward fa-lg\" aria-hidden=\"true\"></i>\n <i class=\"btn-play-pause fa fa-play fa-lg\" aria-hidden=\"true\"></i>\n <i class=\"fa fa-step-forward fa-lg\" aria-hidden=\"true\"></i>\n </div>\n\n <div class=\"ui-volume\">\n <div class=\"slider\">\n <div class=\"slider_base\"></div>\n <div class=\"slider_progress\"></div>\n </div>\n <div style=\"width: 10%;\">\n <i class=\"btn-volume fa fa-volume-up fa-lg\" aria-hidden=\"true\"></i>\n </div>\n <div style=\"width: 10%; text-align: right; margin-left: 16px;\">\n <i class=\"fa fa-heart fa-lg\" aria-hidden=\"true\"></i>\n </div>\n </div>\n </div>\n </div>\n <div class=\"bar\">\n <div class=\"bar_progress\"></div>\n </div>\n </div>\n</div>\n<script>\n /*! js-cookie v3.0.0-rc.1 | MIT */\n !function (e, t) { \"object\" == typeof exports && \"undefined\" != typeof module ? module.exports = t() : \"function\" == typeof define && define.amd ? define(t) : (e = e || self, function () { var n = e.Cookies, r = e.Cookies = t(); r.noConflict = function () { return e.Cookies = n, r } }()) }(this, function () { \"use strict\"; function e(e) { for (var t = 1; t < arguments.length; t++) { var n = arguments[t]; for (var r in n) e[r] = n[r] } return e } var t = { read: function (e) { return e.replace(/(%[\\dA-F]{2})+/gi, decodeURIComponent) }, write: function (e) { return encodeURIComponent(e).replace(/%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g, decodeURIComponent) } }; return function n(r, o) { function i(t, n, i) { if (\"undefined\" != typeof document) { \"number\" == typeof (i = e({}, o, i)).expires && (i.expires = new Date(Date.now() + 864e5 * i.expires)), i.expires && (i.expires = i.expires.toUTCString()), t = encodeURIComponent(t).replace(/%(2[346B]|5E|60|7C)/g, decodeURIComponent).replace(/[()]/g, escape), n = r.write(n, t); var c = \"\"; for (var u in i) i[u] && (c += \"; \" + u, !0 !== i[u] && (c += \"=\" + i[u].split(\";\")[0])); return document.cookie = t + \"=\" + n + c } } return Object.create({ set: i, get: function (e) { if (\"undefined\" != typeof document && (!arguments.length || e)) { for (var n = document.cookie ? document.cookie.split(\"; \") : [], o = {}, i = 0; i < n.length; i++) { var c = n[i].split(\"=\"), u = c.slice(1).join(\"=\"); '\"' === u[0] && (u = u.slice(1, -1)); try { var f = t.read(c[0]); if (o[f] = r.read(u, f), e === f) break } catch (e) { } } return e ? o[e] : o } }, remove: function (t, n) { i(t, \"\", e({}, n, { expires: -1 })) }, withAttributes: function (t) { return n(this.converter, e({}, this.attributes, t)) }, withConverter: function (t) { return n(e({}, this.converter, t), this.attributes) } }, { attributes: { value: Object.freeze(o) }, converter: { value: Object.freeze(r) } }) }(t, { path: \"/\" }) });\n</script>\n<script>\n (function (scope) {\n // function\n function setVolume(volume) {\n //console.log('setVolume:' + volume);\n\n if (volume <= 4.5) {\n volume = 0;\n } else if (volume > 4.5 && volume < 10) {\n volume = 10;\n } else if (volume >= 95.5) {\n volume = 100;\n }\n\n let toggle = volume > 0 ? true : false;\n $('.btn-volume').toggleClass('fa-volume-up', toggle).toggleClass('fa-volume-off', !toggle);\n\n scope.send({ 'payload': { 'command': 'setVolume', 'volume': volume / 100 } });\n }\n\n function setProgress(playerState) {\n let progress = (playerState.progress / playerState.duration) * 100;\n //$('.bar_progress').animate({ width: progress + '%' }, 'fast');\n $('.bar_progress').width(progress + '%');\n }\n\n function setLike(id) {\n let like = JSON.parse(Cookies.get('yandex-like') || '{}');\n like[id] = id;\n Cookies.set('yandex-like', JSON.stringify(like), { expires: 3 });\n $('.fa-heart').addClass('red');\n\n scope.send({ payload: { 'command': 'sendText', 'text': 'поставь лайк' } });\n }\n\n function setDislike(id) {\n let like = JSON.parse(Cookies.get('yandex-like') || '{}');\n if (like[id] !== undefined) {\n delete like[id];\n }\n Cookies.set('yandex-like', JSON.stringify(like), { expires: 3 });\n $('.fa-heart').removeClass('red');\n\n scope.send({ payload: { 'command': 'sendText', 'text': 'поставь дизлайк' } });\n }\n\n // init+update\n scope.$watch('msg', function (msg) {\n if (msg) {\n\n if ('volume' in msg.payload) {\n $('.slider_progress').animate({width: msg.payload.volume * 100 + '%'}, 150);\n }\n\n if (msg.payload.playing === true || msg.payload.playing === false) {\n $('.btn-play-pause').toggleClass('fa-pause', msg.payload.playing).toggleClass('fa-play', !msg.payload.playing);\n }\n\n if ('playerState' in msg.payload) {\n //update title\n $('.title').text(msg.payload.playerState.title);\n $('.subtitle').text(msg.payload.playerState.subtitle);\n\n //update img\n let thumb = 'avatars.mds.yandex.net/get-music-misc/29541/img.5e6a1c5b38be6e3bae26558a/%%';\n if (msg.payload.playerState.extra !== null && typeof msg.payload.playerState.extra.coverURI !== \"undefined\") {\n thumb = msg.payload.playerState.extra.coverURI;\n }\n thumb = '//' + thumb.replace(/%%/g, \"600x600\");\n\n if (thumb != $('.yandex-player').attr('data-image')) {\n $('.yandex-player').css('background-image', 'url(' + thumb + ')').attr('data-image', thumb);\n }\n\n setProgress(msg.payload.playerState);\n\n let id = msg.payload.playerState.id;\n if (id != $('.fa-heart').attr('data-id')) {\n $('.fa-heart').removeClass('red').attr('data-id', id);\n let like = JSON.parse(Cookies.get('yandex-like') || '{}');\n if (like[id]) {\n $('.fa-heart').addClass('red');\n }\n }\n }\n }\n });\n\n // controls\n $('.btn-play-pause').click(function () {\n scope.send({ 'payload': { 'command': scope.msg.payload.playing ? 'stop' : 'play' } });\n });\n\n $('.fa-step-backward').click(function () {\n scope.send({ 'payload': { 'command': 'prev' } });\n });\n\n $('.fa-step-forward').click(function () {\n scope.send({ 'payload': { 'command': 'next' } });\n });\n\n // slider volume\n $('.slider').on('click', function (event) {\n let left = $('.slider').offset().left;\n let width = $('.slider').outerWidth();\n\n let volume = (event.clientX - left) / width * 100;\n\n setVolume(volume);\n });\n\n // volume\n $('.btn-volume').on('click', function () {\n if ($(this).hasClass('fa-volume-up')) {\n Cookies.set('yandex-volume-save', $('.slider_progress').width(), { expires: 7 });\n setVolume(0);\n } else if ($(this).hasClass('fa-volume-off')) {\n setVolume(Cookies.get('yandex-volume-save'));\n }\n });\n\n // bar\n $('.bar').on('click', function (event) {\n let left = $('.bar').offset().left;\n let width = $('.bar').outerWidth();\n\n let percent = (event.clientX - left) / width * 100;\n let position = Math.round(percent * (scope.msg.payload.playerState.duration / 100));\n\n scope.send({ 'payload': { 'command': 'rewind', 'position': position } });\n if ($('.btn-play-pause').hasClass('fa-play')) {\n scope.send({ 'payload': { 'command': 'play' } });\n }\n });\n\n //like\n $('.fa-heart').on('click', function () {\n let id = $(this).attr('data-id');\n if (!$(this).hasClass('red')) {\n setLike(id);\n } else {\n setDislike(id);\n }\n });\n\n })(scope);\n</script>",
12
+ "storeOutMessages": true,
13
+ "fwdInMessages": false,
14
+ "resendOnRefresh": true,
15
+ "templateScope": "local",
16
+ "className": "",
17
+ "x": 400,
18
+ "y": 260,
19
+ "wires": [
20
+ [
21
+ "ae2a51d2fe666ff9"
22
+ ]
23
+ ]
24
+ },
25
+ {
26
+ "id": "8865f91.bf77188",
27
+ "type": "ui_group",
28
+ "name": "Alice",
29
+ "tab": "bd17abae.b2461",
30
+ "order": 1,
31
+ "disp": true,
32
+ "width": "8",
33
+ "collapse": false,
34
+ "className": ""
35
+ },
36
+ {
37
+ "id": "bd17abae.b2461",
38
+ "type": "ui_tab",
39
+ "name": "Кубик³",
40
+ "icon": "dashboard",
41
+ "order": 1,
42
+ "disabled": false,
43
+ "hidden": false
44
+ }
45
+ ]
@@ -94,7 +94,8 @@ module.exports = function(RED) {
94
94
  if (bufferStation) {
95
95
  let result = registerDevice(bufferStation.id, bufferStation.manager, bufferStation.parameters)
96
96
  if (result != 2 && result != undefined) {
97
- registrationBuffer.splice(registrationBuffer.indexOf(bufferStation,1));
97
+ //https://github.com/n0name45/node-red-contrib-yandex-station-management/issues/20#issuecomment-1373709408
98
+ //registrationBuffer.splice(registrationBuffer.indexOf(bufferStation,1));
98
99
  }
99
100
 
100
101
  }
@@ -291,7 +292,9 @@ module.exports = function(RED) {
291
292
  device.waitForListening = false;
292
293
  device.playAfterTTS = false;
293
294
  device.waitForIdle = false;
294
- device.watchDog = setTimeout(() => device.ws.close(), 10000);
295
+ device.watchDog = setTimeout(() => {
296
+ if (typeof(device) != 'undefined' && typeof(device.ws) != 'undefined') {device.ws.close()}
297
+ }, 10000);
295
298
  device.pingInterval = setInterval(onPing,300,device);
296
299
  debugMessage(`${device.id}: Kill connection watchdog`);
297
300
  clearTimeout(device.watchDogConn);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-yandex-station-management",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "description": "Local management of YandexStation using API on websockets",
5
5
  "main": "index.js",
6
6
  "scripts": {