cncjs-pendant-alexa 0.1.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/LICENSE +21 -0
- package/README.md +147 -0
- package/bin/cncjs-pendant-alexa +2 -0
- package/examples/systemd/alexa-tunnel-ngrok.service +15 -0
- package/examples/systemd/cncjs-pendant-alexa.service +20 -0
- package/models/en-US.json +254 -0
- package/models/pt-BR.json +277 -0
- package/package.json +40 -0
- package/src/cli.js +76 -0
- package/src/cnc.js +141 -0
- package/src/intent.js +74 -0
- package/src/jog.js +105 -0
- package/src/server.js +37 -0
- package/src/skill.js +233 -0
- package/src/strings.js +74 -0
- package/tests/intent.test.js +60 -0
- package/tests/jog.test.js +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cassio Simoes
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# cncjs-pendant-alexa
|
|
2
|
+
|
|
3
|
+
Voice jog control for [cncjs](https://github.com/cncjs/cncjs) through a custom
|
|
4
|
+
Alexa skill:
|
|
5
|
+
|
|
6
|
+
> One-shot: "Alexa, **ask mill x plus 20**" — or open CNC mode: "Alexa,
|
|
7
|
+
> open **mill**" … "**x plus 20**" … "**position x 130 y 100**" … "**close**"
|
|
8
|
+
>
|
|
9
|
+
> One-shot: "Alexa, **peça pra fresa xis mais vinte**" — ou modo CNC:
|
|
10
|
+
> "Alexa, abrir **fresa**" … "**xis mais vinte**" … "**fechar**"
|
|
11
|
+
|
|
12
|
+
Numbers are millimeters. The pendant connects to the cncjs server's socket.io
|
|
13
|
+
API (like any other `cncjs-pendant-*`), sends Grbl `$J=` jog commands and
|
|
14
|
+
exposes a small HTTPS-tunneled endpoint that your own Alexa developer skill
|
|
15
|
+
calls. No cloud service in the middle besides Alexa itself; no skill-store
|
|
16
|
+
publication needed (a development-mode skill works forever on the Echos of
|
|
17
|
+
your Amazon account, for free).
|
|
18
|
+
|
|
19
|
+
Interaction models for **en-US** and **pt-BR** are included, ready to paste.
|
|
20
|
+
|
|
21
|
+
## Safety model
|
|
22
|
+
|
|
23
|
+
Voice is a lossy input, so the pendant refuses instead of trusting:
|
|
24
|
+
|
|
25
|
+
- Only `$J=` jog commands are ever sent — cancellable instantly, no parser
|
|
26
|
+
state pollution. Never raw `G0/G1`.
|
|
27
|
+
- Per-command travel limits (default: 50 mm on X/Y, 10 mm on Z). Asking for
|
|
28
|
+
200 mm gets a spoken refusal, not a move.
|
|
29
|
+
- Jogs are refused unless the controller is `Idle`/`Jog` **and** the cncjs
|
|
30
|
+
workflow is idle — a running job can never be disturbed.
|
|
31
|
+
- `Alarm` is reported, never auto-unlocked by voice.
|
|
32
|
+
- Z uses a slower feed rate than X/Y (defaults: F300 vs F1000).
|
|
33
|
+
- "halt" / "para" (and exiting the skill) send Grbl jog-cancel (`0x85`).
|
|
34
|
+
- Every request is cryptographically verified as coming from Alexa
|
|
35
|
+
(certificate + timestamp) and, with `--skill-id`, from *your* skill only.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
npm install -g thcasssio/cncjs-pendant-alexa
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
(Installs straight from GitHub; an npm registry release will follow.)
|
|
44
|
+
|
|
45
|
+
Run it on the same host as the cncjs server (it reads the `secret` from the
|
|
46
|
+
server's `~/.cncrc` to authenticate, exactly like the other community
|
|
47
|
+
pendants):
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
cncjs-pendant-alexa --port /dev/ttyUSB0 --skill-id amzn1.ask.skill.xxxx
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Options: `--baudrate 115200`, `--cncjs-url ws://127.0.0.1:8000`,
|
|
54
|
+
`--http-port 8977`, `--http-path /alexa`, `--secret-file /home/cnc/.cncrc`,
|
|
55
|
+
`--max-xy 50 --max-z 10 --feed-xy 1000 --feed-z 300`,
|
|
56
|
+
`--controller-type Grbl`.
|
|
57
|
+
|
|
58
|
+
Sanity check (connects, prints machine state as JSON, exits):
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
cncjs-pendant-alexa --port /dev/ttyUSB0 --check
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Set up the Alexa skill (one time, ~15 min, free)
|
|
65
|
+
|
|
66
|
+
1. **HTTPS tunnel** — Alexa must reach your endpoint over public HTTPS with a
|
|
67
|
+
valid certificate. Easiest free option: [ngrok](https://ngrok.com) with the
|
|
68
|
+
one free **static domain** every account gets
|
|
69
|
+
(`ngrok http 8977 --url=yourname.ngrok-free.app`). Alternatives: Cloudflare
|
|
70
|
+
Tunnel (if you own a domain) or Tailscale Funnel. See
|
|
71
|
+
`examples/systemd/alexa-tunnel-ngrok.service`.
|
|
72
|
+
2. Go to the [Alexa developer console](https://developer.amazon.com/alexa/console/ask)
|
|
73
|
+
→ **Create Skill** → name it (e.g. "CNC Pendant") → primary locale
|
|
74
|
+
**English (US)** or **Portuguese (BR)** → type **Custom** → host
|
|
75
|
+
**Provision your own**.
|
|
76
|
+
3. In **Build → Interaction Model → JSON Editor**, paste
|
|
77
|
+
[`models/en-US.json`](models/en-US.json) or
|
|
78
|
+
[`models/pt-BR.json`](models/pt-BR.json) (add the other locale under
|
|
79
|
+
**Language Settings** if you want both) → **Save Model** → **Build Model**.
|
|
80
|
+
4. In **Build → Endpoint**, choose **HTTPS**, set the default region to
|
|
81
|
+
`https://yourname.ngrok-free.app/alexa`, and pick the SSL certificate type
|
|
82
|
+
that matches your host — **this choice matters and the errors it causes are
|
|
83
|
+
silent**:
|
|
84
|
+
- Behind **Cloudflare** (or any host whose certificate covers your
|
|
85
|
+
subdomain via a wildcard like `*.example.com`): pick *"My development
|
|
86
|
+
endpoint is a sub-domain of a domain that has a wildcard certificate"*
|
|
87
|
+
(`Wildcard`).
|
|
88
|
+
- Only if the certificate names your exact hostname: *"...has a certificate
|
|
89
|
+
from a trusted certificate authority"* (`Trusted`).
|
|
90
|
+
|
|
91
|
+
With the wrong type, Alexa rejects the TLS handshake before any request
|
|
92
|
+
reaches you, and the simulator only says "endpoint error". The
|
|
93
|
+
[skill invocation API](https://developer.amazon.com/en-US/docs/alexa/smapi/skill-invocation-api.html)
|
|
94
|
+
is the one tool that reports the real reason (e.g. *"Certificate for host
|
|
95
|
+
contains wildcard"*).
|
|
96
|
+
5. Copy **Your Skill ID** (top of the Endpoint page) and pass it to the
|
|
97
|
+
pendant via `--skill-id`.
|
|
98
|
+
6. In **Test**, enable testing in **Development**. The skill is now live on
|
|
99
|
+
every Echo logged into your account — no certification, no publication.
|
|
100
|
+
|
|
101
|
+
## Run as a service
|
|
102
|
+
|
|
103
|
+
Copy the units from `examples/systemd/`, edit port/skill-id/domain:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
sudo cp examples/systemd/*.service /etc/systemd/system/
|
|
107
|
+
sudo systemctl daemon-reload
|
|
108
|
+
sudo systemctl enable --now cncjs-pendant-alexa alexa-tunnel-ngrok
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`GET /healthz` on the HTTP port reports the live machine state for monitoring.
|
|
112
|
+
|
|
113
|
+
## Voice reference
|
|
114
|
+
|
|
115
|
+
The default invocation name is **"mill"** (pt-BR: **"fresa"**) — change the
|
|
116
|
+
`invocationName` in the model JSON to whatever you prefer before pasting it. Every command
|
|
117
|
+
works one-shot ("Alexa, ask my mill x plus 20") or inside CNC mode ("Alexa, open
|
|
118
|
+
c. n. c.", then bare commands). While CNC mode is open, unrelated phrases get
|
|
119
|
+
a spoken reminder that Alexa is in CNC mode and how to leave; Alexa closes an
|
|
120
|
+
idle session on its own after a few seconds of silence (platform limit — a
|
|
121
|
+
custom skill cannot hold the microphone open indefinitely).
|
|
122
|
+
|
|
123
|
+
| Intent | en-US | pt-BR |
|
|
124
|
+
|---|---|---|
|
|
125
|
+
| Move (relative) | "x plus 20", "move y minus 5" | "xis mais vinte", "mover y menos cinco" |
|
|
126
|
+
| Go to (absolute, 1-3 axes) | "position x 130 y 100 z 5", "go to x 50" | "posição x 130 y 100 z 5", "vai para x 50" |
|
|
127
|
+
| Cancel jog | "halt", "stop moving" | "para", "pare" |
|
|
128
|
+
| Position | "where are you", "position" | "onde está", "posição" |
|
|
129
|
+
| Help | "help" | "ajuda" |
|
|
130
|
+
| Exit CNC mode | "close", "exit cnc mode" | "fechar", "sair do modo cnc" |
|
|
131
|
+
|
|
132
|
+
Absolute moves use work coordinates and the same per-axis travel limits,
|
|
133
|
+
measured from the current position.
|
|
134
|
+
|
|
135
|
+
## Development
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
npm test # unit tests (jog math, state gating, slot parsing)
|
|
139
|
+
node bin/cncjs-pendant-alexa --port /dev/ttyUSB0 --no-verify # local testing
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`--no-verify` disables Alexa signature verification so you can `curl` synthetic
|
|
143
|
+
request envelopes at the endpoint. Never expose an unverified endpoint.
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=HTTPS tunnel for the Alexa pendant endpoint (ngrok free static domain)
|
|
3
|
+
After=network-online.target
|
|
4
|
+
Wants=network-online.target
|
|
5
|
+
|
|
6
|
+
[Service]
|
|
7
|
+
# One free static domain per ngrok account: https://dashboard.ngrok.com/domains
|
|
8
|
+
# First run once: ngrok config add-authtoken <token>
|
|
9
|
+
User=cncjs
|
|
10
|
+
ExecStart=/usr/local/bin/ngrok http 8977 --url=REPLACE-ME.ngrok-free.app
|
|
11
|
+
Restart=always
|
|
12
|
+
RestartSec=5
|
|
13
|
+
|
|
14
|
+
[Install]
|
|
15
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=cncjs Alexa voice pendant
|
|
3
|
+
After=network-online.target cncjs.service
|
|
4
|
+
Wants=network-online.target
|
|
5
|
+
|
|
6
|
+
[Service]
|
|
7
|
+
# Must run as a user that can read the cncjs server's ~/.cncrc (the "secret"),
|
|
8
|
+
# or pass --secret-file pointing at it.
|
|
9
|
+
User=cncjs
|
|
10
|
+
ExecStart=/usr/bin/env cncjs-pendant-alexa \
|
|
11
|
+
--port /dev/ttyUSB0 \
|
|
12
|
+
--baudrate 115200 \
|
|
13
|
+
--cncjs-url ws://127.0.0.1:8000 \
|
|
14
|
+
--http-port 8977 \
|
|
15
|
+
--skill-id amzn1.ask.skill.REPLACE-WITH-YOUR-SKILL-ID
|
|
16
|
+
Restart=always
|
|
17
|
+
RestartSec=5
|
|
18
|
+
|
|
19
|
+
[Install]
|
|
20
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
{
|
|
2
|
+
"interactionModel": {
|
|
3
|
+
"languageModel": {
|
|
4
|
+
"invocationName": "mill",
|
|
5
|
+
"intents": [
|
|
6
|
+
{
|
|
7
|
+
"name": "AMAZON.CancelIntent",
|
|
8
|
+
"samples": []
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"name": "AMAZON.HelpIntent",
|
|
12
|
+
"samples": []
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "AMAZON.StopIntent",
|
|
16
|
+
"samples": []
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "AMAZON.NavigateHomeIntent",
|
|
20
|
+
"samples": []
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "AMAZON.FallbackIntent",
|
|
24
|
+
"samples": []
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "MoveIntent",
|
|
28
|
+
"slots": [
|
|
29
|
+
{
|
|
30
|
+
"name": "axis",
|
|
31
|
+
"type": "AXIS_TYPE"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "direction",
|
|
35
|
+
"type": "DIRECTION_TYPE"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "distance",
|
|
39
|
+
"type": "AMAZON.NUMBER"
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"samples": [
|
|
43
|
+
"{axis} {direction} {distance}",
|
|
44
|
+
"{axis} {direction} {distance} millimeters",
|
|
45
|
+
"{axis} {direction} {distance} millimeter",
|
|
46
|
+
"move {axis} {direction} {distance}",
|
|
47
|
+
"move the {axis} axis {direction} {distance}",
|
|
48
|
+
"jog {axis} {direction} {distance}",
|
|
49
|
+
"axis {axis} {direction} {distance}"
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "StopJogIntent",
|
|
54
|
+
"samples": [
|
|
55
|
+
"halt",
|
|
56
|
+
"stop moving",
|
|
57
|
+
"stop the move",
|
|
58
|
+
"cancel the move",
|
|
59
|
+
"cancel the jog"
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "StatusIntent",
|
|
64
|
+
"samples": [
|
|
65
|
+
"where are you",
|
|
66
|
+
"where is the spindle",
|
|
67
|
+
"position",
|
|
68
|
+
"what is the position",
|
|
69
|
+
"status"
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"name": "CloseIntent",
|
|
74
|
+
"samples": [
|
|
75
|
+
"close",
|
|
76
|
+
"close cnc",
|
|
77
|
+
"exit",
|
|
78
|
+
"exit cnc mode",
|
|
79
|
+
"leave cnc mode"
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"name": "GotoIntent",
|
|
84
|
+
"slots": [
|
|
85
|
+
{
|
|
86
|
+
"name": "xVal",
|
|
87
|
+
"type": "AMAZON.NUMBER"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"name": "yVal",
|
|
91
|
+
"type": "AMAZON.NUMBER"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"name": "zVal",
|
|
95
|
+
"type": "AMAZON.NUMBER"
|
|
96
|
+
}
|
|
97
|
+
],
|
|
98
|
+
"samples": [
|
|
99
|
+
"position x {xVal}",
|
|
100
|
+
"position y {yVal}",
|
|
101
|
+
"position z {zVal}",
|
|
102
|
+
"position x {xVal} y {yVal}",
|
|
103
|
+
"position x {xVal} z {zVal}",
|
|
104
|
+
"position y {yVal} z {zVal}",
|
|
105
|
+
"position x {xVal} y {yVal} z {zVal}",
|
|
106
|
+
"go to x {xVal}",
|
|
107
|
+
"go to x {xVal} y {yVal}",
|
|
108
|
+
"go to x {xVal} y {yVal} z {zVal}",
|
|
109
|
+
"go to position x {xVal} y {yVal} z {zVal}",
|
|
110
|
+
"go to z {zVal}"
|
|
111
|
+
]
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"name": "OpenIntent",
|
|
115
|
+
"samples": [
|
|
116
|
+
"open",
|
|
117
|
+
"start",
|
|
118
|
+
"enter cnc mode",
|
|
119
|
+
"cnc mode"
|
|
120
|
+
]
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"name": "UpDownIntent",
|
|
124
|
+
"slots": [
|
|
125
|
+
{
|
|
126
|
+
"name": "updown",
|
|
127
|
+
"type": "UPDOWN_TYPE"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"name": "distance",
|
|
131
|
+
"type": "AMAZON.NUMBER"
|
|
132
|
+
}
|
|
133
|
+
],
|
|
134
|
+
"samples": [
|
|
135
|
+
"{updown} {distance}",
|
|
136
|
+
"{updown} {distance} millimeters"
|
|
137
|
+
]
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"name": "ZMoveIntent",
|
|
141
|
+
"slots": [
|
|
142
|
+
{
|
|
143
|
+
"name": "direction",
|
|
144
|
+
"type": "DIRECTION_TYPE"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"name": "distance",
|
|
148
|
+
"type": "AMAZON.NUMBER"
|
|
149
|
+
}
|
|
150
|
+
],
|
|
151
|
+
"samples": [
|
|
152
|
+
"z {direction} {distance}",
|
|
153
|
+
"zed {direction} {distance}",
|
|
154
|
+
"zee {direction} {distance}",
|
|
155
|
+
"z axis {direction} {distance}",
|
|
156
|
+
"z {direction} {distance} millimeters"
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
],
|
|
160
|
+
"types": [
|
|
161
|
+
{
|
|
162
|
+
"name": "AXIS_TYPE",
|
|
163
|
+
"values": [
|
|
164
|
+
{
|
|
165
|
+
"id": "X",
|
|
166
|
+
"name": {
|
|
167
|
+
"value": "x",
|
|
168
|
+
"synonyms": [
|
|
169
|
+
"ex"
|
|
170
|
+
]
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"id": "Y",
|
|
175
|
+
"name": {
|
|
176
|
+
"value": "y",
|
|
177
|
+
"synonyms": [
|
|
178
|
+
"why"
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"id": "Z",
|
|
184
|
+
"name": {
|
|
185
|
+
"value": "z",
|
|
186
|
+
"synonyms": [
|
|
187
|
+
"zee",
|
|
188
|
+
"zed"
|
|
189
|
+
]
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
]
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"name": "DIRECTION_TYPE",
|
|
196
|
+
"values": [
|
|
197
|
+
{
|
|
198
|
+
"id": "PLUS",
|
|
199
|
+
"name": {
|
|
200
|
+
"value": "plus",
|
|
201
|
+
"synonyms": [
|
|
202
|
+
"positive",
|
|
203
|
+
"forward"
|
|
204
|
+
]
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"id": "MINUS",
|
|
209
|
+
"name": {
|
|
210
|
+
"value": "minus",
|
|
211
|
+
"synonyms": [
|
|
212
|
+
"negative",
|
|
213
|
+
"back"
|
|
214
|
+
]
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
]
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"name": "UPDOWN_TYPE",
|
|
221
|
+
"values": [
|
|
222
|
+
{
|
|
223
|
+
"id": "UP",
|
|
224
|
+
"name": {
|
|
225
|
+
"value": "up",
|
|
226
|
+
"synonyms": [
|
|
227
|
+
"raise",
|
|
228
|
+
"go up",
|
|
229
|
+
"move up"
|
|
230
|
+
]
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
"id": "DOWN",
|
|
235
|
+
"name": {
|
|
236
|
+
"value": "down",
|
|
237
|
+
"synonyms": [
|
|
238
|
+
"lower",
|
|
239
|
+
"go down",
|
|
240
|
+
"move down"
|
|
241
|
+
]
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
]
|
|
245
|
+
}
|
|
246
|
+
],
|
|
247
|
+
"modelConfiguration": {
|
|
248
|
+
"fallbackIntentSensitivity": {
|
|
249
|
+
"level": "HIGH"
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|