iobroker.fakeroku 1.7.0 → 1.8.0
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 +42 -34
- package/admin/i18n/de.json +3 -3
- package/admin/i18n/en.json +3 -3
- package/admin/i18n/es.json +4 -4
- package/admin/i18n/fr.json +3 -3
- package/admin/i18n/it.json +3 -3
- package/admin/i18n/nl.json +3 -3
- package/admin/i18n/pl.json +4 -4
- package/admin/i18n/pt.json +3 -3
- package/admin/i18n/ru.json +3 -3
- package/admin/i18n/uk.json +3 -3
- package/admin/i18n/zh-cn.json +4 -4
- package/build/device-management.js +87 -51
- package/build/device-management.js.map +2 -2
- package/build/discovery/ssdp-messages.js +18 -4
- package/build/discovery/ssdp-messages.js.map +2 -2
- package/build/discovery/ssdp-responder.js +180 -84
- package/build/discovery/ssdp-responder.js.map +2 -2
- package/build/ecp/device-info.js +32 -3
- package/build/ecp/device-info.js.map +2 -2
- package/build/ecp/ecp-command.js +2 -2
- package/build/ecp/ecp-command.js.map +2 -2
- package/build/ecp/ecp-http-server.js +46 -18
- package/build/ecp/ecp-http-server.js.map +3 -3
- package/build/ecp/state-model.js +19 -6
- package/build/ecp/state-model.js.map +2 -2
- package/build/lib/detect-ip.js +105 -49
- package/build/lib/detect-ip.js.map +3 -3
- package/build/lib/device-config.js +68 -12
- package/build/lib/device-config.js.map +2 -2
- package/build/lib/device-identity.js +5 -0
- package/build/lib/device-identity.js.map +2 -2
- package/build/lib/errors.js +32 -2
- package/build/lib/errors.js.map +2 -2
- package/build/lib/lan-guard.js +6 -19
- package/build/lib/lan-guard.js.map +2 -2
- package/build/lib/native-key-migration.js +16 -5
- package/build/lib/native-key-migration.js.map +2 -2
- package/build/lib/pure-helpers.js +6 -2
- package/build/lib/pure-helpers.js.map +2 -2
- package/build/main.js +297 -64
- package/build/main.js.map +3 -3
- package/io-package.json +64 -64
- package/package.json +8 -13
package/README.md
CHANGED
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
**Support:** [](https://ko-fi.com/krobipd) [](https://paypal.me/krobipd)
|
|
8
8
|
|
|
9
|
-
Emulates one or more **[Roku](https://www.roku.com/) devices** on your LAN so that ECP/SSDP remotes
|
|
10
|
-
Logitech Harmony Hub
|
|
11
|
-
ioBroker. It is the **input** counterpart to the Logitech
|
|
12
|
-
on the remote becomes a datapoint in ioBroker.
|
|
9
|
+
Emulates one or more **[Roku](https://www.roku.com/) devices** on your LAN so that ECP/SSDP remotes and
|
|
10
|
+
controllers — a Logitech Harmony Hub, a Sofabaton X1/X2, Home Assistant's Roku integration,
|
|
11
|
+
openHAB — can trigger events in ioBroker. It is the **input** counterpart to the Logitech
|
|
12
|
+
Harmony adapter: a button on the remote becomes a datapoint in ioBroker.
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
Besides the key presses it answers the queries these controllers make before and while
|
|
15
|
+
they use a Roku — device info, app list, active app, media player, TV channels, app icons —
|
|
16
|
+
so they accept the emulated device instead of giving up on it.
|
|
17
17
|
|
|
18
18
|
> **The official Roku mobile app is not supported.** It drives Rokus over Roku's
|
|
19
19
|
> proprietary, undocumented ECP-2 WebSocket channel, which this emulator does not
|
|
@@ -23,9 +23,9 @@ classic Harmony hub.
|
|
|
23
23
|
## Features
|
|
24
24
|
|
|
25
25
|
- Emulates one or more Roku devices on the LAN — the Roku control protocol (ECP) over HTTP plus SSDP discovery on port 1900.
|
|
26
|
-
-
|
|
26
|
+
- Answers the Roku queries controllers rely on: device info, apps, active app, media player, TV channels and app icons.
|
|
27
27
|
- Clean data model per device: a `command` datapoint plus fixed `keys.<Key>` states, all created up front.
|
|
28
|
-
- Several emulated Rokus from a single instance;
|
|
28
|
+
- Several emulated Rokus from a single instance; with "all interfaces" every network of the host is served with the host's own address in it, a chosen interface keeps everything in its network; only clients from the host's own networks are answered, capped at 25 commands per second per emulated Roku, so a misbehaving device cannot flood ioBroker. Releasing a key you are holding is never dropped by that cap.
|
|
29
29
|
|
|
30
30
|
## Sentry / Error reporting
|
|
31
31
|
|
|
@@ -43,21 +43,25 @@ For details and how to disable it, see the [Sentry plugin documentation](https:/
|
|
|
43
43
|
|
|
44
44
|
## Ports
|
|
45
45
|
|
|
46
|
-
- **TCP 8060 (listening, one per emulated Roku, configurable)** — the Roku control protocol (ECP): the remote sends its key presses here
|
|
46
|
+
- **TCP 8060 (listening, one per emulated Roku, configurable)** — the Roku control protocol (ECP): the remote sends its key presses here. A Harmony or Sofabaton reads the port from the discovery announcement; Home Assistant and Homey always use 8060.
|
|
47
47
|
- **UDP 1900 (multicast, listening and outgoing)** — SSDP discovery, so a Harmony hub or Sofabaton finds the emulated Rokus; fixed by the UPnP standard.
|
|
48
48
|
|
|
49
49
|
## Configuration
|
|
50
50
|
|
|
51
|
-
- **Network interface** —
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
network
|
|
51
|
+
- **Network interface** — leave it on "all interfaces" and the adapter runs out of the
|
|
52
|
+
box: every network of the host is served, each with the host's own address in it.
|
|
53
|
+
Pick a specific address only if the emulated Rokus should exist in one network only —
|
|
54
|
+
then everything stays in that network. An address the host does not carry is waited
|
|
55
|
+
for up to two minutes at start-up, then reported; the adapter never falls back to
|
|
56
|
+
another network.
|
|
55
57
|
- **Emulated Roku devices** — managed as cards: **+ Add** opens a dialog with a
|
|
56
58
|
**name**, an **ECP port** (`8060` the real Roku port; a free one is pre-selected,
|
|
57
|
-
and the dialog
|
|
58
|
-
emulate several Rokus from one instance — each needs its own port.
|
|
59
|
+
and the dialog does not let you confirm a name or port already in use) and a **type**.
|
|
60
|
+
You can emulate several Rokus from one instance — each needs its own port. Renaming a
|
|
61
|
+
card keeps its datapoints, their room and function assignments and their history
|
|
62
|
+
settings.
|
|
59
63
|
- **Type** — *Player* (default) exposes the 16 standard navigation and playback keys;
|
|
60
|
-
*TV* additionally exposes volume, channel and input keys
|
|
64
|
+
*TV* additionally exposes volume, power, channel and input keys. Choose *TV* only if
|
|
61
65
|
you want those extra keys as ioBroker triggers.
|
|
62
66
|
|
|
63
67
|
To add the emulated Roku to a Harmony hub, add a "Roku" device in the Harmony app
|
|
@@ -77,10 +81,12 @@ For every emulated Roku (`fakeroku.0.<name>`):
|
|
|
77
81
|
|---|---|---|
|
|
78
82
|
| `.command` | string, read-only | The last command as plain text (`Home`, `Lit_a`, `launch:12`, `search:news`). One datapoint for everything — no object-per-character sprawl. |
|
|
79
83
|
| `.commandType` | string, read-only | `keypress` / `keydown` / `keyup` / `launch` / `install` / `input` / `search`. |
|
|
80
|
-
| `.keys.<Key>` | boolean, read-only | One state per remote key the device type exposes — a *Player* has the 16 navigation/playback keys, a *TV* adds Volume\*, PowerOff, Channel\*, HDMI/AV inputs — all created up front. A keypress pulses it `true` for a moment; keydown/keyup hold it. |
|
|
84
|
+
| `.keys.<Key>` | boolean, read-only | One state per remote key the device type exposes — a *Player* has the 16 navigation/playback keys, a *TV* adds Volume\*, PowerOn/PowerOff/Power, Sleep, Channel\*, Tuner/HDMI/AV inputs — all created up front. A keypress pulses it `true` for a moment; keydown/keyup hold it. Key names are read in any case (`home` is `Home`). |
|
|
81
85
|
|
|
82
86
|
Free keyboard input (`Lit_x`) and app launches show up in `.command` only — they do
|
|
83
|
-
not get their own objects.
|
|
87
|
+
not get their own objects. An app button of a remote that sends app launches (a
|
|
88
|
+
Sofabaton, Home Assistant) arrives as `launch:<id>`; a Harmony Hub did not send its app
|
|
89
|
+
buttons to the emulated Roku.
|
|
84
90
|
|
|
85
91
|
> Note: the Roku remote sends the **same** `Play` command for play and pause, so
|
|
86
92
|
> play and pause cannot be told apart here — that is a protocol limitation, not an
|
|
@@ -94,6 +100,22 @@ becomes `true` — or watch `.command` for the last button as text.
|
|
|
94
100
|
Placeholder for the next version (at the beginning of the line):
|
|
95
101
|
### **WORK IN PROGRESS**
|
|
96
102
|
-->
|
|
103
|
+
### 1.8.0 (2026-09-25)
|
|
104
|
+
|
|
105
|
+
- (krobipd) Fixed: Home Assistant and openHAB can set up the emulated Roku again — it now answers the active-app, media-player and TV-channel queries they send.
|
|
106
|
+
- (krobipd) Fixed: keys sent in any upper or lower case (home, POWERON) press the right button, and spaces typed in Home Assistant arrive as spaces.
|
|
107
|
+
- (krobipd) Fixed: an upgrade from the old adapter keeps every object tree with its rooms and history, also for names with an umlaut, a bracket or a double space.
|
|
108
|
+
- (krobipd) Fixed: renaming a device only changes its displayed name; its datapoints and scripts pointing at them stay where they are.
|
|
109
|
+
- (krobipd) Fixed: an instance started before the network is up starts its devices and adds discovery as soon as the host has an address.
|
|
110
|
+
- (krobipd) Fixed: the device dialog greys out OK for a taken name or port and says why.
|
|
111
|
+
- (krobipd) Changed: only devices in the host's own networks are answered; a chosen network interface keeps everything in its network and is never swapped for another.
|
|
112
|
+
- (krobipd) Changed: a chosen network interface that does not exist is waited for up to two minutes at start, then reported, instead of being replaced.
|
|
113
|
+
- (krobipd) New: the TV profile adds the PowerOn, Power, Sleep and InputTuner keys and announces itself the way real Roku TVs do.
|
|
114
|
+
- (krobipd) New: every new emulated Roku gets its own network identity instead of one every installation with the same name would share.
|
|
115
|
+
|
|
116
|
+
### 1.7.1 (2026-09-16) — stable
|
|
117
|
+
|
|
118
|
+
- (krobipd) Changed: the instance restarts once after this update to clear a setting left behind by an older version; nothing else changes for you.
|
|
97
119
|
|
|
98
120
|
### 1.7.0 (2026-09-16)
|
|
99
121
|
|
|
@@ -103,7 +125,7 @@ becomes `true` — or watch `.command` for the last button as text.
|
|
|
103
125
|
- (krobipd) Fixed: a key you hold right after a short press stays pressed instead of being released early.
|
|
104
126
|
- (krobipd) Fixed: the device dialog now also refuses a name that would collide with an existing device in the object tree.
|
|
105
127
|
- (krobipd) Improved: after the host gets a new IP address, remotes find the emulated Rokus again without restarting the instance.
|
|
106
|
-
- (krobipd)
|
|
128
|
+
- (krobipd) Changed: the network interface setting moved to the standard settings key (bind); the instance restarts once after this update.
|
|
107
129
|
|
|
108
130
|
### 1.6.1 (2026-09-07) — stable
|
|
109
131
|
|
|
@@ -122,20 +144,6 @@ becomes `true` — or watch `.command` for the last button as text.
|
|
|
122
144
|
- (krobipd) Changed: the adapter can now run in compact mode, sharing one process with other adapters instead of claiming its own.
|
|
123
145
|
- (krobipd) Changed: more than one instance may run on the same machine again; only the ports have to differ.
|
|
124
146
|
|
|
125
|
-
### 1.5.0 (2026-09-03)
|
|
126
|
-
|
|
127
|
-
- (krobipd) Fixed: deleting the last emulated Roku left all of its datapoints behind for good. They are now removed whenever the configuration says a device is gone.
|
|
128
|
-
- (krobipd) Fixed: on a host running Docker the adapter could announce itself under a container address no remote can reach. A real network address is preferred now.
|
|
129
|
-
- (krobipd) Fixed: an emulated Roku whose server died while running left the instance showing "connected". It now reports the failure and names the device.
|
|
130
|
-
|
|
131
|
-
### 1.4.0 (2026-09-03)
|
|
132
|
-
- (krobipd) Fixed: renaming an emulated Roku could change its identity on the network, so a paired Harmony or Sofabaton lost the device and had to be set up again.
|
|
133
|
-
- (krobipd) Fixed: a remote key that was pressed when the adapter stopped stayed on for good. All key datapoints are now released at start-up, so the next press works again.
|
|
134
|
-
- (krobipd) Fixed: a device named "info" entered by hand into the configuration replaced the instance's own status channel. The name is refused now and leftovers are removed.
|
|
135
|
-
- (krobipd) Changed: every datapoint now carries a translated name and, where useful, a short description — in all eleven languages, in existing installations as well.
|
|
136
|
-
- (krobipd) Improved: a remote with a globally routable IPv6 address is accepted when it sits in the same network as the ioBroker host, not just on the reserved IPv6 ranges.
|
|
137
|
-
- (krobipd) New: user documentation in English and German, shown in the ioBroker documentation portal.
|
|
138
|
-
|
|
139
147
|
[Older changelogs can be found there](CHANGELOG_OLD.md)
|
|
140
148
|
|
|
141
149
|
## History
|
package/admin/i18n/de.json
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
"deviceName": "Name",
|
|
8
8
|
"devicePort": "ECP-Port",
|
|
9
9
|
"portLabel": "Port",
|
|
10
|
-
"devicePortHint": "Der ECP-Port, auf dem dieser Roku antwortet. Jeder emulierte Roku braucht
|
|
10
|
+
"devicePortHint": "Der ECP-Port, auf dem dieser Roku antwortet. Jeder emulierte Roku braucht seinen eigenen Port. Harmony und Sofabaton lesen den Port aus der Erkennung; Home Assistant und Homey nutzen immer 8060 — gib 8060 dem Roku, den sie steuern sollen.",
|
|
11
11
|
"deviceTypeLabel": "Typ",
|
|
12
12
|
"deviceTypePlayer": "Player (Streaming-Box)",
|
|
13
|
-
"deviceTypeTv": "TV (mit Lautstärke,
|
|
14
|
-
"deviceTypeHint": "„Player“ bietet die 16 Standard-Fernbedienungstasten. „TV“ zusätzlich Lautstärke-, Kanal- und Eingangstasten
|
|
13
|
+
"deviceTypeTv": "TV (mit Lautstärke, Ein/Aus, Kanal, Eingang)",
|
|
14
|
+
"deviceTypeHint": "„Player“ bietet die 16 Standard-Fernbedienungstasten. „TV“ zusätzlich Lautstärke-, Ein/Aus-, Kanal- und Eingangstasten.",
|
|
15
15
|
"dmAdd": "Roku-Gerät hinzufügen",
|
|
16
16
|
"dmEdit": "Bearbeiten",
|
|
17
17
|
"dmDelete": "Löschen",
|
package/admin/i18n/en.json
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
"deviceName": "Name",
|
|
8
8
|
"devicePort": "ECP port",
|
|
9
9
|
"portLabel": "Port",
|
|
10
|
-
"devicePortHint": "The ECP port this Roku answers on. Each emulated Roku needs its own port
|
|
10
|
+
"devicePortHint": "The ECP port this Roku answers on. Each emulated Roku needs its own port. Harmony and Sofabaton read the port from the discovery; Home Assistant and Homey always use 8060 — give 8060 to the Roku they should control.",
|
|
11
11
|
"deviceTypeLabel": "Type",
|
|
12
12
|
"deviceTypePlayer": "Player (streaming box)",
|
|
13
|
-
"deviceTypeTv": "TV (adds volume, power
|
|
14
|
-
"deviceTypeHint": "\"Player\" exposes the 16 standard remote keys. \"TV\" additionally exposes volume, channel and input keys
|
|
13
|
+
"deviceTypeTv": "TV (adds volume, power, channel, input)",
|
|
14
|
+
"deviceTypeHint": "\"Player\" exposes the 16 standard remote keys. \"TV\" additionally exposes volume, power, channel and input keys.",
|
|
15
15
|
"dmAdd": "Add Roku device",
|
|
16
16
|
"dmEdit": "Edit",
|
|
17
17
|
"dmDelete": "Delete",
|
package/admin/i18n/es.json
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
"deviceName": "Nombre",
|
|
8
8
|
"devicePort": "Puerto ECP",
|
|
9
9
|
"portLabel": "Puerto",
|
|
10
|
-
"devicePortHint": "El puerto ECP en el que responde este Roku. Cada Roku emulado necesita su propio puerto
|
|
10
|
+
"devicePortHint": "El puerto ECP en el que responde este Roku. Cada Roku emulado necesita su propio puerto. Harmony y Sofabaton leen el puerto del descubrimiento; Home Assistant y Homey usan siempre el 8060: asigna el 8060 al Roku que deben controlar.",
|
|
11
11
|
"deviceTypeLabel": "Tipo",
|
|
12
|
-
"deviceTypePlayer": "
|
|
13
|
-
"deviceTypeTv": "TV (con volumen,
|
|
14
|
-
"deviceTypeHint": "«Player» expone las 16 teclas estándar del mando. «TV» añade las teclas de volumen, canal y entrada
|
|
12
|
+
"deviceTypePlayer": "Reproductor (dispositivo de streaming)",
|
|
13
|
+
"deviceTypeTv": "TV (con volumen, encendido, canal, entrada)",
|
|
14
|
+
"deviceTypeHint": "«Player» expone las 16 teclas estándar del mando. «TV» añade las teclas de volumen, encendido/apagado, canal y entrada.",
|
|
15
15
|
"dmAdd": "Añadir dispositivo Roku",
|
|
16
16
|
"dmEdit": "Editar",
|
|
17
17
|
"dmDelete": "Eliminar",
|
package/admin/i18n/fr.json
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
"deviceName": "Nom",
|
|
8
8
|
"devicePort": "Port ECP",
|
|
9
9
|
"portLabel": "Port",
|
|
10
|
-
"devicePortHint": "Le port ECP sur lequel ce Roku répond. Chaque Roku émulé a besoin de son propre port
|
|
10
|
+
"devicePortHint": "Le port ECP sur lequel ce Roku répond. Chaque Roku émulé a besoin de son propre port. Harmony et Sofabaton lisent le port dans la découverte ; Home Assistant et Homey utilisent toujours 8060 — attribuez 8060 au Roku qu'ils doivent piloter.",
|
|
11
11
|
"deviceTypeLabel": "Type",
|
|
12
12
|
"deviceTypePlayer": "Player (box de streaming)",
|
|
13
|
-
"deviceTypeTv": "TV (avec volume,
|
|
14
|
-
"deviceTypeHint": "« Player » expose les 16 touches standard de la télécommande. « TV » ajoute les touches volume, chaîne et entrée
|
|
13
|
+
"deviceTypeTv": "TV (avec volume, marche/arrêt, chaîne, entrée)",
|
|
14
|
+
"deviceTypeHint": "« Player » expose les 16 touches standard de la télécommande. « TV » ajoute les touches volume, marche/arrêt, chaîne et entrée.",
|
|
15
15
|
"dmAdd": "Ajouter un appareil Roku",
|
|
16
16
|
"dmEdit": "Modifier",
|
|
17
17
|
"dmDelete": "Supprimer",
|
package/admin/i18n/it.json
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
"deviceName": "Nome",
|
|
8
8
|
"devicePort": "Porta ECP",
|
|
9
9
|
"portLabel": "Porta",
|
|
10
|
-
"devicePortHint": "La porta ECP su cui risponde questo Roku. Ogni Roku emulato ha bisogno della propria porta
|
|
10
|
+
"devicePortHint": "La porta ECP su cui risponde questo Roku. Ogni Roku emulato ha bisogno della propria porta. Harmony e Sofabaton leggono la porta dal rilevamento; Home Assistant e Homey usano sempre la 8060 — assegna la 8060 al Roku che devono controllare.",
|
|
11
11
|
"deviceTypeLabel": "Tipo",
|
|
12
12
|
"deviceTypePlayer": "Player (box di streaming)",
|
|
13
|
-
"deviceTypeTv": "TV (con volume,
|
|
14
|
-
"deviceTypeHint": "«Player» espone i 16 tasti standard del telecomando. «TV» aggiunge i tasti volume, canale e ingresso
|
|
13
|
+
"deviceTypeTv": "TV (con volume, accensione, canale, ingresso)",
|
|
14
|
+
"deviceTypeHint": "«Player» espone i 16 tasti standard del telecomando. «TV» aggiunge i tasti volume, accensione/spegnimento, canale e ingresso.",
|
|
15
15
|
"dmAdd": "Aggiungi dispositivo Roku",
|
|
16
16
|
"dmEdit": "Modifica",
|
|
17
17
|
"dmDelete": "Elimina",
|
package/admin/i18n/nl.json
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
"deviceName": "Naam",
|
|
8
8
|
"devicePort": "ECP-poort",
|
|
9
9
|
"portLabel": "Poort",
|
|
10
|
-
"devicePortHint": "De ECP-poort waarop deze Roku antwoordt. Elke geëmuleerde Roku heeft een eigen poort nodig
|
|
10
|
+
"devicePortHint": "De ECP-poort waarop deze Roku antwoordt. Elke geëmuleerde Roku heeft een eigen poort nodig. Harmony en Sofabaton lezen de poort uit de detectie; Home Assistant en Homey gebruiken altijd 8060 — geef 8060 aan de Roku die zij moeten bedienen.",
|
|
11
11
|
"deviceTypeLabel": "Type",
|
|
12
12
|
"deviceTypePlayer": "Player (streaming-box)",
|
|
13
|
-
"deviceTypeTv": "TV (met volume,
|
|
14
|
-
"deviceTypeHint": "„Player“ biedt de 16 standaardtoetsen. „TV“ voegt volume-, kanaal- en ingangstoetsen toe
|
|
13
|
+
"deviceTypeTv": "TV (met volume, aan/uit, kanaal, ingang)",
|
|
14
|
+
"deviceTypeHint": "„Player“ biedt de 16 standaardtoetsen. „TV“ voegt volume-, aan/uit-, kanaal- en ingangstoetsen toe.",
|
|
15
15
|
"dmAdd": "Roku-apparaat toevoegen",
|
|
16
16
|
"dmEdit": "Bewerken",
|
|
17
17
|
"dmDelete": "Verwijderen",
|
package/admin/i18n/pl.json
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
"deviceName": "Nazwa",
|
|
8
8
|
"devicePort": "Port ECP",
|
|
9
9
|
"portLabel": "Port",
|
|
10
|
-
"devicePortHint": "Port ECP, na którym odpowiada ten Roku. Każdy emulowany Roku potrzebuje własnego portu
|
|
10
|
+
"devicePortHint": "Port ECP, na którym odpowiada ten Roku. Każdy emulowany Roku potrzebuje własnego portu. Harmony i Sofabaton odczytują port z wykrywania; Home Assistant i Homey zawsze używają 8060 — nadaj 8060 temu Roku, którym mają sterować.",
|
|
11
11
|
"deviceTypeLabel": "Typ",
|
|
12
12
|
"deviceTypePlayer": "Player (przystawka)",
|
|
13
|
-
"deviceTypeTv": "TV (z głośnością,
|
|
14
|
-
"deviceTypeHint": "„Player“ udostępnia 16 standardowych przycisków pilota. „TV“ dodaje przyciski głośności, kanału i wejścia
|
|
13
|
+
"deviceTypeTv": "TV (z głośnością, zasilaniem, kanałem, wejściem)",
|
|
14
|
+
"deviceTypeHint": "„Player“ udostępnia 16 standardowych przycisków pilota. „TV“ dodaje przyciski głośności, zasilania, kanału i wejścia.",
|
|
15
15
|
"dmAdd": "Dodaj urządzenie Roku",
|
|
16
16
|
"dmEdit": "Edytuj",
|
|
17
17
|
"dmDelete": "Usuń",
|
|
@@ -33,5 +33,5 @@
|
|
|
33
33
|
"stateLastCommandType": "Typ ostatniego polecenia",
|
|
34
34
|
"stateLastCommandTypeDesc": "Jakiego rodzaju było to polecenie: naciśnięcie klawisza, przytrzymanie lub zwolnienie, uruchomienie lub instalacja aplikacji, wpisanie tekstu albo wyszukiwanie.",
|
|
35
35
|
"channelKeys": "Klawisze pilota",
|
|
36
|
-
"channelKeysDesc": "Jeden stan na każdy klawisz oferowany przez to urządzenie; naciśnięcie ustawia go na krótko na
|
|
36
|
+
"channelKeysDesc": "Jeden stan na każdy klawisz oferowany przez to urządzenie; naciśnięcie ustawia go na krótko na wartość true."
|
|
37
37
|
}
|
package/admin/i18n/pt.json
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
"deviceName": "Nome",
|
|
8
8
|
"devicePort": "Porta ECP",
|
|
9
9
|
"portLabel": "Porta",
|
|
10
|
-
"devicePortHint": "A porta ECP em que este Roku responde. Cada Roku emulado precisa da sua própria porta
|
|
10
|
+
"devicePortHint": "A porta ECP em que este Roku responde. Cada Roku emulado precisa da sua própria porta. O Harmony e o Sofabaton leem a porta na descoberta; o Home Assistant e o Homey usam sempre a 8060 — atribua a 8060 ao Roku que devem controlar.",
|
|
11
11
|
"deviceTypeLabel": "Tipo",
|
|
12
12
|
"deviceTypePlayer": "Player (box de streaming)",
|
|
13
|
-
"deviceTypeTv": "TV (com volume, desligar, canal, entrada)",
|
|
14
|
-
"deviceTypeHint": "«Player» expõe as 16 teclas padrão do comando. «TV» adiciona teclas de volume, canal e entrada
|
|
13
|
+
"deviceTypeTv": "TV (com volume, ligar/desligar, canal, entrada)",
|
|
14
|
+
"deviceTypeHint": "«Player» expõe as 16 teclas padrão do comando. «TV» adiciona teclas de volume, ligar/desligar, canal e entrada.",
|
|
15
15
|
"dmAdd": "Adicionar dispositivo Roku",
|
|
16
16
|
"dmEdit": "Editar",
|
|
17
17
|
"dmDelete": "Eliminar",
|
package/admin/i18n/ru.json
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
"deviceName": "Имя",
|
|
8
8
|
"devicePort": "Порт ECP",
|
|
9
9
|
"portLabel": "Порт",
|
|
10
|
-
"devicePortHint": "ECP
|
|
10
|
+
"devicePortHint": "Порт ECP, на котором отвечает этот Roku. Каждому эмулированному Roku нужен свой порт. Harmony и Sofabaton берут порт из обнаружения; Home Assistant и Homey всегда используют 8060 — назначьте 8060 тому Roku, которым они должны управлять.",
|
|
11
11
|
"deviceTypeLabel": "Тип",
|
|
12
12
|
"deviceTypePlayer": "Проигрыватель (приставка)",
|
|
13
|
-
"deviceTypeTv": "ТВ (с громкостью,
|
|
14
|
-
"deviceTypeHint": "«Проигрыватель» даёт 16 стандартных кнопок пульта. «ТВ» добавляет кнопки громкости, канала и
|
|
13
|
+
"deviceTypeTv": "ТВ (с громкостью, питанием, каналом, входом)",
|
|
14
|
+
"deviceTypeHint": "«Проигрыватель» даёт 16 стандартных кнопок пульта. «ТВ» добавляет кнопки громкости, питания, канала и входа.",
|
|
15
15
|
"dmAdd": "Добавить устройство Roku",
|
|
16
16
|
"dmEdit": "Изменить",
|
|
17
17
|
"dmDelete": "Удалить",
|
package/admin/i18n/uk.json
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
"deviceName": "Назва",
|
|
8
8
|
"devicePort": "Порт ECP",
|
|
9
9
|
"portLabel": "Порт",
|
|
10
|
-
"devicePortHint": "ECP
|
|
10
|
+
"devicePortHint": "Порт ECP, на якому відповідає цей Roku. Кожному емульованому Roku потрібен власний порт. Harmony і Sofabaton беруть порт із виявлення; Home Assistant і Homey завжди використовують 8060 — призначте 8060 тому Roku, яким вони мають керувати.",
|
|
11
11
|
"deviceTypeLabel": "Тип",
|
|
12
12
|
"deviceTypePlayer": "Програвач (приставка)",
|
|
13
|
-
"deviceTypeTv": "ТБ (з гучністю,
|
|
14
|
-
"deviceTypeHint": "«Програвач» надає 16 стандартних кнопок пульта. «ТБ» додає кнопки гучності, каналу та
|
|
13
|
+
"deviceTypeTv": "ТБ (з гучністю, живленням, каналом, входом)",
|
|
14
|
+
"deviceTypeHint": "«Програвач» надає 16 стандартних кнопок пульта. «ТБ» додає кнопки гучності, живлення, каналу та входу.",
|
|
15
15
|
"dmAdd": "Додати пристрій Roku",
|
|
16
16
|
"dmEdit": "Редагувати",
|
|
17
17
|
"dmDelete": "Видалити",
|
package/admin/i18n/zh-cn.json
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
"deviceName": "名称",
|
|
8
8
|
"devicePort": "ECP 端口",
|
|
9
9
|
"portLabel": "端口",
|
|
10
|
-
"devicePortHint": "此 Roku
|
|
10
|
+
"devicePortHint": "此 Roku 响应的 ECP 端口。每个模拟 Roku 都需要自己的端口。Harmony 和 Sofabaton 从发现信息中读取端口;Home Assistant 和 Homey 始终使用 8060——请把 8060 分配给它们要控制的 Roku。",
|
|
11
11
|
"deviceTypeLabel": "类型",
|
|
12
12
|
"deviceTypePlayer": "播放器(流媒体盒子)",
|
|
13
|
-
"deviceTypeTv": "
|
|
14
|
-
"deviceTypeHint": "“播放器”提供 16
|
|
13
|
+
"deviceTypeTv": "电视(带音量、电源、频道、输入)",
|
|
14
|
+
"deviceTypeHint": "“播放器”提供 16 个标准遥控按键。“电视”额外提供音量、电源、频道和输入按键。",
|
|
15
15
|
"dmAdd": "添加 Roku 设备",
|
|
16
16
|
"dmEdit": "编辑",
|
|
17
17
|
"dmDelete": "删除",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"dmDeleteConfirm": "删除 Roku 设备“%s”?",
|
|
20
20
|
"deviceNameInUse": "此名称已被另一台设备使用。",
|
|
21
21
|
"devicePortInUse": "此端口已被另一台 Roku 使用。",
|
|
22
|
-
"deviceNameInvalid": "无法使用此名称——它是保留名称,或与另一设备生成相同的对象
|
|
22
|
+
"deviceNameInvalid": "无法使用此名称——它是保留名称,或与另一设备生成相同的对象 ID。",
|
|
23
23
|
"deviceNameRejected": "此名称无法使用:它已被占用、为适配器保留,或与另一设备生成相同的对象 ID。",
|
|
24
24
|
"supportHeader": "支持",
|
|
25
25
|
"aboutInfo": "如果此适配器对您有用,一点捐赠有助于维护它——谢谢!",
|
|
@@ -28,47 +28,67 @@ var import_constants = require("./lib/constants");
|
|
|
28
28
|
var import_device_config = require("./lib/device-config");
|
|
29
29
|
var import_device_identity = require("./lib/device-identity");
|
|
30
30
|
var import_i18n = require("./lib/i18n");
|
|
31
|
+
var import_pure_helpers = require("./lib/pure-helpers");
|
|
31
32
|
const ID_EXPRESSION = "(data.name||'').trim().replace(/[^A-Za-z0-9\\-_]/g,'_')";
|
|
32
33
|
function buildDeviceForm(usedNames, usedPorts, usedIds) {
|
|
33
34
|
const nameList = JSON.stringify(usedNames.map((n) => n.trim().toLowerCase()));
|
|
34
|
-
const idList = JSON.stringify([...import_constants.RESERVED_IDS, ...usedIds]);
|
|
35
35
|
const portList = JSON.stringify([...usedPorts]);
|
|
36
|
+
const nameFilled = "(data.name||'').trim().length>0";
|
|
37
|
+
const nameFree = `!${nameList}.includes((data.name||'').trim().toLowerCase())`;
|
|
38
|
+
const idFree = usedIds === null ? "true" : `${ID_EXPRESSION}.length>0 && !${JSON.stringify([...import_constants.RESERVED_IDS, ...usedIds])}.includes(${ID_EXPRESSION})`;
|
|
39
|
+
const nameOk = `(${nameFilled} && ${nameFree} && ${idFree})`;
|
|
40
|
+
const portOk = `(!${portList}.includes(Number(data.port)))`;
|
|
36
41
|
return {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
42
|
+
applyDisabledRule: `!(${nameOk} && ${portOk})`,
|
|
43
|
+
schema: {
|
|
44
|
+
type: "panel",
|
|
45
|
+
items: {
|
|
46
|
+
name: {
|
|
47
|
+
type: "text",
|
|
48
|
+
label: (0, import_i18n.t)("deviceName"),
|
|
49
|
+
validator: nameOk,
|
|
50
|
+
validatorNoSaveOnError: true,
|
|
51
|
+
sm: 12,
|
|
52
|
+
md: 6
|
|
53
|
+
},
|
|
54
|
+
port: {
|
|
55
|
+
type: "number",
|
|
56
|
+
label: (0, import_i18n.t)("devicePort"),
|
|
57
|
+
min: 1,
|
|
58
|
+
max: 65535,
|
|
59
|
+
validator: portOk,
|
|
60
|
+
validatorNoSaveOnError: true,
|
|
61
|
+
sm: 12,
|
|
62
|
+
md: 3
|
|
63
|
+
},
|
|
64
|
+
type: {
|
|
65
|
+
type: "select",
|
|
66
|
+
label: (0, import_i18n.t)("deviceTypeLabel"),
|
|
67
|
+
default: "player",
|
|
68
|
+
options: [
|
|
69
|
+
{ label: (0, import_i18n.t)("deviceTypePlayer"), value: "player" },
|
|
70
|
+
{ label: (0, import_i18n.t)("deviceTypeTv"), value: "tv" }
|
|
71
|
+
],
|
|
72
|
+
sm: 12,
|
|
73
|
+
md: 3
|
|
74
|
+
},
|
|
75
|
+
_nameRejected: {
|
|
76
|
+
type: "staticText",
|
|
77
|
+
text: (0, import_i18n.t)("deviceNameRejected"),
|
|
78
|
+
hidden: `!${nameFilled} || ${nameOk}`,
|
|
79
|
+
style: { color: "#d32f2f" },
|
|
80
|
+
sm: 12
|
|
81
|
+
},
|
|
82
|
+
_portRejected: {
|
|
83
|
+
type: "staticText",
|
|
84
|
+
text: (0, import_i18n.t)("devicePortInUse"),
|
|
85
|
+
hidden: portOk,
|
|
86
|
+
style: { color: "#d32f2f" },
|
|
87
|
+
sm: 12
|
|
88
|
+
},
|
|
89
|
+
_portHint: { type: "staticText", text: (0, import_i18n.t)("devicePortHint"), sm: 12 },
|
|
90
|
+
_typeHint: { type: "staticText", text: (0, import_i18n.t)("deviceTypeHint"), sm: 12 }
|
|
91
|
+
}
|
|
72
92
|
}
|
|
73
93
|
};
|
|
74
94
|
}
|
|
@@ -81,11 +101,13 @@ class FakerokuDeviceManagement extends import_dm_utils.DeviceManagement {
|
|
|
81
101
|
return `system.adapter.${this.adapter.namespace}`;
|
|
82
102
|
}
|
|
83
103
|
/**
|
|
84
|
-
* Read the device list from the live config object, through the shared normaliser
|
|
104
|
+
* Read the device list from the live config object, through the shared normaliser — against
|
|
105
|
+
* the object tree the installation has, exactly like the runtime, so a row without a stored
|
|
106
|
+
* object id or type resolves to the tree it already owns (lib/device-config.ts).
|
|
85
107
|
*
|
|
86
108
|
* Every row keeps BOTH names: the trimmed one the card shows and the next write stores,
|
|
87
109
|
* and the stored one the identity was resolved from. Nothing here re-derives an identity
|
|
88
|
-
* from the display name — that is what unpaired the remote before
|
|
110
|
+
* from the display name — that is what unpaired the remote before.
|
|
89
111
|
*
|
|
90
112
|
* @returns the configured devices (normalised), or an empty list
|
|
91
113
|
*/
|
|
@@ -93,7 +115,18 @@ class FakerokuDeviceManagement extends import_dm_utils.DeviceManagement {
|
|
|
93
115
|
var _a, _b;
|
|
94
116
|
const obj = await this.adapter.getForeignObjectAsync(this.objId);
|
|
95
117
|
const devices = (_a = obj == null ? void 0 : obj.native) == null ? void 0 : _a.devices;
|
|
96
|
-
|
|
118
|
+
const objects = await this.adapter.getAdapterObjectsAsync();
|
|
119
|
+
const prefix = `${this.adapter.namespace}.`;
|
|
120
|
+
const relative = /* @__PURE__ */ new Map();
|
|
121
|
+
for (const [id, o] of Object.entries(objects)) {
|
|
122
|
+
if (id.startsWith(prefix)) {
|
|
123
|
+
relative.set(id.slice(prefix.length), o == null ? void 0 : o.type);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return (_b = (0, import_device_config.toDeviceRows)(
|
|
127
|
+
devices,
|
|
128
|
+
(0, import_device_config.deviceTreeOf)(relative.keys(), (id) => relative.get(id))
|
|
129
|
+
)) != null ? _b : [];
|
|
97
130
|
}
|
|
98
131
|
/**
|
|
99
132
|
* Persist the device list.
|
|
@@ -114,19 +147,20 @@ class FakerokuDeviceManagement extends import_dm_utils.DeviceManagement {
|
|
|
114
147
|
}
|
|
115
148
|
/**
|
|
116
149
|
* The stored shape of a row the user did NOT touch: its name exactly as it was, the
|
|
117
|
-
* normalised port and type, and the identity resolved when the row was read.
|
|
150
|
+
* normalised port and type, and the identity and object id resolved when the row was read.
|
|
118
151
|
*
|
|
119
152
|
* The name stays untouched on purpose. Writing the trimmed form would move that device's
|
|
120
153
|
* object id on the next start — a tree wandering, and every script pointing into it
|
|
121
154
|
* breaking, because someone edited a different card. Trimming happens where the user
|
|
122
155
|
* pressed save, nowhere else. Persisting the identity is safe by contrast: it is exactly
|
|
123
|
-
* the value the runtime is already advertising for that row, just no longer derived
|
|
156
|
+
* the value the runtime is already advertising for that row, just no longer derived — and the
|
|
157
|
+
* same holds for the object id and for a type read from the tree.
|
|
124
158
|
*
|
|
125
159
|
* @param row the normalised row
|
|
126
160
|
* @returns the row as it is persisted
|
|
127
161
|
*/
|
|
128
162
|
static toStored(row) {
|
|
129
|
-
return { name: row.storedName, port: row.port, type: row.type, uuid: row.identity };
|
|
163
|
+
return { name: row.storedName, port: row.port, type: row.type, uuid: row.identity, objectId: row.objectId };
|
|
130
164
|
}
|
|
131
165
|
/**
|
|
132
166
|
* Populate the manager with one card per configured device.
|
|
@@ -158,12 +192,11 @@ class FakerokuDeviceManagement extends import_dm_utils.DeviceManagement {
|
|
|
158
192
|
* @returns the card descriptor
|
|
159
193
|
*/
|
|
160
194
|
toDeviceInfo(device) {
|
|
161
|
-
const kind = device.type === "tv" ? "TV" : "Player";
|
|
162
195
|
return {
|
|
163
196
|
id: device.identity,
|
|
164
197
|
name: device.name,
|
|
165
198
|
identifier: String(device.port),
|
|
166
|
-
model:
|
|
199
|
+
model: (0, import_i18n.t)(device.type === "tv" ? "deviceTypeTv" : "deviceTypePlayer"),
|
|
167
200
|
actions: [
|
|
168
201
|
{
|
|
169
202
|
id: "edit",
|
|
@@ -204,9 +237,11 @@ class FakerokuDeviceManagement extends import_dm_utils.DeviceManagement {
|
|
|
204
237
|
const usedNames = devices.map((d) => d.name);
|
|
205
238
|
const usedPorts = devices.map((d) => d.port);
|
|
206
239
|
const usedIds = devices.map(import_device_config.deviceObjectId);
|
|
207
|
-
const
|
|
240
|
+
const form = buildDeviceForm(usedNames, usedPorts, usedIds);
|
|
241
|
+
const data = await context.showForm(form.schema, {
|
|
208
242
|
title: (0, import_i18n.t)("dmAdd"),
|
|
209
|
-
data: { type: "player", port: (0, import_device_config.nextFreePort)(usedPorts) }
|
|
243
|
+
data: { type: "player", port: (0, import_device_config.nextFreePort)(usedPorts) },
|
|
244
|
+
applyDisabledRule: form.applyDisabledRule
|
|
210
245
|
});
|
|
211
246
|
if (data && typeof data.name === "string" && data.name.trim()) {
|
|
212
247
|
const clean = cleanDevice(data);
|
|
@@ -216,7 +251,7 @@ class FakerokuDeviceManagement extends import_dm_utils.DeviceManagement {
|
|
|
216
251
|
return { refresh: true };
|
|
217
252
|
}
|
|
218
253
|
const stored = devices.map((d) => FakerokuDeviceManagement.toStored(d));
|
|
219
|
-
stored.push({ ...clean, uuid: (0, import_device_identity.
|
|
254
|
+
stored.push({ ...clean, uuid: (0, import_device_identity.randomIdentity)(), objectId: (0, import_pure_helpers.sanitizeId)(clean.name) });
|
|
220
255
|
await this.writeDevices(stored);
|
|
221
256
|
}
|
|
222
257
|
return { refresh: true };
|
|
@@ -243,10 +278,11 @@ class FakerokuDeviceManagement extends import_dm_utils.DeviceManagement {
|
|
|
243
278
|
}
|
|
244
279
|
const usedNames = devices.filter((_, i) => i !== index).map((d) => d.name);
|
|
245
280
|
const usedPorts = devices.filter((_, i) => i !== index).map((d) => d.port);
|
|
246
|
-
const
|
|
247
|
-
const data = await context.showForm(
|
|
281
|
+
const form = buildDeviceForm(usedNames, usedPorts, null);
|
|
282
|
+
const data = await context.showForm(form.schema, {
|
|
248
283
|
title: (0, import_i18n.t)("dmEditTitle"),
|
|
249
|
-
data: { name: current.name, port: current.port, type: current.type }
|
|
284
|
+
data: { name: current.name, port: current.port, type: current.type },
|
|
285
|
+
applyDisabledRule: form.applyDisabledRule
|
|
250
286
|
});
|
|
251
287
|
if (data && typeof data.name === "string" && data.name.trim()) {
|
|
252
288
|
const clean = cleanDevice(data);
|
|
@@ -256,7 +292,7 @@ class FakerokuDeviceManagement extends import_dm_utils.DeviceManagement {
|
|
|
256
292
|
return { refresh: "devices" };
|
|
257
293
|
}
|
|
258
294
|
const stored = devices.map((d) => FakerokuDeviceManagement.toStored(d));
|
|
259
|
-
stored[index] = { ...clean, uuid: current.identity };
|
|
295
|
+
stored[index] = { ...clean, uuid: current.identity, objectId: current.objectId };
|
|
260
296
|
await this.writeDevices(stored);
|
|
261
297
|
}
|
|
262
298
|
return { refresh: "devices" };
|