├── .gitignore
├── LICENSE
├── README.md
├── controls
├── README.md
├── agenda
│ ├── Agenda-CLEUR-About.png
│ ├── Agenda-CLEUR-Thursday.png
│ ├── Agenda-CLEUR-Tuesday.png
│ ├── Agenda-CLEUR-Wednesday.png
│ ├── README.md
│ ├── Touch10-Home.png
│ ├── agenda-CLANZ.xml
│ └── agenda-CLEUR.xml
├── agenda_notif
│ ├── README.md
│ ├── agenda.js
│ ├── agenda.xml
│ ├── img
│ │ ├── incoming_webhook.png
│ │ ├── push_to_teams.png
│ │ └── touch10_snapshot.png
│ ├── package.json
│ └── util
│ │ └── sessions.js
├── agenda_post
│ ├── README.md
│ ├── agenda.js
│ ├── agenda.xml
│ ├── img
│ │ ├── push_to_teams.png
│ │ └── touch10_snapshot.png
│ └── package.json
├── family_link
│ ├── README.md
│ ├── WebexQuickDial.js
│ ├── WebexQuickDial.xml
│ └── docs
│ │ └── ICU-DX.png
├── hue
│ ├── README.md
│ ├── control.xml
│ ├── docs
│ │ ├── control.png
│ │ └── light.png
│ ├── macro.js
│ └── updateLight.js
├── hue_disco
│ ├── README.md
│ ├── control.xml
│ └── macro.js
├── maze
│ ├── README.md
│ ├── map.png
│ ├── maze.js
│ ├── maze.png
│ └── maze.xml
├── maze_french
│ ├── README.md
│ ├── game.png
│ ├── levels-french.js
│ ├── levels-french.xml
│ ├── options.png
│ └── rules.png
├── maze_levels
│ ├── README.md
│ ├── game.png
│ ├── levels.js
│ ├── levels.xml
│ ├── map.png
│ ├── options.png
│ └── rules.png
├── maze_scores
│ ├── README.md
│ ├── game.png
│ ├── package.json
│ ├── rules.png
│ ├── score-jsxapi.js
│ ├── score.js
│ └── score.xml
├── notifier
│ ├── README.md
│ ├── img
│ │ ├── panel_notify.png
│ │ ├── panel_recipient.png
│ │ ├── push_to_teams.png
│ │ └── touch10_home.png
│ ├── notifier.js
│ ├── notifier.xml
│ ├── package.json
│ └── to_webex_teams.js
├── onair
│ ├── README.md
│ ├── control.xml
│ ├── macro.js
│ ├── multi.js
│ ├── package.json
│ └── script.js
├── proximity
│ ├── README.md
│ ├── proximity.js
│ ├── proximity.png
│ └── proximity.xml
└── ultrasound
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── ultrasound-macro.js
│ ├── ultrasound.js
│ ├── ultrasound.png
│ └── ultrasound.xml
├── docs
├── QuickStart.md
└── img
│ ├── controls-import-from-file.png
│ ├── controls-push-to-device.png
│ ├── macro-activate.png
│ ├── macro-import-from-file.png
│ └── playground
│ ├── 1-playground-first-launch.png
│ ├── 2-playground-import-file.png
│ ├── 3-playground-after-import.png
│ └── 4-playground-preview.png
├── jsxapi
├── .gitignore
├── 0-quickstart.js
├── 1-connect.js
├── 10-autoanswer.js
├── 11-update-wallpaper.js
├── 12-encrypt-nokeygen.js
├── 12-encrypt.js
├── 2-environment.js
├── 3-dial.js
├── 4-custom-messages.js
├── 5-rolling-messages.js
├── 6-branding-logo.js
├── 7-feedback-peoplecount.js
├── 8-listen-audio-volume.js
├── 9-listen-all-status.js
├── README.md
├── docs
│ ├── 4.0.0
│ │ ├── ast
│ │ │ └── source
│ │ │ │ ├── backend
│ │ │ │ ├── index.js.json
│ │ │ │ ├── tsh.js.json
│ │ │ │ └── ws.js.json
│ │ │ │ ├── cli.js.json
│ │ │ │ ├── index.js.json
│ │ │ │ ├── json-parser.js.json
│ │ │ │ ├── log.js.json
│ │ │ │ ├── transport
│ │ │ │ ├── ssh.js.json
│ │ │ │ ├── stream.js.json
│ │ │ │ └── tsh.js.json
│ │ │ │ └── xapi
│ │ │ │ ├── components.js.json
│ │ │ │ ├── exc.js.json
│ │ │ │ ├── feedback.js.json
│ │ │ │ ├── index.js.json
│ │ │ │ ├── mixins.js.json
│ │ │ │ └── rpc.js.json
│ │ ├── badge.svg
│ │ ├── class
│ │ │ └── src
│ │ │ │ ├── backend
│ │ │ │ ├── index.js~Backend.html
│ │ │ │ ├── tsh.js~TSHBackend.html
│ │ │ │ └── ws.js~WSBackend.html
│ │ │ │ ├── json-parser.js~JSONParser.html
│ │ │ │ ├── transport
│ │ │ │ └── stream.js~StreamTransport.html
│ │ │ │ └── xapi
│ │ │ │ ├── components.js~Component.html
│ │ │ │ ├── components.js~Config.html
│ │ │ │ ├── components.js~Event.html
│ │ │ │ ├── components.js~Status.html
│ │ │ │ ├── exc.js~IllegalValueError.html
│ │ │ │ ├── exc.js~InvalidPathError.html
│ │ │ │ ├── exc.js~ParameterError.html
│ │ │ │ ├── exc.js~XAPIError.html
│ │ │ │ ├── feedback.js~Feedback.html
│ │ │ │ ├── feedback.js~FeedbackGroup.html
│ │ │ │ ├── index.js~XAPI.html
│ │ │ │ ├── mixins.js~Gettable.html
│ │ │ │ ├── mixins.js~Listenable.html
│ │ │ │ └── mixins.js~Settable.html
│ │ ├── coverage.json
│ │ ├── css
│ │ │ ├── prettify-tomorrow.css
│ │ │ └── style.css
│ │ ├── dump.json
│ │ ├── file
│ │ │ └── src
│ │ │ │ ├── backend
│ │ │ │ ├── index.js.html
│ │ │ │ ├── tsh.js.html
│ │ │ │ └── ws.js.html
│ │ │ │ ├── cli.js.html
│ │ │ │ ├── index.js.html
│ │ │ │ ├── json-parser.js.html
│ │ │ │ ├── log.js.html
│ │ │ │ ├── transport
│ │ │ │ ├── ssh.js.html
│ │ │ │ ├── stream.js.html
│ │ │ │ └── tsh.js.html
│ │ │ │ └── xapi
│ │ │ │ ├── components.js.html
│ │ │ │ ├── exc.js.html
│ │ │ │ ├── feedback.js.html
│ │ │ │ ├── index.js.html
│ │ │ │ ├── mixins.js.html
│ │ │ │ └── rpc.js.html
│ │ ├── function
│ │ │ └── index.html
│ │ ├── identifiers.html
│ │ ├── image
│ │ │ ├── badge.svg
│ │ │ ├── github.png
│ │ │ └── search.png
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── script
│ │ │ ├── inherited-summary.js
│ │ │ ├── inner-link.js
│ │ │ ├── manual.js
│ │ │ ├── patch-for-local.js
│ │ │ ├── prettify
│ │ │ │ ├── Apache-License-2.0.txt
│ │ │ │ └── prettify.js
│ │ │ ├── pretty-print.js
│ │ │ ├── search.js
│ │ │ ├── search_index.js
│ │ │ └── test-summary.js
│ │ ├── source.html
│ │ └── variable
│ │ │ └── index.html
│ └── 4.2.0
│ │ ├── ast
│ │ └── source
│ │ │ ├── .external-ecmascript.js.json
│ │ │ ├── backend
│ │ │ ├── index.js.json
│ │ │ ├── tsh.js.json
│ │ │ └── ws.js.json
│ │ │ ├── cli.js.json
│ │ │ ├── index.js.json
│ │ │ ├── json-parser.js.json
│ │ │ ├── log.js.json
│ │ │ ├── transport
│ │ │ ├── ssh.js.json
│ │ │ ├── stream.js.json
│ │ │ └── tsh.js.json
│ │ │ └── xapi
│ │ │ ├── components.js.json
│ │ │ ├── exc.js.json
│ │ │ ├── feedback.js.json
│ │ │ ├── index.js.json
│ │ │ ├── mixins.js.json
│ │ │ ├── normalizePath.js.json
│ │ │ └── rpc.js.json
│ │ ├── badge.svg
│ │ ├── class
│ │ └── src
│ │ │ ├── backend
│ │ │ ├── index.js~Backend.html
│ │ │ ├── tsh.js~TSHBackend.html
│ │ │ └── ws.js~WSBackend.html
│ │ │ ├── json-parser.js~JSONParser.html
│ │ │ ├── transport
│ │ │ └── stream.js~StreamTransport.html
│ │ │ └── xapi
│ │ │ ├── components.js~Component.html
│ │ │ ├── components.js~Config.html
│ │ │ ├── components.js~Event.html
│ │ │ ├── components.js~Status.html
│ │ │ ├── exc.js~IllegalValueError.html
│ │ │ ├── exc.js~InvalidPathError.html
│ │ │ ├── exc.js~ParameterError.html
│ │ │ ├── exc.js~XAPIError.html
│ │ │ ├── feedback.js~Feedback.html
│ │ │ ├── feedback.js~FeedbackGroup.html
│ │ │ ├── index.js~XAPI.html
│ │ │ ├── mixins.js~Gettable.html
│ │ │ ├── mixins.js~Listenable.html
│ │ │ └── mixins.js~Settable.html
│ │ ├── coverage.json
│ │ ├── css
│ │ ├── github.css
│ │ ├── identifiers.css
│ │ ├── manual.css
│ │ ├── prettify-tomorrow.css
│ │ ├── search.css
│ │ ├── source.css
│ │ ├── style.css
│ │ └── test.css
│ │ ├── file
│ │ └── src
│ │ │ ├── backend
│ │ │ ├── index.js.html
│ │ │ ├── tsh.js.html
│ │ │ └── ws.js.html
│ │ │ ├── cli.js.html
│ │ │ ├── index.js.html
│ │ │ ├── json-parser.js.html
│ │ │ ├── log.js.html
│ │ │ ├── transport
│ │ │ ├── ssh.js.html
│ │ │ ├── stream.js.html
│ │ │ └── tsh.js.html
│ │ │ └── xapi
│ │ │ ├── components.js.html
│ │ │ ├── exc.js.html
│ │ │ ├── feedback.js.html
│ │ │ ├── index.js.html
│ │ │ ├── mixins.js.html
│ │ │ ├── normalizePath.js.html
│ │ │ └── rpc.js.html
│ │ ├── function
│ │ └── index.html
│ │ ├── identifiers.html
│ │ ├── image
│ │ ├── badge.svg
│ │ ├── esdoc-logo-mini-black.png
│ │ ├── esdoc-logo-mini.png
│ │ ├── manual-badge.svg
│ │ └── search.png
│ │ ├── index.html
│ │ ├── index.json
│ │ ├── lint.json
│ │ ├── script
│ │ ├── inherited-summary.js
│ │ ├── inner-link.js
│ │ ├── manual.js
│ │ ├── patch-for-local.js
│ │ ├── prettify
│ │ │ ├── Apache-License-2.0.txt
│ │ │ └── prettify.js
│ │ ├── pretty-print.js
│ │ ├── search.js
│ │ ├── search_index.js
│ │ └── test-summary.js
│ │ ├── source.html
│ │ └── variable
│ │ └── index.html
├── httpfeedback
│ ├── README.md
│ ├── package.json
│ └── server.js
├── img
│ ├── cisco-logo-white.png
│ └── create-logo-transparent.png
└── package.json
├── macros
├── 1-helloworld.js
├── 10-httpclient.js
├── 11-philips-hue.js
├── 12a-primary.js
├── 12b-secondary.js
├── 13a-environment.js
├── 13b-getenv.js
├── 14-persist.js
├── 15-cipher.js
├── 16-encrypt-rsa.js
├── 17-persistenv.js
├── 18-hue_disco.js
├── 19-hue_onair.js
├── 2-showVolume.js
├── 20-import_main.js
├── 20-import_util.js
├── 3-changeLanguage.js
├── 4-webhook.js
├── 4b-webhook-controls.js
├── 5-checkEmail.js
├── 6-maze.js
├── 7-showProgress.js
├── 8-message.js
├── 9-timer.js
├── README.md
└── pdf
│ └── macro-tutorial.pdf
├── python
├── .gitignore
├── advanced.py
├── requirements.txt
└── websocket.py
├── pyxows
├── README.md
├── connection_timeout.py
├── control_volume.py
└── usage.py
└── web
├── README.md
├── example.html
└── jsxapi-501.min.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode/
2 | node_modules/
3 | work/
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Cisco Systems
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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Handy samples for xAPI UI Extensions and Macros [](https://developer.cisco.com/codeexchange/github/repo/CiscoDevNet/xapi-samples)
2 |
3 | This repo can get you quickly ramp up with CE programmability and xAPI, with examples for:
4 | - [UI Extensions](./controls),
5 | - [JavaScript Macros](./macros),
6 | - [Node.js applications](./jsxapi),
7 | - [Python applications](./pyxows)
8 |
9 | **New to xAPI UI Extensions and Macros?**
10 | - check the [QuickStart Guide](./docs/QuickStart.md) to learn to load Controls and Macros to your device,
11 | - take a DevNet Tutorial from the xAPI track (intro to xAPI and creating custom UI Extensions)
12 |
13 | **Don't have a CE device at hand to mess up with?**
14 | - reserve a [DevNet sandbox](https://github.com/CiscoDevNet/awesome-xapi/#sandboxes) equiped with CE latest
15 |
16 | **Going Futher**
17 | Once you're done browsing the examples in this repo, here are a few suggestions
18 | - check the [official Macro Samples repository](https://github.com/CiscoDevNet/roomdevices-macros-samples)
19 | - load the [Postman collection for xAPI](https://github.com/CiscoDevNet/postman-xapi) to invoke the xAPI from code external to the Room Device
20 | - read through the full [CE Customization PDF Guide](https://www.cisco.com/c/dam/en/us/td/docs/telepresence/endpoint/ce99/collaboration-endpoint-software-api-reference-guide-ce99.pdf)
21 | - check for the curated list of resources at [awesome-xapi](https://github.com/CiscoDevNet/awesome-xapi)
22 |
23 |
24 | ## T-Shell Tips for developers
25 |
26 | Simply SSH to your Collaboration Device and run the commands below:
27 |
28 | ### Listen to events
29 |
30 | The 'xfeedback' commands let you see all events fired on your device.
31 | This is very useful to investigate possibilities, and take shortcut without going through the whole documentation at times.
32 |
33 | ```shell
34 | # Listen to all notifications (events, status, commands)
35 | xfeedback register /
36 | ```
37 |
38 | ```shell
39 | # Listen to UI Extensions events
40 | xfeedback register /Event/UserInterface/Extensions
41 | ```
42 |
43 | ```shell
44 | # Stop listening
45 | xfeedback deregisterall
46 | ```
47 |
48 | ### Send messages
49 |
50 | Sending messages lets you craft custom APIs, by coming up with your own protocols, aka, Event Driven Architectures.
51 | One code will send a serialized message, the other code will capture the message and decode it.
52 |
53 | ```shell
54 | # Listen to messages
55 | xfeedback register /Event/Message/Send
56 | ```
57 |
58 | ```shell
59 | # Send message
60 | xCommand Message Send Text: "This is random text"
61 | ```
62 |
63 | Check the [message Macro](macros/8-message.js) for an example in JavaScript.
--------------------------------------------------------------------------------
/controls/README.md:
--------------------------------------------------------------------------------
1 | # Examples of In-Room Controls
2 |
3 | Run these samples to explore a few of the possibilities of your Room Device programmability:
4 |
5 | **New to Controls & Macros? check the [QuickStart Guide](../docs/QuickStart.md) to learn to load Controls and Macros to your device**
6 |
7 | - [Agenda](./agenda): example of a control showing static information
8 | - [Ultrasound](./ultrasound): helper panel to update the Ultrasound volume
9 | - [Proximity](./proximity): helper panel to toggle Proximity Mode on/off
10 | - [Maze](./maze): Play an interactive game where you navigate blind in a maze, looking for a treasure. Check the more advanced versions with [a scoring system](./maze_scores) and [difficulty levels](./maze_levels)
11 |
12 |
--------------------------------------------------------------------------------
/controls/agenda/Agenda-CLEUR-About.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/agenda/Agenda-CLEUR-About.png
--------------------------------------------------------------------------------
/controls/agenda/Agenda-CLEUR-Thursday.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/agenda/Agenda-CLEUR-Thursday.png
--------------------------------------------------------------------------------
/controls/agenda/Agenda-CLEUR-Tuesday.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/agenda/Agenda-CLEUR-Tuesday.png
--------------------------------------------------------------------------------
/controls/agenda/Agenda-CLEUR-Wednesday.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/agenda/Agenda-CLEUR-Wednesday.png
--------------------------------------------------------------------------------
/controls/agenda/README.md:
--------------------------------------------------------------------------------
1 | # Example of a static Agenda
2 |
3 | **New to Controls & Macros? check the [QuickStart Guide](../../docs/QuickStart.md) to learn to load Controls and Macros to your device**
4 |
5 |
6 | This control does not invoke any macro, simply displays a static agenda.
7 |
8 | 
9 |
10 |
11 | With the example of my schedule at Cisco Live Europe 2018 - Barcelona
12 |
13 | 
14 |
15 | 
16 |
17 | 
18 |
19 | 
20 |
--------------------------------------------------------------------------------
/controls/agenda/Touch10-Home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/agenda/Touch10-Home.png
--------------------------------------------------------------------------------
/controls/agenda_notif/README.md:
--------------------------------------------------------------------------------
1 | # Agenda with notifications
2 |
3 | This agenda pushes session details to a Webex Teams space (via an incoming Webhook)
4 |
5 | 
6 |
7 |
8 | ## Quickstart
9 |
10 | Deploy the [control](./agenda.xml) to your device
11 |
12 | Create an [incoming webhook](https://apphub.webex.com/categories/other/integrations/incoming-webhooks-cisco-systems) from Webex App Hub Integrations
13 |
14 | Run the JS script.
15 | _Make sure to add an extra `INCOMING_WEBHOOK_ID` env variable filled with the identifier created earlier_
16 |
17 | ```shell
18 | git clone https://github.com/ObjectIsAdvantag/xapi-samples
19 | cd controls
20 | cd agenda_notif
21 | npm install
22 | INCOMING_WEBHOOK_ID="XXXXX" JSXAPI_DEVICE_URL='ssh://192.168.1.34' JSXAPI_USERNAME='integrator' JSXAPI_PASSWORD='integrator' node agenda.js
23 | ```
24 |
25 | Press 'push' and see messages poping up in Webex Teams.
26 |
27 | 
--------------------------------------------------------------------------------
/controls/agenda_notif/agenda.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2018 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | /**
7 | * Listen to UserInterfaace realtime events via xAPI's feedback function
8 | * Pushes events to a Webex Teams space via an incoming Webhook
9 | * Use the INCOMING_WEBHOOK_ID env variable to override default space
10 | */
11 |
12 | // Incoming webhook id
13 | var webhook_id = process.env.INCOMING_WEBHOOK_ID;
14 | if (!webhook_id) {
15 | console.log("WARNING: no incoming webhook id specified on the command line, using defaults.");
16 | console.log("to join the default Webex Teams space, open https://eurl.io/#H1Cb0XKgQ")
17 | webhook_id = "Y2lzY29zcGFyazovL3VzL1dFQkhPT0svMTI5YzA4ZGQtODJlYy00ZTg2LThkODAtZWFiMjNjYWVmOTRm";
18 | }
19 |
20 | //
21 | // Connect to the device
22 | //
23 |
24 | const jsxapi = require('jsxapi');
25 |
26 | // Check args
27 | if (!process.env.JSXAPI_DEVICE_URL || !process.env.JSXAPI_USERNAME) {
28 | console.error("Please specify info to connect to your device as JSXAPI_DEVICE_URL, JSXAPI_USERNAME, JSXAPI_PASSWORD env variables");
29 | console.error("Bash example: JSXAPI_DEVICE_URL='ssh://192.168.1.34' JSXAPI_USERNAME='integrator' JSXAPI_PASSWORD='integrator' node agenda.js");
30 | process.exit(1);
31 | }
32 |
33 | // Empty passwords are supported
34 | const password = process.env.JSXAPI_PASSWORD ? process.env.JSXAPI_PASSWORD : "";
35 |
36 | // Connect to the device
37 | console.log("connecting to your device...");
38 | const xapi = jsxapi.connect(process.env.JSXAPI_DEVICE_URL, {
39 | username: process.env.JSXAPI_USERNAME,
40 | password: password
41 | });
42 | xapi.on('error', (err) => {
43 | switch (err) {
44 | case "client-socket":
45 | console.error("could not connect: invalid URL.");
46 | break;
47 |
48 | case "client-authentication":
49 | console.error("could not connect: invalid credentials.");
50 | break;
51 |
52 | case "client-timeout":
53 | console.error("could not connect: timeout.");
54 | break;
55 |
56 | default:
57 | console.error(`encountered error: ${err}.`);
58 | break;
59 | }
60 |
61 | console.log("exiting...");
62 | process.exit(1);
63 | });
64 |
65 |
66 | //
67 | // Code logic
68 | //
69 |
70 | xapi.on('ready', () => {
71 | console.log("connexion successful");
72 |
73 | // Listen to custom In-Room Controls events
74 | console.log("added feedback event listener: UserInterface Extensions Event Clicked");
75 | xapi.event.on('UserInterface Extensions Event Clicked', (event) => {
76 | console.log(`new event from: ${event.Signal}`);
77 |
78 | // Identify session associated to button
79 | let sessionId = extractSession(event.Signal);
80 | if (!sessionId) {
81 | console.log("bad format, ignoring...");
82 | return;
83 | }
84 |
85 | // Fetch session details
86 | const getSessionDetails = require('./util/sessions.js')
87 | let session = getSessionDetails(sessionId);
88 | if (!session) {
89 | console.log("could not find details for session, ignoring...");
90 | return;
91 | }
92 |
93 | // Push info about the session
94 | let href = "https://www.ciscolive.com/us/learn/sessions/session-catalog/?search=" + sessionId;
95 | push(`${session.day}, ${session.time}: **${session.title}** in ${session.location}
_${session.description}_`);
96 | });
97 | });
98 |
99 | function extractSession(component) {
100 | //let parsed = component.match(/^push_(DEVNET-\d{4}[A:B]?)$/);
101 | let parsed = component.match(/^push_(.*)$/);
102 |
103 | if (!parsed) {
104 | console.log("format error, please comply with 'push_DEVNET-DDDD'");
105 | return undefined;
106 | }
107 |
108 | return parsed[1];
109 | }
110 |
111 | // Post message to a Webex Teams space via an Incoming Webhook
112 | function push(msg, cb) {
113 | var request = require("request");
114 |
115 | // Post message
116 | let options = {
117 | method: 'POST',
118 | url: 'https://api.ciscospark.com/v1/webhooks/incoming/' + webhook_id,
119 | headers: { 'Content-Type': 'application/json' },
120 | body: { markdown: msg },
121 | json: true
122 | };
123 |
124 | request(options, function (err, response, body) {
125 | if (err) {
126 | if (cb) cb(err.message);
127 | return;
128 | }
129 |
130 | if (response.statusCode == 204) {
131 | console.log("message pushed to Webex Teams");
132 | if (cb) cb(null);
133 | return;
134 | }
135 |
136 | if (cb) cb("Could not post message to Webex Teams");
137 | });
138 | }
139 |
--------------------------------------------------------------------------------
/controls/agenda_notif/img/incoming_webhook.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/agenda_notif/img/incoming_webhook.png
--------------------------------------------------------------------------------
/controls/agenda_notif/img/push_to_teams.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/agenda_notif/img/push_to_teams.png
--------------------------------------------------------------------------------
/controls/agenda_notif/img/touch10_snapshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/agenda_notif/img/touch10_snapshot.png
--------------------------------------------------------------------------------
/controls/agenda_notif/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "agenda_notif",
3 | "version": "0.1.0",
4 | "description": "In-Room Control to display an list of session, and push to a Webex teams.",
5 | "scripts": {
6 | "test": "echo \"Error: no test specified\" && exit 1"
7 | },
8 | "author": "Stève Sfartz ",
9 | "license": "MIT",
10 | "dependencies": {
11 | "jsxapi": "^4.1.3",
12 | "request": "^2.87.0"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/controls/agenda_notif/util/sessions.js:
--------------------------------------------------------------------------------
1 | //
2 | // Utilities: catalog of sessions
3 | //
4 |
5 | // List of sessions
6 | const sessions = {};
7 | sessions["steve"] = {
8 | id: "push_steve",
9 | title: "KeyNote - When apps meet infrastructure",
10 | description: "...",
11 | location: "Room 1",
12 | type: "keynote",
13 | day: "Thursday",
14 | time: "09:30AM",
15 | duration: "15",
16 | speaker: "Stève Sfartz"
17 | }
18 |
19 | sessions["matt"] = {
20 | id: "push_matt",
21 | title: "My developer journey towards true hybrid cloud with Kubernetes",
22 | description: "...",
23 | location: "Room 6",
24 | type: "technical talk",
25 | day: "Thursday",
26 | time: "11:30AM",
27 | duration: "40",
28 | speaker: "Matt Johnson"
29 | }
30 |
31 | sessions["roger"] = {
32 | id: "push_roger",
33 | title: "Making Enterprise Virtual Reality a Practical Reality",
34 | description: "...",
35 | location: "Room 6",
36 | type: "technical talk",
37 | day: "Thursday",
38 | time: "12:30PM",
39 | duration: "40",
40 |
41 | }
42 |
43 | sessions["cory"] = {
44 | id: "push_cory",
45 | title: "API Magic and Applications on the Network [Cory Guynn]",
46 | description: "...",
47 | location: "Room 6",
48 | type: "technical talk",
49 | day: "Thursday",
50 | time: "2:10PM",
51 | duration: "4O"
52 | }
53 |
54 | sessions["challenge"] = {
55 | id: "push_challenge",
56 | title: "Grab the Bag Challenge & Demos",
57 | description: "...",
58 | location: "Cisco Booth",
59 | type: "activity",
60 | day: "Thursday/Friday",
61 | time: "Till 4:00PM",
62 | duration: "300"
63 | }
64 |
65 | sessions["labs"] = {
66 | id: "push_labs",
67 | title: "Discover Kubernetes, Meraki and Webex Devices",
68 | description: "...",
69 | location: "Hands-On Labs",
70 | type: "activity",
71 | day: "Thursday",
72 | time: "11:30AM",
73 | duration: "360"
74 | }
75 |
76 | sessions["demos"] = {
77 | id: "push_demos",
78 | title: "Meet Cisco DevNet: Meraki & Webex Devices Demos",
79 | description: "...",
80 | location: "Cisco Booth",
81 | type: "activity",
82 | day: "Thursday/Friday",
83 | time: "Til 6:00PM",
84 | duration: "360"
85 | }
86 |
87 |
88 | // Export function
89 | module.exports = function getInfo(sessionId) {
90 | return sessions[sessionId];
91 | }
92 |
--------------------------------------------------------------------------------
/controls/agenda_post/README.md:
--------------------------------------------------------------------------------
1 | # Agenda with notifications (via Bot Account)
2 |
3 | This agenda pushes session details to a Webex Teams space, via a Bot Account posting to the space.
4 |
5 | 
6 |
7 |
8 | ## Quickstart
9 |
10 | Deploy the [control](./agenda.xml) to your device
11 |
12 | Create a [bot account](https://apphub.webex.com/categories/other/integrations/incoming-webhooks-cisco-systems) from Webex for Developers, and paste the bot token into the code below.
13 |
14 | Run the JS script.
15 |
16 | ```shell
17 | git clone https://github.com/ObjectIsAdvantag/xapi-samples
18 | cd controls
19 | cd agenda_post
20 | npm install
21 | JSXAPI_DEVICE_URL='ssh://192.168.1.34' JSXAPI_USERNAME='integrator' JSXAPI_PASSWORD='integrator' node agenda.js
22 | ```
23 |
24 | Press 'push' and see messages poping up in Webex Teams.
25 |
26 | 
--------------------------------------------------------------------------------
/controls/agenda_post/img/push_to_teams.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/agenda_post/img/push_to_teams.png
--------------------------------------------------------------------------------
/controls/agenda_post/img/touch10_snapshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/agenda_post/img/touch10_snapshot.png
--------------------------------------------------------------------------------
/controls/agenda_post/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "agenda_notif",
3 | "version": "0.1.0",
4 | "description": "In-Room Control to display an list of session, and push to a Webex teams.",
5 | "scripts": {
6 | "test": "echo \"Error: no test specified\" && exit 1"
7 | },
8 | "author": "Stève Sfartz ",
9 | "license": "MIT",
10 | "dependencies": {
11 | "jsxapi": "^4.2.0"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/controls/family_link/README.md:
--------------------------------------------------------------------------------
1 | # Intensive Care Unit - Family Link
2 |
3 | The WebexQuickDial XML menu presents a simple way for clinical staff to start a Personal Meeting Room for a bedspace (below showing ICU-101 -> ICU-108)
4 |
5 | 
6 |
7 |
8 | The WebexQuickDial.js file initiates a call into Personal Meeting Room related to individual buttons:
9 |
10 | - MATCH_STRING: uses the value entered in the XML file for the respective button
11 | - const DIALPREFIX_WEBEXURL: this value is replaced with the text on the button control after wqd- and will use it to join the actual PMR
12 | - const DIALPREPOSTFIX_WEBEXURL: this is the customer’s webex site
13 | - const DIALPOSTFIX_WEBEXURL: '@webex.com'; added after the customer’s site
14 | - let hostpin = '1234'; this needs to change, and reflect the actual Host PIN configured for the PMR
15 |
--------------------------------------------------------------------------------
/controls/family_link/WebexQuickDial.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 | // by Peter Welburn (petwelbu@cisco.com)
6 | //
7 |
8 | const xapi = require('xapi');
9 |
10 | // Set this is you don't want to prompt users for a PIN
11 | let hostpin = '1234';
12 |
13 | // This is what every widget on the UI should have as a prefix for us to make a webex call
14 | // REPLACE with your UI Extensions
15 | const MATCH_STRING = 'wqd-';
16 |
17 | // REPLACE with values for your Webex Devicces deployment
18 | const DIALPREFIX_WEBEXURL = 'devnet';
19 | const DIALPREPOSTFIX_WEBEXURL = '.devnetzone';
20 | const DIALPOSTFIX_WEBEXURL = '@webex.com';
21 |
22 | const KEYBOARD_TYPES = {
23 | NUMERIC : 'Numeric'
24 | , SINGLELINE : 'SingleLine'
25 | , PASSWORD : 'Password'
26 | , PIN : 'PIN'
27 | }
28 | const CALL_TYPES = {
29 | AUDIO : 'Audio'
30 | , VIDEO : 'Video'
31 | }
32 |
33 | const DIALPAD_ID = 'webexdialpad';
34 | const DIALHOSTPIN_ID = 'webexhostpin';
35 |
36 | const INROOMCONTROL_WEBEXCONTROL_PANELID = 'webexdialler';
37 | const INROOMCONTROL_WEBEXQUICKDIAL_PANELID = 'webexquickdial';
38 |
39 | /* Use these to check that its a valid number (depending on what you want to allow users to call */
40 | const REGEXP_URLDIALER = /([a-zA-Z0-9@_\-\.]+)/; /* . Use this one if you want to allow URL dialling */
41 | const REGEXP_NUMERICDIALER = /^([0-9]{1,2})$/; /* Use this one if you want to limit calls to numeric only. In this example, require number to be between 3 and 10 digits. */
42 |
43 | let webexnumbertodial = '';
44 | let isInWebexCall = 0;
45 |
46 | function sleep(ms) {
47 | return new Promise(resolve => setTimeout(resolve, ms));
48 | }
49 |
50 | xapi.event.on('CallDisconnect', (event) => {
51 | isInWebexCall = 0;
52 | });
53 |
54 | function showDialPad(text){
55 | xapi.command("UserInterface Message TextInput Display", {
56 | InputType: KEYBOARD_TYPES.NUMERIC
57 | , Placeholder: '1 or 2-digit room number'
58 | , Title: "Webex Call"
59 | , Text: text
60 | , SubmitText: "Next"
61 | , FeedbackId: DIALPAD_ID
62 | }).catch((error) => { console.error(error); });
63 | }
64 |
65 | /* This is the listener for the in-room control panel button that will trigger the dial panel to appear */
66 | xapi.event.on('UserInterface Extensions Panel Clicked', (event) => {
67 | if (event.PanelId === INROOMCONTROL_WEBEXCONTROL_PANELID){
68 | showDialPad("Enter the 1 or 2-digit room number:" );
69 | }
70 | });
71 |
72 | xapi.event.on('UserInterface Extensions Widget Action', (event) => {
73 | if (event.WidgetId.search(MATCH_STRING) !== -1) {
74 | webexnumbertodial = event.WidgetId.replace("wqd-", "")
75 | console.log(webexnumbertodial);
76 | webexnumbertodial = webexnumbertodial + DIALPREPOSTFIX_WEBEXURL + DIALPOSTFIX_WEBEXURL ;
77 | xapi.command("dial", {Number: webexnumbertodial}).catch((error) => { console.error(error); });
78 | }
79 | });
80 |
81 | xapi.event.on('UserInterface Message TextInput Response', (event) => {
82 | switch(event.FeedbackId) {
83 | case DIALPAD_ID:
84 | let regex = REGEXP_NUMERICDIALER; // First check, is it a valid number to dial
85 | let match = regex.exec(event.Text);
86 | if (match !== null) {
87 | let contains_at_regex = /@/;
88 | let contains_at_in_dialstring = contains_at_regex.exec(event.Text);
89 | if (contains_at_in_dialstring !== null) {
90 | webexnumbertodial = match[1];
91 | } else {
92 | webexnumbertodial = match[1];
93 | webexnumbertodial = DIALPREFIX_WEBEXURL + webexnumbertodial + DIALPREPOSTFIX_WEBEXURL + DIALPOSTFIX_WEBEXURL ; // Here we add the default hostname to the SIP number
94 | }
95 | if (hostpin === '') {
96 | sleep(200).then(() => { //this is a necessary trick to get it working with multiple touch panels to not mess up event-clears from other panels
97 | xapi.command("UserInterface Message TextInput Display", {
98 | InputType: KEYBOARD_TYPES.NUMERIC
99 | , Placeholder: "Hostpin (optional)"
100 | , Title: "Enter Host pin or leave blank"
101 | , Text: 'Webex call number:' + webexnumbertodial
102 | , SubmitText: "Dial"
103 | , FeedbackId: DIALHOSTPIN_ID
104 | }).catch((error) => { console.error(error); });
105 | });
106 | } else {
107 | xapi.command("dial", {Number: webexnumbertodial}).catch((error) => { console.error(error); });
108 | }
109 | } else {
110 | showDialPad("You typed in an invalid number. Enter a 1 or 2-digit number." );
111 | }
112 | break;
113 |
114 | case DIALHOSTPIN_ID:
115 | hostpin = event.Text;
116 | xapi.command("dial", {Number: webexnumbertodial}).catch((error) => { console.error(error); });
117 | break;
118 | }
119 | });
120 |
121 | xapi.status.on('Conference Call', (callInfo) => {
122 | if (callInfo.AuthenticationRequest === 'HostPinOrGuest') {
123 | isInWebexCall = 1;
124 | console.log(callInfo);
125 | xapi.command('Conference Call AuthenticationResponse', {
126 | CallId: callInfo.id,
127 | ParticipantRole: 'Host',
128 | Pin: hostpin + '#',
129 | });
130 | }
131 | });
--------------------------------------------------------------------------------
/controls/family_link/WebexQuickDial.xml:
--------------------------------------------------------------------------------
1 |
2 | 1.7
3 |
4 | 1
5 | webexquickdial
6 | local
7 | Home
8 | Webex
9 | #FF7033
10 | Join Webex
11 | Custom
12 |
13 | ICU Webex
14 |
15 | Row
16 |
17 | wqd-icu-1
18 | ICU-1
19 | Button
20 | size=2
21 |
22 |
23 | wqd-icu-2
24 | ICU-2
25 | Button
26 | size=2
27 |
28 |
29 | wqd-icu-3
30 | ICU-3
31 | Button
32 | size=2
33 |
34 |
35 | wqd-icu-4
36 | ICU-4
37 | Button
38 | size=2
39 |
40 |
41 | wqd-icu-5
42 | ICU-5
43 | Button
44 | size=2
45 |
46 |
47 | wqd-icu-6
48 | ICU-6
49 | Button
50 | size=2
51 |
52 |
53 | wqd-icu-7
54 | ICU-7
55 | Button
56 | size=2
57 |
58 |
59 | wqd-icu-8
60 | ICU-8
61 | Button
62 | size=2
63 |
64 |
65 | wqd-icu-9
66 | ICU-9
67 | Button
68 | size=2
69 |
70 |
71 | wqd-icu-10
72 | ICU-10
73 | Button
74 | size=2
75 |
76 |
77 |
78 | Row
79 |
80 | wqd-icuisolation-1
81 | ICU Isolation 1
82 | Button
83 | size=2
84 |
85 |
86 | wqd-icuisolation-2
87 | ICU Isolation 2
88 | Button
89 | size=2
90 |
91 |
92 | hideRowNames=1
93 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/controls/family_link/docs/ICU-DX.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/family_link/docs/ICU-DX.png
--------------------------------------------------------------------------------
/controls/hue/README.md:
--------------------------------------------------------------------------------
1 | # Philips Hue demo
2 |
3 | Control a Philips Hue Light from a Cisco Collaboration Device.
4 |
5 | Steps:
6 | - Open the [macro](./macro.js) and update BRIDGE_IP, BRIDGE_USER with your deployment info
7 | - Load the macro and the panel to your CE9.6+ capable device
8 |
9 |
10 | 
11 |
12 |
13 | 
14 |
--------------------------------------------------------------------------------
/controls/hue/control.xml:
--------------------------------------------------------------------------------
1 |
2 | 1.5
3 |
4 | Hue
5 | Statusbar
6 | Sliders
7 | 2
8 | #A866FF
9 | Philips Hue
10 |
11 | Basic
12 |
13 | Switch
14 |
15 | toggle
16 | ToggleButton
17 | size=1
18 |
19 |
20 | widget_22
21 |
22 | Text
23 | size=3;fontSize=normal;align=center
24 |
25 |
26 |
27 | Pick a color
28 |
29 | color_group
30 | GroupButton
31 | size=4
32 |
33 |
34 | color_red
35 | Red
36 |
37 |
38 | color_green
39 | Green
40 |
41 |
42 | color_blue
43 | Blue
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | Advanced
52 |
53 | Saturation
54 |
55 | widget_19
56 | Slider
57 | size=4
58 |
59 |
60 |
61 | Brightness
62 |
63 | widget_20
64 | Slider
65 | size=4
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/controls/hue/docs/control.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/hue/docs/control.png
--------------------------------------------------------------------------------
/controls/hue/docs/light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/hue/docs/light.png
--------------------------------------------------------------------------------
/controls/hue/macro.js:
--------------------------------------------------------------------------------
1 | const xapi = require('xapi')
2 |
3 |
4 | // Update for your Hue deployment
5 | const BRIDGE_IP = '192.168.1.33'
6 | const BRIDGE_USER = 'SECRET'
7 | const LIGHT_ID = 1 // number of your Bulb as registered at your Hue Bridge
8 |
9 |
10 | //
11 | // Interact with Hue Lights
12 | //
13 |
14 | const COLOR_RED = 65535
15 | const COLOR_BLUE = 46920
16 | const COLOR_GREEN = 25500
17 |
18 | function changeColorForLight(light, color) {
19 | console.debug(`changeColor: ${color} ForLight: ${light}`)
20 | updateLight(BRIDGE_IP, BRIDGE_USER, light, { "hue": color }, console.log)
21 | }
22 |
23 | function toggleLight(light, bool) {
24 | console.debug(`toggleLight: ${light} to: ${bool}`)
25 | updateLight(BRIDGE_IP, BRIDGE_USER, light, { "on": bool }, console.log)
26 | }
27 |
28 | function updateLight(bridgeip, username, light, payload, cb) {
29 | console.debug('updateLight: pushing payload')
30 | console.debug(`bridgeip: ${bridgeip} light: ${light} payload: ${JSON.stringify(payload)}`)
31 |
32 | // Post message
33 | xapi.command(
34 | 'HttpClient Put',
35 | {
36 | Header: ["Content-Type: application/json"],
37 | Url: `http://${bridgeip}/api/${username}/lights/${light}/state`
38 | },
39 | JSON.stringify(payload))
40 | .then((response) => {
41 | if (response.StatusCode == 200) {
42 | console.log("message pushed to bridge")
43 | if (cb) cb(null, response.StatusCode)
44 | return
45 | }
46 |
47 | console.warn("updateLight: request failed with status code: " + response.StatusCode)
48 | if (cb) cb("failed with status code: " + response.StatusCode, response.StatusCode)
49 | })
50 | .catch((err) => {
51 | console.error("updateLight: failed with err: " + err.message)
52 | if (cb) cb("Could not contact the bridge")
53 | })
54 | }
55 |
56 |
57 | // Listen to In-Room control events
58 | xapi.event.on('UserInterface Extensions Widget Action', (action) => {
59 | console.log(`new event from group: ${action.WidgetId}`)
60 |
61 | // Toggle (on/off)
62 | if ((action.WidgetId === 'toggle') && (action.Type === 'changed')) {
63 | console.info(`toggling light to: ${action.Value}`)
64 |
65 | // [WORKAROUND] Switch Light and UI to Red as we cannot read from the macro
66 | changeColorForLight(LIGHT_ID, COLOR_RED)
67 | xapi.command('UserInterface Extensions Widget SetValue', {
68 | WidgetId: 'color_group',
69 | Value: 'color_red'
70 | })
71 |
72 | // Switch on
73 | toggleLight(LIGHT_ID, (action.Value === 'on'))
74 | return
75 | }
76 |
77 | // Color
78 | if (action.WidgetId === 'color_group') {
79 | console.info(`color change requested by: ${action.Value}`)
80 | switch (action.Value) {
81 | case 'color_red':
82 | changeColorForLight(LIGHT_ID, COLOR_RED)
83 | break
84 | case 'color_blue':
85 | changeColorForLight(LIGHT_ID, COLOR_BLUE)
86 | break
87 | case 'color_green':
88 | changeColorForLight(LIGHT_ID, COLOR_GREEN)
89 | break
90 | default:
91 | console.log(`WARNING: unsupported color change request: suspected wrong widget id: ${action.Value}`)
92 | break
93 | }
94 | return
95 | }
96 | })
97 |
98 | // Init at launch
99 | xapi.event.on('UserInterface Extensions Panel Clicked PanelId', (panel) => {
100 | console.debug(`new panel opened group: ${panel}`)
101 |
102 | // Toggle (on/off)
103 | if (panel === 'Hue') {
104 | console.log(`resetting panel: turning off, and color to red`)
105 |
106 | // update lights
107 | changeColorForLight(LIGHT_ID, COLOR_RED)
108 | toggleLight(LIGHT_ID, false)
109 |
110 | // update UI
111 | xapi.command('UserInterface Extensions Widget SetValue', {
112 | WidgetId: 'color_group',
113 | Value: 'color_red'
114 | })
115 | xapi.command('UserInterface Extensions Widget SetValue', {
116 | WidgetId: 'toggle',
117 | Value: 'Off'
118 | })
119 |
120 | return
121 | }
122 | })
123 |
124 |
125 | console.info('Philips Hue macro listening...')
126 |
--------------------------------------------------------------------------------
/controls/hue/updateLight.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Posts a payload to the specified light number of a Hue Bridge
3 | *
4 | */
5 |
6 | const xapi = require('xapi');
7 |
8 | function updateLight(bridgeip, username, light, payload, cb) {
9 |
10 | // Post message
11 | xapi.command(
12 | 'HttpClient Put',
13 | {
14 | Header: ["Content-Type: application/json"],
15 | Url: `http://${bridgeip}/api/${username}/lights/${light}/state`
16 | },
17 | JSON.stringify(payload))
18 | .then((response) => {
19 | if (response.StatusCode == 200) {
20 | console.log("message pushed to bridge")
21 | if (cb) cb(null, response.StatusCode)
22 | return
23 | }
24 |
25 | console.log("failed with status code: " + response.StatusCode)
26 | if (cb) cb("failed with status code: " + response.StatusCode, response.StatusCode)
27 | })
28 | .catch((err) => {
29 | console.log("failed with err: " + err.message)
30 | if (cb) cb("Could not contact the bridge")
31 | })
32 | }
33 |
34 | // Update for your Hue deployment
35 | const BRIDGE_IP = '192.168.1.33'
36 | const BRIDGE_USER = 'SECRET'
37 | const LIGHT_ID = 1 // number of your Bulb as registered at your Hue Bridge
38 |
39 | // Turn green color
40 | updateLight(BRIDGE_IP, BRIDGE_USER, LIGHT_ID, { "hue": 25500 }, console.log)
41 |
--------------------------------------------------------------------------------
/controls/hue_disco/README.md:
--------------------------------------------------------------------------------
1 | # Turn your meeting room into a Hue Disco
2 |
3 | Configure and deploy the macro
4 |
5 | Deploy the UI Extension
6 |
7 | Play disco!
8 |
--------------------------------------------------------------------------------
/controls/hue_disco/control.xml:
--------------------------------------------------------------------------------
1 |
2 | 1.6
3 |
4 | DISCO
5 | Home
6 | Lightbulb
7 | 1
8 | #FFB400
9 | Disco
10 | Custom
11 |
12 | Settings
13 |
14 | Blink
15 |
16 | DISCO_slider
17 | Slider
18 | size=4
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/controls/hue_disco/macro.js:
--------------------------------------------------------------------------------
1 | const xapi = require('xapi');
2 |
3 | xapi.on('ready', init);
4 |
5 | function init() {
6 | let slider = 0;
7 | xapi.event.on('UserInterface Extensions Widget Action', (event) => {
8 | if ((event.WidgetId == 'DISCO_slider') && (event.Type == 'released')) {
9 | console.debug(`updated slider to: ${event.Value}`);
10 | slider = event.Value;
11 | }
12 | });
13 | xapi.command('UserInterface Extensions Widget SetValue', {
14 | WidgetId: 'DISCO_slider',
15 | Value: 0
16 | })
17 | .catch((err) => {
18 | if (err.message == 'Unknown widget: \'DISCO_slider\'') {
19 | console.warn('is the Disco Control Panel deployed? no worries I can live with it...');
20 | }
21 | })
22 |
23 | const MIN_DELAY = 300;
24 | function latestDelay(max) {
25 | let delay = Math.round((255 - slider) / 255 * max) + MIN_DELAY;
26 | console.debug(`new delay: ${delay}`)
27 | return delay;
28 | }
29 |
30 | disco(latestDelay, 2200, false);
31 | }
32 |
33 |
34 | // Blinks with random colors with a delay that can be dynamically changed
35 | // example: disco(latestDelay, 3000, false);
36 |
37 | function disco(getDelay, maxDelay, black) {
38 | // Get current value for delay
39 | let delay = getDelay(maxDelay);
40 |
41 | // Randomize color
42 | let color = COLOR_BLACK;
43 | if (delay < 800 || !black) {
44 | color = {
45 | hue: Math.round(Math.random() * 65535),
46 | on: true,
47 | sat: 255
48 | };
49 |
50 | }
51 | black = !black;
52 | changeColor(color);
53 |
54 | // Randomize delay
55 | setTimeout(function () {
56 | disco(getDelay, maxDelay, black);
57 | }, delay);
58 | }
59 |
60 |
61 | //
62 | // Hue Library
63 | //
64 |
65 | // ACTION: Configure for your Philips Hue deployment
66 | const HUE_BRIDGE = '192.168.1.33';
67 | const HUE_USERNAME = 'EM2Vg2GtNUqAASukv47wm1pWY0FayFe48D03f6Cb';
68 | const HUE_LIGHT = 1; // number of the light in your deployment
69 |
70 | console.info(`Hue Bridge with ip: ${HUE_BRIDGE}, bulb: ${HUE_LIGHT}`);
71 | const hue_url = `http://${HUE_BRIDGE}/api/${HUE_USERNAME}/lights/${HUE_LIGHT}/state`
72 |
73 | const COLOR_RED = { on: true, hue: 65535, sat: 255 };
74 | const COLOR_BLUE = { on: true, hue: 46920, sat: 255 };
75 | const COLOR_GREEN = { on: true, hue: 25500, sat: 255 };
76 | const COLOR_WHITE = { on: true, hue: 0, sat: 0 };
77 | const COLOR_BLACK = { on: false };
78 |
79 | function changeColor(color) {
80 |
81 | console.debug(`posting to hue bulb: ${JSON.stringify(color)}`)
82 |
83 | // Post message
84 | xapi.command(
85 | 'HttpClient Put',
86 | {
87 | Header: ["Content-Type: application/json"],
88 | Url: hue_url,
89 | AllowInsecureHTTPS: "True"
90 | },
91 | JSON.stringify(color))
92 | .catch((err) => {
93 | console.error('could not contact the Hue Bridge');
94 | });
95 | }
96 |
--------------------------------------------------------------------------------
/controls/maze/README.md:
--------------------------------------------------------------------------------
1 | # Play an interactive game
2 |
3 | **New to Controls & Macros? check the [QuickStart Guide](../../docs/QuickStart.md) to learn to load Controls and Macros to your device**
4 |
5 |
6 | Navigate blind in a maze, look for the treasure.
7 |
8 | 
9 |
10 |
11 | Hit 'Help' to check your current position against the map.
12 |
13 | 
14 |
15 |
16 | Check these more advanced versions of the game with [a scoring system](../maze_scores) and [difficulty levels](../maze_levels).
17 |
--------------------------------------------------------------------------------
/controls/maze/map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/maze/map.png
--------------------------------------------------------------------------------
/controls/maze/maze.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/maze/maze.png
--------------------------------------------------------------------------------
/controls/maze/maze.xml:
--------------------------------------------------------------------------------
1 |
2 | 1.4
3 |
4 | Disc
5 | Home
6 |
7 | Play the Maze
8 |
9 | Instructions
10 |
11 | instructions
12 | Then here you are, lost in an hostile maze, looking for the treasure. Pick a direction...
13 | Text
14 | size=4;align=left;fontSize=normal
15 |
16 |
17 |
18 | Pick a direction
19 |
20 | directions
21 | help
22 | DirectionalPad
23 | size=4
24 |
25 |
26 |
27 | Restart
28 |
29 | restart
30 | play again
31 | Button
32 | size=2
33 |
34 |
35 | unused
36 |
37 | Text
38 | size=2
39 |
40 |
41 | PlayMaze
42 | hideRowNames=0
43 |
44 | Play
45 |
46 |
--------------------------------------------------------------------------------
/controls/maze_french/README.md:
--------------------------------------------------------------------------------
1 | # Maze Game with Scoring & Levels
2 |
3 | **New to Controls & Macros? check the [QuickStart Guide](../../docs/QuickStart.md) to learn to load Controls and Macros to your device**
4 |
5 |
6 | French version of the Maze game with scoring system and three difficulty levels.
7 |
8 | 
9 |
10 |
11 | Check the rules
12 |
13 | 
14 |
15 |
16 | Change difficulty level as you succeed
17 |
18 | 
--------------------------------------------------------------------------------
/controls/maze_french/game.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/maze_french/game.png
--------------------------------------------------------------------------------
/controls/maze_french/options.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/maze_french/options.png
--------------------------------------------------------------------------------
/controls/maze_french/rules.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/maze_french/rules.png
--------------------------------------------------------------------------------
/controls/maze_levels/README.md:
--------------------------------------------------------------------------------
1 | # Maze Game with Scoring & Levels
2 |
3 | **New to Controls & Macros? check the [QuickStart Guide](../../docs/QuickStart.md) to learn to load Controls and Macros to your device**
4 |
5 |
6 | This version of the Maze game incorporates a scoring system doubled with several difficulty levels.
7 |
8 | 
9 |
10 |
11 | Concretely, the maze will get bigger if you choose a more advanced level.
12 |
13 | 
--------------------------------------------------------------------------------
/controls/maze_levels/game.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/maze_levels/game.png
--------------------------------------------------------------------------------
/controls/maze_levels/levels.xml:
--------------------------------------------------------------------------------
1 |
2 | 1.4
3 |
4 | Disc
5 | Home
6 |
7 | Play the Maze
8 |
9 | Score
10 |
11 | score
12 |
13 | Text
14 | size=2;fontSize=normal
15 |
16 |
17 | restart
18 | restart
19 | Button
20 | size=2
21 |
22 |
23 |
24 | Instructions
25 |
26 | instructions
27 | Then here you are, lost in an hostile maze, looking for the treasure. Pick a direction...
28 | Text
29 | size=4;align=left;fontSize=normal
30 |
31 |
32 |
33 | Pick a direction
34 |
35 | directions
36 | help
37 | DirectionalPad
38 | size=4
39 |
40 |
41 | hideRowNames=0
42 |
43 |
44 | Rules
45 |
46 | Instructions
47 |
48 | rules
49 | Navigate around the maze and find the treasure. Press 'Help' to show the map. Note that it will cost you 500 points every time you ask for it.
50 | Text
51 | size=4;fontSize=small
52 |
53 |
54 |
55 | Symbols
56 |
57 | symbol_location
58 | 'o' : your current location
59 | Text
60 | size=2;fontSize=small
61 |
62 |
63 | symbol_treasure
64 | '?' : the treasure (+5.000)
65 | Text
66 | size=2;fontSize=small
67 |
68 |
69 |
70 |
71 |
72 | symbol_nothing
73 | '_' : nothing here (+50)
74 | Text
75 | size=2;fontSize=small
76 |
77 |
78 | symbol_border
79 | '|' : maze border (-200)
80 | Text
81 | size=2;fontSize=small
82 |
83 |
84 |
85 |
86 |
87 | symbol_wall
88 | 'X' : a wall (-100)
89 | Text
90 | size=2;fontSize=small
91 |
92 |
93 | symbol_cat
94 | 'C' : a cat (+200)
95 | Text
96 | size=2;fontSize=small
97 |
98 |
99 |
100 |
101 |
102 | symbol_dog
103 | 'D' : a dog (-200)
104 | Text
105 | size=2;fontSize=small
106 |
107 |
108 | symbol_monster
109 | 'M': a monster (-500)
110 | Text
111 | size=2;fontSize=small
112 |
113 |
114 | hideRowNames=0
115 |
116 |
117 | Options
118 |
119 | Level
120 |
121 | difficulty
122 | GroupButton
123 | size=4
124 |
125 |
126 | 0
127 | Rookie
128 |
129 |
130 | 1
131 | Seasoned
132 |
133 |
134 | 2
135 | Expert
136 |
137 |
138 |
139 |
140 |
141 | Version
142 |
143 | unused
144 | v1.0.0
145 | Text
146 | size=4;fontSize=normal;align=left
147 |
148 |
149 |
150 | About
151 |
152 | widget_1
153 | by @SteveSfartz (developer.cisco.com)
154 | Text
155 | size=4;fontSize=normal
156 |
157 |
158 | hideRowNames=0
159 |
160 | Play
161 |
162 |
--------------------------------------------------------------------------------
/controls/maze_levels/map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/maze_levels/map.png
--------------------------------------------------------------------------------
/controls/maze_levels/options.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/maze_levels/options.png
--------------------------------------------------------------------------------
/controls/maze_levels/rules.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/maze_levels/rules.png
--------------------------------------------------------------------------------
/controls/maze_scores/README.md:
--------------------------------------------------------------------------------
1 | # Maze Game with Scoring
2 |
3 | **New to Controls & Macros? check the [QuickStart Guide](../../docs/QuickStart.md) to learn to load Controls and Macros to your device**
4 |
5 |
6 | This version of the Maze game incorporates a scoring system:
7 |
8 | 
9 |
10 |
11 | Moreover, the rules are exposed in a dedicated tab:
12 |
13 | 
14 |
--------------------------------------------------------------------------------
/controls/maze_scores/game.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/maze_scores/game.png
--------------------------------------------------------------------------------
/controls/maze_scores/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "maze_scores",
3 | "version": "0.1.0",
4 | "description": "Maze powered by a macro, with scoring system",
5 | "scripts": {
6 | "test": "echo \"Error: no test specified\" && exit 1"
7 | },
8 | "author": "Stève Sfartz ",
9 | "license": "MIT",
10 | "dependencies": {
11 | "jsxapi": "^4.1.3"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/controls/maze_scores/rules.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/maze_scores/rules.png
--------------------------------------------------------------------------------
/controls/maze_scores/score.xml:
--------------------------------------------------------------------------------
1 |
2 | 1.4
3 |
4 | Disc
5 | Home
6 |
7 | Play the Maze
8 |
9 | Score
10 |
11 | score
12 | not started
13 | Text
14 | size=2;fontSize=normal
15 |
16 |
17 | restart
18 | restart
19 | Button
20 | size=2
21 |
22 |
23 |
24 | Instructions
25 |
26 | instructions
27 | Then here you are, lost in an hostile maze, looking for the treasure. Pick a direction...
28 | Text
29 | size=4;align=left;fontSize=normal
30 |
31 |
32 |
33 | Pick a direction
34 |
35 | directions
36 | help
37 | DirectionalPad
38 | size=4
39 |
40 |
41 | PlayMaze
42 | hideRowNames=0
43 |
44 |
45 | Rules
46 |
47 | Instructions
48 |
49 | rules
50 | Navigate around the maze and find the treasure. Press help to show the map, note that it will cost you 500 points every time you ask for it.
51 | Text
52 | size=4;fontSize=small
53 |
54 |
55 |
56 | Symbols
57 |
58 | symbol_location
59 | 'o' : your current location
60 | Text
61 | size=2;fontSize=small
62 |
63 |
64 | symbol_treasure
65 | '?' : the treasure (+5.000)
66 | Text
67 | size=2;fontSize=small
68 |
69 |
70 |
71 |
72 |
73 | symbol_nothing
74 | '_' : nothing here (+50)
75 | Text
76 | size=2;fontSize=small
77 |
78 |
79 | symbol_border
80 | '|' : maze border (-200)
81 | Text
82 | size=2;fontSize=small
83 |
84 |
85 |
86 |
87 |
88 | symbol_wall
89 | 'X' : a wall (-100)
90 | Text
91 | size=2;fontSize=small
92 |
93 |
94 | symbol_cat
95 | 'C' : a cat (+200)
96 | Text
97 | size=2;fontSize=small
98 |
99 |
100 |
101 |
102 |
103 | symbol_dog
104 | 'D' : a dog (-200)
105 | Text
106 | size=2;fontSize=small
107 |
108 |
109 | symbol_monster
110 | 'M': a monster (-500)
111 | Text
112 | size=2;fontSize=small
113 |
114 |
115 |
116 | About
117 |
118 | about
119 | by @SteveSfartz (https://developer.cisco.com)
120 | Text
121 | size=4;align=left;fontSize=small
122 |
123 |
124 | hideRowNames=0
125 |
126 | Play
127 |
128 |
--------------------------------------------------------------------------------
/controls/notifier/README.md:
--------------------------------------------------------------------------------
1 | # Webex Teams Notifier
2 |
3 | In-Room Control that lets users set and update an email address, and sends Webex Teams notifications to this email via a bot.
4 |
5 | 
6 |
7 | 
8 |
9 |
10 | ## Quickstart
11 |
12 | Deploy the [control](./notifier.xml) to your device
13 |
14 | Run the `notifier.js` script from a bash command line:
15 |
16 | ```shell
17 | git clone https://github.com/ObjectIsAdvantag/xapi-samples
18 | cd controls
19 | cd notifier
20 | npm install
21 | JSXAPI_DEVICE_URL='ssh://192.168.1.34' JSXAPI_USERNAME='integrator' JSXAPI_PASSWORD='integrator' node notifier.js
22 | ```
23 |
24 | Press 'push' and see notifications poping up on your screen.
25 |
26 | ## Sending notifications to Webex Teams
27 |
28 | Create a [Webex Teams bot account](https://developer.webex.com/add-bot.html) and paste the bot access token.
29 |
30 | Run the `to_webex_teams.js` script from a bash command line:
31 |
32 | ```shell
33 | git clone https://github.com/ObjectIsAdvantag/xapi-samples
34 | cd controls
35 | cd notifier
36 | npm install
37 | ACCESS_TOKEN="XXXXX" JSXAPI_DEVICE_URL='ssh://192.168.1.34' JSXAPI_USERNAME='integrator' JSXAPI_PASSWORD='integrator' node to_webex_teams.js
38 | ```
39 |
40 | Press 'push' and see messages poping up in the Webex Teams space.
41 |
42 | 
43 |
44 |
--------------------------------------------------------------------------------
/controls/notifier/img/panel_notify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/notifier/img/panel_notify.png
--------------------------------------------------------------------------------
/controls/notifier/img/panel_recipient.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/notifier/img/panel_recipient.png
--------------------------------------------------------------------------------
/controls/notifier/img/push_to_teams.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/notifier/img/push_to_teams.png
--------------------------------------------------------------------------------
/controls/notifier/img/touch10_home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/notifier/img/touch10_home.png
--------------------------------------------------------------------------------
/controls/notifier/notifier.xml:
--------------------------------------------------------------------------------
1 |
2 | 1.5
3 |
4 | panel_3
5 | Statusbar
6 | Spark
7 | 2
8 | #D541D8
9 | Notifier
10 |
11 | Notify
12 |
13 | Where are you ?
14 |
15 | send_where_are_you
16 | send
17 | Button
18 | size=2
19 |
20 |
21 | widget_6
22 |
23 | Text
24 | size=2;fontSize=normal;align=center
25 |
26 |
27 |
28 | Meeting canceled
29 |
30 | send_meeting_canceled
31 | send
32 | Button
33 | size=2
34 |
35 |
36 | widget_7
37 |
38 | Text
39 | size=2;fontSize=normal;align=center
40 |
41 |
42 |
43 | What about coffee ?
44 |
45 | send_what_about_coffee
46 | send
47 | Button
48 | size=2
49 |
50 |
51 | widget_8
52 |
53 | Text
54 | size=2;fontSize=normal;align=center
55 |
56 |
57 |
58 |
59 |
60 | Recipient
61 |
62 | Webex Teams
63 |
64 | recipient_email
65 | please enter an email address
66 | Text
67 | size=4;fontSize=normal;align=left
68 |
69 |
70 | update_email
71 | update
72 | Button
73 | size=2
74 |
75 |
76 | reset_email
77 | reset
78 | Button
79 | size=2
80 |
81 |
82 |
83 |
84 |
85 | About
86 |
87 | Author
88 |
89 | widget_9
90 | Stève Sfartz <stsfartz@cisco.com>
91 | Text
92 | size=4;fontSize=small;align=left
93 |
94 |
95 |
96 | Resources
97 |
98 | widget_10
99 | https://github.com/ObjectIsAdvantag/xapi-samples
100 | Text
101 | size=4;fontSize=small;align=left
102 |
103 |
104 |
105 | Version
106 |
107 | status_version
108 |
109 | Text
110 | size=4;fontSize=small;align=left
111 |
112 |
113 |
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/controls/notifier/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "notifier",
3 | "version": "0.1.0",
4 | "description": "In-Room Control that lets users set and update an email address, and sends Webex Teams notifications to this address via a bot.",
5 | "scripts": {
6 | "test": "echo \"Error: no test specified\" && exit 1"
7 | },
8 | "author": "Stève Sfartz ",
9 | "license": "MIT",
10 | "dependencies": {
11 | "jsxapi": "^4.1.3",
12 | "request": "^2.87.0"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/controls/onair/README.md:
--------------------------------------------------------------------------------
1 | # OnAir - CanIBugDad?
2 |
3 | an In-Room Control that toogles a Hue bulb color, depending on the room's state: free, occupied, busy, on air (call in progress)
4 |
5 | 
6 |
7 |
8 |
9 | ## Quickstart
10 |
11 | Deploy the [control](./onair.xml) to your device
12 |
13 | Open the file `script.js` if you're planning to run/test/debug, or the `macro.js` if ready to deploy as a Macro.
14 |
15 | > Note: the `multi.js` file targets both the Macro runtime AND standalone Node.js execution.
16 |
17 | Update wih your Philipps Hue deployment settings.
18 |
19 | Example to run as the script from a shell terminal:
20 |
21 | ```shell
22 | git clone https://github.com/ObjectIsAdvantag/xapi-samples
23 | cd controls
24 | cd onair
25 | npm install
26 | JSXAPI_DEVICE_URL='ssh://192.168.1.32' JSXAPI_USERNAME='localadmin' JSXAPI_PASSWORD='ciscopsdt' node script.js
27 | ```
28 |
--------------------------------------------------------------------------------
/controls/onair/control.xml:
--------------------------------------------------------------------------------
1 |
2 | 1.5
3 |
4 | ONAIR
5 | Statusbar
6 | Lightbulb
7 | 2
8 | #FF503C
9 | OnAir
10 |
11 | Console
12 |
13 | 'OnAir' Status
14 |
15 | onair_console_statusIcon
16 | Button
17 | size=1;icon=blue
18 |
19 |
20 | onair_console_statusText
21 | then, where's dad ?
22 | Text
23 | size=3;fontSize=normal;align=center
24 |
25 |
26 |
27 | Mode
28 |
29 | onair_console_modeToggle
30 | ToggleButton
31 | size=1
32 |
33 |
34 | onair_console_modeText
35 | Automatic
36 | Text
37 | size=3;fontSize=normal;align=left
38 |
39 |
40 |
41 | Manual
42 |
43 | onair_console_manualGroup
44 | GroupButton
45 | size=4
46 |
47 |
48 | free
49 | Off
50 |
51 |
52 | occupied
53 | Free
54 |
55 |
56 | busy
57 | Busy
58 |
59 |
60 | onair
61 | OnAir
62 |
63 |
64 |
65 |
66 | onair_consolePanel
67 |
68 |
69 |
70 | Settings
71 |
72 | Switch
73 |
74 | widget_36
75 | ToggleButton
76 | size=1
77 |
78 |
79 | widget_37
80 | on/off
81 | Text
82 | size=3;fontSize=normal;align=left
83 |
84 |
85 |
86 | Automatic Mode
87 |
88 | widget_38
89 | Spacer
90 | size=4
91 |
92 |
93 |
94 | Standby delay
95 |
96 | widget_24
97 | 1 minute
98 | Text
99 | size=4;fontSize=normal;align=left
100 |
101 |
102 |
103 | Wakeup on Motion
104 |
105 | widget_25
106 | yes
107 | Text
108 | size=4;fontSize=normal;align=left
109 |
110 |
111 |
112 | DoNotDisturb Timeout
113 |
114 | widget_26
115 | 120 minutes
116 | Text
117 | size=4;fontSize=normal;align=left
118 |
119 |
120 | onair_settingsPanel
121 |
122 |
123 |
124 |
125 |
--------------------------------------------------------------------------------
/controls/onair/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "onair",
3 | "version": "0.1.1",
4 | "description": "an In-Room Control that toogles a Hue bulb color, depending on the room's state.",
5 | "scripts": {
6 | "test": "echo \"Error: no test specified\" && exit 1"
7 | },
8 | "author": "Stève Sfartz ",
9 | "license": "MIT",
10 | "dependencies": {
11 | "jsxapi": "^4.3.1",
12 | "request": "^2.88.0"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/controls/proximity/README.md:
--------------------------------------------------------------------------------
1 | # Helper panel to switch Proximity Mode on/off
2 |
3 | **New to Controls & Macros? check the [QuickStart Guide](../../docs/QuickStart.md) to learn to load Controls and Macros to your device**
4 |
5 |
6 | Simply press the toggle:
7 |
8 | 
9 |
10 |
11 | Note that the toggle will [automatically update](https://github.com/ObjectIsAdvantag/xapi-samples/blob/master/controls/proximity/proximity.js#L52) if the configuration is updated from the Room Device Admin UI.
12 |
--------------------------------------------------------------------------------
/controls/proximity/proximity.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2017 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | /**
7 | * Macro companion to the Ultrasound Control
8 | * - lets users toggle Proximity Mode to On/Off
9 | * - displays the current MaxVolume level
10 | */
11 |
12 | const xapi = require('xapi')
13 |
14 |
15 | // Change proximity mode to "On" or "Off"
16 | function switchProximityMode(mode) {
17 | console.debug(`switching proximity mode to: ${mode}`)
18 |
19 | xapi.config.set('Proximity Mode', mode)
20 | .then(() => {
21 | console.info(`turned proximity mode: ${mode}`)
22 | })
23 | .catch((err) => {
24 | console.error(`could not turn proximity mode: ${mode}`)
25 | })
26 | }
27 |
28 | // React to UI events
29 | function onGui(event) {
30 | // Proximity Mode Switch
31 | if ((event.Type == 'changed') && (event.WidgetId == 'proximity_switch')) {
32 | switchProximityMode(event.Value)
33 | return;
34 | }
35 | }
36 | xapi.event.on('UserInterface Extensions Widget Action', onGui);
37 |
38 |
39 | //
40 | // Proximity Services Availability
41 | //
42 |
43 | // Update Toogle if proximity mode changes
44 | function updateProximityToggle(mode) {
45 | console.debug(`switching toggle to ${mode}`)
46 |
47 | xapi.command("UserInterface Extensions Widget SetValue", {
48 | WidgetId: "proximity_switch",
49 | Value: mode
50 | })
51 | }
52 | xapi.config.on("Proximity Mode", mode => {
53 | console.log(`proximity mode changed to: ${mode}`)
54 |
55 | // Update toggle
56 | // [WORKAROUND] Configuration is On or Off, needs to be turned to lowercase
57 | updateProximityToggle(mode.toLowerCase())
58 | })
59 |
60 | // Refresh Toggle state
61 | function refreshProximityToggle() {
62 | xapi.status.get("Proximity Services Availability")
63 | .then(availability => {
64 | console.debug(`current proximity mode is ${availability}`)
65 | switch (availability) {
66 | case 'Available':
67 | updateProximityToggle('on')
68 | return;
69 |
70 | case 'Disabled':
71 | default:
72 | updateProximityToggle('off')
73 | return;
74 | }
75 | })
76 | .catch((err) => {
77 | console.error(`could not read current proximity mode, err: ${err.message}`)
78 | })
79 | }
80 |
81 | //
82 | // Audio Ultrasound MaxVolume
83 | //
84 | function updateUltrasoundMaxVolume(volume) {
85 | console.debug(`updating Ultrasound text to: ${volume}`)
86 |
87 | xapi.command("UserInterface Extensions Widget SetValue", {
88 | WidgetId: "ultrasound_maxvolume",
89 | Value: volume
90 | })
91 | }
92 | xapi.config.on("Audio Ultrasound MaxVolume", volume => {
93 | console.log(`Ultrasound maxVolume changed to: ${volume}`)
94 |
95 | // Update toggle
96 | // [WORKAROUND] Configuration is On or Off, needs to be turned to lowercase
97 | updateUltrasoundMaxVolume(volume)
98 | })
99 |
100 | function refreshUltrasoundMaxVolume() {
101 | xapi.config.get("Audio Ultrasound MaxVolume")
102 | .then(volume => updateUltrasoundMaxVolume(volume))
103 | }
104 |
105 |
106 | //
107 | // Reset UI
108 | //
109 |
110 | // Initialize at macro startup
111 | function refreshUserInterface() {
112 | refreshProximityToggle()
113 | refreshUltrasoundMaxVolume()
114 | }
115 | refreshUserInterface()
116 |
117 | // Initialize at widget deployment
118 | xapi.event.on('UserInterface Extensions Widget LayoutUpdated', (event) => {
119 | console.debug("layout updated, let's refresh our toogle")
120 | refreshUserInterface()
121 | });
122 |
--------------------------------------------------------------------------------
/controls/proximity/proximity.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/proximity/proximity.png
--------------------------------------------------------------------------------
/controls/proximity/proximity.xml:
--------------------------------------------------------------------------------
1 |
2 | 1.4
3 |
4 | Microphone
5 | Statusbar
6 |
7 | UltraSound Configuration
8 |
9 | Proximity
10 |
11 | proximity_switch
12 | ToggleButton
13 | size=1
14 |
15 |
16 | unused
17 |
18 | Text
19 | size=3
20 |
21 |
22 |
23 | MaxVolume
24 |
25 | ultrasound_maxvolume
26 |
27 | Text
28 | size=1;fontSize=normal;align=center
29 |
30 |
31 | unused
32 | (0 to 90)
33 | Text
34 | size=3;align=left;fontSize=normal
35 |
36 |
37 |
38 |
39 | Configuration
40 |
41 |
--------------------------------------------------------------------------------
/controls/ultrasound/README.md:
--------------------------------------------------------------------------------
1 | # Helper panel to update the Ultrasound MaxVolume
2 |
3 | **New to Controls & Macros? check the [QuickStart Guide](../../docs/QuickStart.md) to learn to load Controls and Macros to your device**
4 |
5 | Note that you can deploy the custom code logic for the Ultrasound panel:
6 | - [as a Macro](./ultrasound-macro.js)
7 | - [as a standalone Node.js script](./ultrasound-jsxapi.js).
8 | - or [as a script](./ultrasound-jsxapi.js) that can be used either as a Macro or a standalone script
9 |
10 | Simply move the slider around.
11 |
12 | 
13 |
14 |
15 | _Note that a custom message is pushed to your device's Touch10/DX interface as Webex Teams pairing is enabled / disabled._
16 |
--------------------------------------------------------------------------------
/controls/ultrasound/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ultrasound",
3 | "version": "0.2.0",
4 | "description": "Backend code for the Ultrasound control",
5 | "main": "ultrasound.js",
6 | "scripts": {
7 | "start": "node ultrasound.js",
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "author": "Stève Sfartz ",
11 | "license": "MIT",
12 | "dependencies": {
13 | "jsxapi": "^5.0.1"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/controls/ultrasound/ultrasound-macro.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 |
7 | const xapi = require('xapi')
8 |
9 | xapi.on('ready', init)
10 |
11 |
12 | // CE maximum volume for Ultrasound
13 | // note: Since CE 9.9, max is 70 across all devices
14 | //const MAX = 90 // for a DX80
15 | //const MAX = 70 // for a RoomKit
16 | const MAX = 70
17 |
18 | function init() {
19 | console.log("connexion successful")
20 |
21 | // Initialize the widgets
22 | xapi.config.get('Audio Ultrasound MaxVolume')
23 | .then(updateUI)
24 |
25 | // Update configuration from UI actions
26 | xapi.event.on('UserInterface Extensions Widget Action', (event) => {
27 | if (event.WidgetId !== 'US_volume_slider') return
28 | if (event.Type !== 'released') return
29 |
30 | // Update Ultrasound configuration
31 | const volume = Math.round(parseInt(event.Value) * MAX / 255)
32 | console.log(`updating Ultrasound configuration to: ${volume}`)
33 | xapi.config.set('Audio Ultrasound MaxVolume', volume)
34 | })
35 |
36 | // Update UI from configuration changes
37 | xapi.config.on('Audio Ultrasound MaxVolume', updateUI)
38 |
39 | // Update if the controls is (re-)deployed
40 | xapi.event.on('UserInterface Extensions Widget LayoutUpdated', (event) => {
41 | console.log(`layout updated, let's refresh the widgets`)
42 | xapi.config.get('Audio Ultrasound MaxVolume')
43 | .then(updateUI)
44 | })
45 | }
46 |
47 |
48 | function updateUI(volume) {
49 | console.log(`updating UI to new Ultrasound configuration: ${volume}`)
50 |
51 | // Update Widget: slider
52 | xapi.command('UserInterface Extensions Widget SetValue', {
53 | WidgetId: 'US_volume_text',
54 | Value: volume
55 | })
56 | .catch((error) => {
57 | console.log("cannot update UI component 'Volume level'. Is the panel deployed?")
58 | return
59 | })
60 |
61 | // Update Widget: slider
62 | let newVolume = parseInt(volume)
63 | const level = Math.round(newVolume * 255 / MAX)
64 | xapi.command('UserInterface Extensions Widget SetValue', {
65 | WidgetId: 'US_volume_slider',
66 | Value: level
67 | })
68 | .catch((error) => {
69 | console.log("cannot update UI component 'Volume slider'. Is the panel deployed?")
70 | return
71 | })
72 |
73 | // Update "Pairing" Invite on the Monitor
74 | updatePairingInvite(newVolume)
75 | }
76 |
77 | function updatePairingInvite(volume) {
78 |
79 | const MIN_LEVEL_TO_PAIR = 5 // note that this is an arbitrary value
80 | if (volume < MIN_LEVEL_TO_PAIR) {
81 | xapi.config.set('UserInterface CustomMessage', "/!\\ Pairing is disabled")
82 | return
83 | }
84 |
85 | // Pick the message that suits your device's registration mode
86 | xapi.status.get('Webex Status')
87 | .then((status) => {
88 | // If cloud-registered
89 | if (status == 'Registered') {
90 | xapi.config.set('UserInterface CustomMessage', "Tip: Launch Webex Teams to pair")
91 | }
92 | else {
93 | // If registered on-premises (VCS or CUCM )
94 | xapi.config.set('UserInterface CustomMessage', "Tip: Pair from a Proximity client")
95 | }
96 | })
97 | }
98 |
--------------------------------------------------------------------------------
/controls/ultrasound/ultrasound.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | // Bootstraping sequence to support running either as a JS macro or as standalone Node.js
7 | let xapi;
8 | try {
9 | // Run as a Macro
10 | xapi = require('xapi')
11 | console.log("running as a macro")
12 | }
13 | catch (err) {
14 | if (err.code != "MODULE_NOT_FOUND") {
15 | console.log(`unexpected error code: ${err.code}, exiting...`)
16 | process.exit(1)
17 | }
18 |
19 | // Bind to a CE device
20 | console.log("running as a standalone app")
21 | xapi = connect(process.env.JSXAPI_DEVICE_URL, process.env.JSXAPI_USERNAME, process.env.JSXAPI_PASSWORD)
22 | }
23 |
24 | xapi.on('ready', init)
25 |
26 |
27 | function connect(url, username, password) {
28 | let jsxapi = require('jsxapi')
29 |
30 | // Check args
31 | if (!url || !username) {
32 | console.error("Please specify info to connect to your device as JSXAPI_DEVICE_URL, JSXAPI_USERNAME, JSXAPI_PASSWORD env variables")
33 | console.error("Bash example: JSXAPI_DEVICE_URL='ssh://192.168.1.34' JSXAPI_USERNAME='integrator' JSXAPI_PASSWORD='integrator' node ultrasound-jsxapi.js")
34 | process.exit(1)
35 | }
36 |
37 | // Empty passwords are supported
38 | password = password ? password : ""
39 |
40 | // Connect to the device
41 | console.log(`connecting to device with url: ${url}`)
42 | let xapi = jsxapi.connect(url, {
43 | username: username,
44 | password: password
45 | })
46 | xapi.on('error', (err) => {
47 | switch (err) {
48 | case "client-socket":
49 | console.error("could not connect: invalid URL.")
50 | break
51 |
52 | case "client-authentication":
53 | console.error("could not connect: invalid credentials.")
54 | break
55 |
56 | case "client-timeout":
57 | console.error("could not connect: timeout.")
58 | break
59 |
60 | default:
61 | console.error(`encountered error: ${err}.`)
62 | break
63 | }
64 |
65 | console.log("exiting...")
66 | process.exit(1)
67 | })
68 |
69 | return xapi
70 | }
71 |
72 | //
73 | // Code logic
74 | //
75 |
76 | // CE maximum volume for Ultrasound
77 | // note: Since CE 9.9, max is 70 across all devices
78 | //const MAX = 90 // for a DX80
79 | //const MAX = 70 // for a RoomKit
80 | const MAX = 70
81 |
82 | function init() {
83 | console.log("successfully binded to device")
84 |
85 | // Initialize the widgets
86 | xapi.config.get('Audio Ultrasound MaxVolume')
87 | .then(updateUI)
88 |
89 | // Update configuration from UI actions
90 | xapi.event.on('UserInterface Extensions Widget Action', (event) => {
91 | if (event.WidgetId !== 'US_volume_slider') return
92 | if (event.Type !== 'released') return
93 |
94 | // Update Ultrasound configuration
95 | const volume = Math.round(parseInt(event.Value) * MAX / 255)
96 | console.log(`updating Ultrasound configuration to: ${volume}`)
97 | xapi.config.set('Audio Ultrasound MaxVolume', volume)
98 | })
99 |
100 | // Update UI from configuration changes
101 | xapi.config.on('Audio Ultrasound MaxVolume', updateUI)
102 |
103 | // Update if the controls is (re-)deployed
104 | xapi.event.on('UserInterface Extensions Widget LayoutUpdated', (event) => {
105 | console.log(`layout updated, let's refresh the widgets`)
106 | xapi.config.get('Audio Ultrasound MaxVolume')
107 | .then(updateUI)
108 | })
109 | }
110 |
111 | function updateUI(volume) {
112 | console.log(`updating UI to new Ultrasound configuration: ${volume}`)
113 |
114 | // Update Widget: slider
115 | xapi.command('UserInterface Extensions Widget SetValue', {
116 | WidgetId: 'US_volume_text',
117 | Value: volume
118 | })
119 | .catch((error) => {
120 | console.log("cannot update UI component 'Volume level'. Is the panel deployed?")
121 | return
122 | })
123 |
124 | // Update Widget: slider
125 | let newVolume = parseInt(volume)
126 | const level = Math.round(newVolume * 255 / MAX)
127 | xapi.command('UserInterface Extensions Widget SetValue', {
128 | WidgetId: 'US_volume_slider',
129 | Value: level
130 | })
131 | .catch((error) => {
132 | console.log("cannot update UI component 'Volume slider'. Is the panel deployed?")
133 | return
134 | })
135 |
136 | // Update "Pairing" Invite on the Monitor
137 | updatePairingInvite(newVolume)
138 | }
139 |
140 | function updatePairingInvite(volume) {
141 |
142 | const MIN_LEVEL_TO_PAIR = 5 // note that this is an arbitrary value
143 | if (volume < MIN_LEVEL_TO_PAIR) {
144 | xapi.config.set('UserInterface CustomMessage', "/!\\ Pairing is disabled")
145 | return
146 | }
147 |
148 | // Pick the message that suits your device's registration mode
149 | xapi.status.get('Webex Status')
150 | .then((status) => {
151 | // If cloud-registered
152 | if (status == 'Registered') {
153 | xapi.config.set('UserInterface CustomMessage', "Tip: Launch Webex Teams to pair")
154 | }
155 | else {
156 | // If registered on-premises (VCS or CUCM )
157 | xapi.config.set('UserInterface CustomMessage', "Tip: Pair from a Proximity client")
158 | }
159 | })
160 | }
161 |
--------------------------------------------------------------------------------
/controls/ultrasound/ultrasound.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/controls/ultrasound/ultrasound.png
--------------------------------------------------------------------------------
/controls/ultrasound/ultrasound.xml:
--------------------------------------------------------------------------------
1 |
2 | 1.6
3 |
4 | ULTRASOUND
5 | Home
6 | Proximity
7 | 1
8 | #FFB400
9 | Ultrasound
10 | Custom
11 |
12 | Ultrasound
13 |
14 | Volume
15 |
16 | volume_text
17 | ?
18 | Text
19 | size=1;fontSize=normal;align=center
20 |
21 |
22 | volume_slider
23 | Slider
24 | size=3
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/docs/QuickStart.md:
--------------------------------------------------------------------------------
1 | # QuickStart: Loading UI Extensions and Macros to a Collaboration Device
2 |
3 | Follow the steps below to load Controls and Macros.
4 |
5 | If you're looking for more details, check the [CE Customization Guide](https://www.cisco.com/c/dam/en/us/td/docs/telepresence/endpoint/ce99/sx-mx-dx-room-kit-customization-guide-ce99.pdf).
6 |
7 |
8 | ## Loading an In-Room Control
9 |
10 | Connect to your Device's local Web interface by entering its ip address in a Web Browser, and authenticate with your admin credentials. Example: http://192.168.1.30
11 |
12 | Launch the UI Extensions editor from the 'Integration > UI Extensions' Menu, and clicking the "Launch Editor" button. This would typically take you to http://192.168.1.30/web/roomcontrol/editor from the example above.
13 |
14 | Open the top right menu, click "Import from file".
15 |
16 | 
17 |
18 | Supposing you've clone this repository, select the source XML file [agenda-CLEUR.xml](../controls/agenda/agenda-CLEUR.xml) placed in the 'controls/agenda' subfolder.
19 |
20 | Now click the "Export configuration" button as show below to push the control to your device.
21 |
22 | 
23 |
24 | The upload is instantaneous.
25 | Simply reach to your Device and start using the control.
26 |
27 | In the next step, you'll get details on how to push a JavaScript macro on the Device so that custom logic is executed as end-users interact with the controls.
28 |
29 |
30 | ## Loading a JavaScript Macro
31 |
32 | First, repeat the steps above with the [levels.xml](../controls/levels/levels.xml) file placed in the 'controls/levels' subfolder, and push the control to the device.
33 | You can now interact with the control, but nothing really happens as no logic has been deployed to the device yet.
34 |
35 | Now, launch the Macro editor from the 'Integration > Macro Editor' Menu. This would typically take you to http://192.168.1.30/web/customization/macro from the example above.
36 |
37 | Click "Import from file..." and select the source JS file [levels.js](../controls/levels/levels.js) placed in the same 'controls/levels' subfolder as shown in Step 1 below.
38 |
39 | 
40 |
41 | Click the 'Save' icon as shown in Step 2 above.
42 |
43 | > If you get an error at this point, simply refresh the Web page. Example: hit http://192.168.1.30/web/customization/macro again. The error is due to a User Interface timeout as the macro was pushed to the device. But no worries, the macro did got pushed.
44 |
45 | The macro is now deployed on the Device.
46 | To activate it, click the toggle, and check the logs show 'Macro ready'
47 |
48 | 
49 |
50 | > If you see any error in the logs, there's good chances you missed to deploy the Control associated to the macro.
51 |
52 | Done. Enjoy the game, and start writing your own controls!
53 |
--------------------------------------------------------------------------------
/docs/img/controls-import-from-file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/docs/img/controls-import-from-file.png
--------------------------------------------------------------------------------
/docs/img/controls-push-to-device.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/docs/img/controls-push-to-device.png
--------------------------------------------------------------------------------
/docs/img/macro-activate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/docs/img/macro-activate.png
--------------------------------------------------------------------------------
/docs/img/macro-import-from-file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/docs/img/macro-import-from-file.png
--------------------------------------------------------------------------------
/docs/img/playground/1-playground-first-launch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/docs/img/playground/1-playground-first-launch.png
--------------------------------------------------------------------------------
/docs/img/playground/2-playground-import-file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/docs/img/playground/2-playground-import-file.png
--------------------------------------------------------------------------------
/docs/img/playground/3-playground-after-import.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/docs/img/playground/3-playground-after-import.png
--------------------------------------------------------------------------------
/docs/img/playground/4-playground-preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/docs/img/playground/4-playground-preview.png
--------------------------------------------------------------------------------
/jsxapi/.gitignore:
--------------------------------------------------------------------------------
1 | work/
2 |
3 | node_modules/
4 |
5 | package-lock.json
--------------------------------------------------------------------------------
/jsxapi/0-quickstart.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2018 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | /**
7 | * Simple script that connects to a device and shows the current volume level
8 | */
9 |
10 | // Connect to the device
11 | const jsxapi = require('jsxapi');
12 | const xapi = jsxapi.connect("ssh://192.168.1.34", {
13 | username: 'integrator',
14 | password: ''
15 | });
16 | xapi.on('error', (err) => {
17 | console.error(`connexion failed: ${err}, exiting`);
18 | process.exit(1);
19 | });
20 |
21 | xapi.on('ready', () => {
22 | console.log("connexion successful");
23 |
24 | // Display current audio volume
25 | xapi.status
26 | .get('Audio Volume')
27 | .then((level) => {
28 | console.log(`Current volume level: ${level}`);
29 |
30 | // Ending script
31 | xapi.close();
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/jsxapi/1-connect.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2018 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | /**
7 | * Script that connects to a device, and checks for errors at connection
8 | * then displays current volume level, and attempts to change level if authorized
9 | * and finally exists gracefully.
10 | *
11 | * /!\ Changing the volume level requires admin priviledges (not authorized from the integrator role)
12 | */
13 |
14 | // Connect to the device
15 | const jsxapi = require('jsxapi');
16 | const xapi = jsxapi.connect("ssh://192.168.1.34", {
17 | username: 'admin',
18 | password: ''
19 | });
20 |
21 | xapi.on('error', (err) => {
22 | switch (err) {
23 | case "client-socket":
24 | console.error("Could not connect: invalid URL.");
25 | process.exit(1);
26 |
27 | case "client-authentication":
28 | console.error("Could not connect: invalid credentials.");
29 | process.exit(1);
30 |
31 | case "client-timeout":
32 | console.error("Could not connect: timeout.");
33 | process.exit(1);
34 |
35 | default:
36 | console.error(`Encountered error: ${err}.`);
37 | process.exit(1);
38 | }
39 | });
40 |
41 |
42 | xapi.on('ready', () => {
43 | console.log("connexion successful");
44 |
45 | // Display current audio volume
46 | xapi.status
47 | .get('Audio Volume')
48 | .then((level) => {
49 | console.log(`Current volume level: ${level}`);
50 | });
51 |
52 | // Reset volume to 50
53 | xapi.command('Audio Volume Set', { Level: "50" })
54 | .then((result) => {
55 | console.log(`Reset volume: ${result.status}`);
56 |
57 | // Ending script
58 | xapi.close();
59 | })
60 | .catch((err) => {
61 | console.error(`Cannot reset volume, reason: ${err.message}`)
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/jsxapi/10-autoanswer.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2018 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | /**
7 | * Auto-answer call example, filtering out not registered origins
8 | *
9 | * Integrator role: not supported, as tested as of June 2018
10 | * - OK: registering a feedback for /Call
11 | * - KO: accepting an incoming call
12 | * *
13 | */
14 |
15 | //
16 | // Connect to the device
17 | //
18 |
19 | const jsxapi = require('jsxapi');
20 |
21 | // Check args
22 | if (!process.env.JSXAPI_DEVICE_URL || !process.env.JSXAPI_USERNAME) {
23 | console.log("Please specify info to connect to your device as JSXAPI_DEVICE_URL, JSXAPI_USERNAME, JSXAPI_PASSWORD env variables");
24 | console.log("Bash example: JSXAPI_DEVICE_URL='ssh://192.168.1.32' JSXAPI_USERNAME='integrator' JSXAPI_PASSWORD='integrator' node example.js");
25 | process.exit(1);
26 | }
27 |
28 | // Empty passwords are supported
29 | const password = process.env.JSXAPI_PASSWORD ? process.env.JSXAPI_PASSWORD : "";
30 |
31 | // Connect to the device
32 | console.log("connecting to your device...");
33 | const xapi = jsxapi.connect(process.env.JSXAPI_DEVICE_URL, {
34 | username: process.env.JSXAPI_USERNAME,
35 | password: password
36 | });
37 | xapi.on('error', (err) => {
38 | console.error(`connexion failed: ${err}, exiting`);
39 | process.exit(1);
40 | });
41 |
42 |
43 | //
44 | // Code logic
45 | //
46 |
47 | xapi.on('ready', () => {
48 | console.log("connexion successful");
49 |
50 | // Listen to call events
51 | xapi.status
52 | .on('Call', (call) => {
53 |
54 | switch (call.Status) {
55 | case "Ringing":
56 | console.log(`NEW call: ${call.id}`);
57 |
58 | // Filter depending the origin of the call
59 | if ((call.Direction == "Incoming")
60 | && (call.Protocol == "Spark")
61 | && (call.DisplayName == "Salon")) {
62 |
63 | // Accept incoming call
64 | console.log(`Accepting incoming call: ${call.id}`);
65 | xapi.command('Call Accept', {
66 | CallId: call.id
67 | });
68 | }
69 | return;
70 |
71 | case "Connected":
72 | console.log(`Connected call: ${call.id}`);
73 | return;
74 |
75 | case "Disconnecting":
76 | console.log(`Disconnecting call: ${call.id}`);
77 | return;
78 |
79 | case "Idle":
80 | console.log(`Idle call: ${call.id}`);
81 | return;
82 |
83 | default:
84 | //console.log("DEBUG: ignoring event");
85 | return;
86 | }
87 | })
88 | });
89 |
--------------------------------------------------------------------------------
/jsxapi/11-update-wallpaper.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2018 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | /**
7 | * Make Custom Wallpaper do update by rebooting the device as new background images are pushed
8 | *
9 | */
10 |
11 | //
12 | // Connect to the device
13 | //
14 |
15 | const jsxapi = require('jsxapi');
16 |
17 | // Check args
18 | if (!process.env.JSXAPI_DEVICE_URL || !process.env.JSXAPI_USERNAME) {
19 | console.log("Please specify info to connect to your device as JSXAPI_DEVICE_URL, JSXAPI_USERNAME, JSXAPI_PASSWORD env variables");
20 | console.log("Bash example: JSXAPI_DEVICE_URL='ssh://192.168.1.32' JSXAPI_USERNAME='integrator' JSXAPI_PASSWORD='integrator' node example.js");
21 | process.exit(1);
22 | }
23 |
24 | // Empty passwords are supported
25 | const password = process.env.JSXAPI_PASSWORD ? process.env.JSXAPI_PASSWORD : "";
26 |
27 | // Connect to the device
28 | console.log("connecting to your device...");
29 | const xapi = jsxapi.connect(process.env.JSXAPI_DEVICE_URL, {
30 | username: process.env.JSXAPI_USERNAME,
31 | password: password
32 | });
33 | xapi.on('error', (err) => {
34 | console.error(`connexion failed: ${err}, exiting`);
35 | process.exit(1);
36 | });
37 |
38 |
39 | //
40 | // Code logic
41 | //
42 |
43 | xapi.on('ready', () => {
44 | console.log("connexion successful");
45 |
46 | xapi.event.on('UserInterface Branding Updated', function (event) {
47 | // Was the background been updated ?
48 | if (event.Type == "Background") {
49 | console.log("New wallpaper pushed to system");
50 |
51 | // Restart the device to update the wallpaper
52 | xapi.command('SystemUnit Boot', {
53 | 'Action' :'Restart'
54 | })
55 | .then(() => {
56 | console.log("Restarting device to update Wallpaper");
57 | })
58 | .catch((err) => {
59 | console.log(`Could not restart device: ${err.message}`);
60 | })
61 | }
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/jsxapi/2-environment.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2018 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | /**
7 | * Reads connection info from environment variables
8 | */
9 |
10 | //
11 | // Connect to the device
12 | //
13 |
14 | // Check args
15 | if (!process.env.JSXAPI_DEVICE_URL || !process.env.JSXAPI_USERNAME) {
16 | console.log("Please specify info to connect to your device as JSXAPI_URL, JSXAPI_USERNAME, JSXAPI_PASSWORD env variables");
17 | console.log("Bash example: JSXAPI_DEVICE_URL='ssh://192.168.1.34' JSXAPI_USERNAME='integrator' JSXAPI_PASSWORD='integrator' node example.js");
18 | process.exit(1);
19 | }
20 | // Empty passwords are supported
21 | const password = process.env.JSXAPI_PASSWORD ? process.env.JSXAPI_PASSWORD : "";
22 |
23 | // Connect to the device
24 | const jsxapi = require('jsxapi');
25 | const xapi = jsxapi.connect(process.env.JSXAPI_DEVICE_URL, {
26 | username: process.env.JSXAPI_USERNAME,
27 | password: password
28 | });
29 |
30 | // Errors management
31 | xapi.on('error', (err) => {
32 | switch (err) {
33 | case "client-socket":
34 | console.error("Could not connect: invalid URL.");
35 | process.exit(1);
36 |
37 | case "client-authentication":
38 | console.error("Could not connect: invalid credentials.");
39 | process.exit(1);
40 |
41 | case "client-timeout":
42 | console.error("Could not connect: timeout.");
43 | process.exit(1);
44 |
45 | default:
46 | console.error(`Encountered error: ${err}.`);
47 | process.exit(1);
48 | }
49 | });
50 |
51 |
52 | //
53 | // Code logic
54 | //
55 |
56 | xapi.on('ready', () => {
57 | console.log("connexion successful");
58 |
59 | // Display current audio volume
60 | xapi.status
61 | .get('Audio Volume')
62 | .then((level) => {
63 | console.log(`Current volume level: ${level}`);
64 | })
65 | .then(() => {
66 | // Gracefully ends after delay
67 | const delay = process.env.DELAY || 5; // in seconds
68 | setTimeout(() => {
69 | // End
70 | console.log('Exiting.');
71 | xapi.close();
72 | }, delay * 1000);
73 | console.log(`Will exit gracefully in ${delay} seconds...`);
74 | })
75 | });
76 |
--------------------------------------------------------------------------------
/jsxapi/3-dial.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2018 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | /**
7 | * Dials a demo SIP address, and closes the call after 30 seconds
8 | */
9 |
10 |
11 | //
12 | // Connect to the device
13 | //
14 |
15 | const jsxapi = require('jsxapi');
16 |
17 | // Check args
18 | if (!process.env.JSXAPI_DEVICE_URL || !process.env.JSXAPI_USERNAME) {
19 | console.log("Please specify info to connect to your device as JSXAPI_URL, JSXAPI_USERNAME, JSXAPI_PASSWORD env variables");
20 | console.log("Bash example: JSXAPI_DEVICE_URL='ssh://192.168.1.34' JSXAPI_USERNAME='integrator' JSXAPI_PASSWORD='integrator' node example.js");
21 | process.exit(1);
22 | }
23 |
24 | // Empty passwords are supported
25 | const password = process.env.JSXAPI_PASSWORD ? process.env.JSXAPI_PASSWORD : "";
26 |
27 | // Connect to the device
28 | console.log("connecting to your device...");
29 | const xapi = jsxapi.connect(process.env.JSXAPI_DEVICE_URL, {
30 | username: process.env.JSXAPI_USERNAME,
31 | password: password
32 | });
33 | xapi.on('error', (err) => {
34 | console.error(`connexion failed: ${err}, exiting`);
35 | process.exit(1);
36 | });
37 |
38 |
39 | //
40 | // Code logic
41 | //
42 |
43 | xapi.on('ready', () => {
44 | console.log("connexion successful");
45 |
46 | // Start a call
47 | xapi.command('Dial', { Number: 'fireplace@ivr.vc' })
48 | .then((call) => {
49 | console.log(`Started call with status: ${call.status}, id: ${call.CallId}`);
50 |
51 | // Stop call after delay
52 | const delay = 20;
53 | setTimeout(() => {
54 | console.log('Disconnecting call, and exiting.');
55 |
56 | xapi.command('Call Disconnect', { CallId: call.CallId })
57 | .then(process.exit);
58 | }, delay * 1000);
59 | console.log(`Call with be disconnected in ${delay} seconds...`);
60 |
61 | })
62 | .catch((err) => {
63 | // Frequent error here is to have several on-going calls
64 | // reason: "Maximum limit of active calls reached"
65 | console.error(`Error in call: ${err.message}`)
66 | });
67 | });
68 |
--------------------------------------------------------------------------------
/jsxapi/4-custom-messages.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2018 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | /**
7 | * Modify some of your device's configuration settings
8 | * In this example, we'll change the Halfwake and Awake messages
9 | */
10 |
11 | //
12 | // Connect to the device
13 | //
14 |
15 | const jsxapi = require('jsxapi');
16 |
17 | // Check args
18 | if (!process.env.JSXAPI_DEVICE_URL || !process.env.JSXAPI_USERNAME) {
19 | console.info("Please specify info to connect to your device as JSXAPI_DEVICE_URL, JSXAPI_USERNAME, JSXAPI_PASSWORD env variables");
20 | console.info("Bash example: JSXAPI_DEVICE_URL='ssh://192.168.1.34' JSXAPI_USERNAME='integrator' JSXAPI_PASSWORD='integrator' node example.js");
21 | process.exit(1);
22 | }
23 |
24 | // Empty passwords are supported
25 | const password = process.env.JSXAPI_PASSWORD ? process.env.JSXAPI_PASSWORD : "";
26 |
27 | // Connect to the device
28 | console.log("connecting to your device...");
29 | const xapi = jsxapi.connect(process.env.JSXAPI_DEVICE_URL, {
30 | username: process.env.JSXAPI_USERNAME,
31 | password: password
32 | });
33 | xapi.on('error', (err) => {
34 | console.error(`connexion failed: ${err}, exiting`);
35 | process.exit(1);
36 | });
37 |
38 |
39 | //
40 | // Custom logic
41 | //
42 |
43 | xapi.on('ready', () => {
44 | console.log("connexion successful");
45 |
46 | // Update Halfwake message
47 | xapi.config.set('UserInterface OSD HalfwakeMessage', "I am API addict")
48 | .then(() => {
49 | console.info('updated HalfwakeMessage')
50 | })
51 | .catch((err) => {
52 | console.error(`could not update Halfwake message : ${err.message}`)
53 | });
54 |
55 | // Update Awake message
56 | xapi.config.set('UserInterface CustomMessage', "I am G33K")
57 | .then(() => {
58 | console.log('updated Awake message')
59 |
60 | // Ending script
61 | xapi.close();
62 | })
63 | .catch((err) => {
64 | console.error(`could not update Awake message : ${err.message}`)
65 | });
66 | });
67 |
--------------------------------------------------------------------------------
/jsxapi/5-rolling-messages.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2018 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | /**
7 | * Modify some of your device's configuration settings
8 | * In this example, we'll change the Halfwake and Awake messages
9 | */
10 |
11 | //
12 | // Connect to the device
13 | //
14 |
15 | const jsxapi = require('jsxapi');
16 |
17 | // Check args
18 | if (!process.env.JSXAPI_DEVICE_URL || !process.env.JSXAPI_USERNAME) {
19 | console.log("Please specify info to connect to your device as JSXAPI_DEVICE_URL, JSXAPI_USERNAME, JSXAPI_PASSWORD env variables");
20 | console.log("Bash example: JSXAPI_DEVICE_URL='ssh://192.168.1.34' JSXAPI_USERNAME='integrator' JSXAPI_PASSWORD='integrator' node example.js");
21 | process.exit(1);
22 | }
23 |
24 | // Empty passwords are supported
25 | const password = process.env.JSXAPI_PASSWORD ? process.env.JSXAPI_PASSWORD : "";
26 |
27 | // Connect to a device
28 | const xapi = jsxapi.connect(process.env.JSXAPI_DEVICE_URL, {
29 | username: process.env.JSXAPI_USERNAME,
30 | password: password
31 | });
32 | xapi.on('error', (err) => {
33 | console.error(`connexion failed: ${err}, exiting`);
34 | process.exit(1);
35 | });
36 |
37 |
38 | //
39 | // Custom logic
40 | //
41 |
42 | function update(value) {
43 |
44 | // Update Awake message
45 | xapi.config.set('UserInterface CustomMessage', value)
46 | .then(() => {
47 | console.log('updated Awake message')
48 | })
49 | .catch((err) => {
50 | console.error(`could not update Awake message : ${err.message}`)
51 | });
52 | }
53 |
54 | // Iterator that rolls values among an array for a max number of times
55 | let current = {}
56 | current.options = [];
57 | current.iterations = 0;
58 | current.iterate = function (original, max) {
59 | // Reset options if array is empty
60 | if (this.options.length === 0) {
61 | this.options = original;
62 | }
63 |
64 | this.iterations++;
65 | if (this.iterations > max) {
66 | clearInterval(current.timer);
67 | return true;
68 | }
69 |
70 | // Pop next option
71 | var choice = this.options.pop();
72 | update(choice);
73 | return false;
74 | }
75 |
76 | xapi.on('ready', () => {
77 | console.log("connexion successful");
78 |
79 | const delay = 3; //seconds
80 | const iterations = 3;
81 | console.log(`Starting loop to roll Awake messages every ${delay} seconds, with ${iterations} iterations`);
82 | current.timer = setInterval(function () {
83 | const completed = current.iterate(["Read a tutorial", "Launch a Sandbox", "Attend an event", "Pick a DevNet activity among:"], iterations);
84 |
85 | // Roll completed
86 | if (completed) {
87 | console.log("Exiting.");
88 | xapi.close();
89 | }
90 | }, delay * 1000);
91 | });
92 |
--------------------------------------------------------------------------------
/jsxapi/6-branding-logo.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2018 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | /**
7 | * In this example, we'll update the Branding logo in Halfwake mode
8 | */
9 |
10 | //
11 | // Connect to the device
12 | //
13 |
14 | const jsxapi = require('jsxapi');
15 |
16 | // Check args
17 | if (!process.env.JSXAPI_DEVICE_URL || !process.env.JSXAPI_USERNAME) {
18 | console.log("Please specify info to connect to your device as JSXAPI_DEVICE_URL, JSXAPI_USERNAME, JSXAPI_PASSWORD env variables");
19 | console.log("Bash example: JSXAPI_DEVICE_URL='ssh://192.168.1.34' JSXAPI_USERNAME='integrator' JSXAPI_PASSWORD='integrator' node example.js");
20 | process.exit(1);
21 | }
22 |
23 | // Empty passwords are supported
24 | const password = process.env.JSXAPI_PASSWORD ? process.env.JSXAPI_PASSWORD : "";
25 |
26 | // Connect to the device
27 | console.log("connecting to your device...");
28 | const xapi = jsxapi.connect(process.env.JSXAPI_DEVICE_URL, {
29 | username: process.env.JSXAPI_USERNAME,
30 | password: password
31 | });
32 | xapi.on('error', (err) => {
33 | console.error(`connexion failed: ${err}, exiting`);
34 | process.exit(1);
35 | });
36 | xapi.on('ready', () => {
37 | console.log("connexion successful");
38 |
39 | let encoded;
40 | try {
41 | // Read binary data
42 | const fs = require('fs');
43 | const bitmap = fs.readFileSync("./img/create-logo-transparent.png");
44 |
45 | // Convert binary data to base64 encoded string
46 | encoded = new Buffer(bitmap).toString('base64');
47 |
48 | console.log("image encoding successful");
49 | }
50 | catch (err) {
51 | console.error(`could not read image: ${err.message}, exiting`);
52 | process.exit(1);
53 | }
54 |
55 | // Update Awake message
56 | xapi.command('UserInterface Branding Upload', {
57 | Type: 'HalfwakeBranding',
58 | body: encoded
59 | })
60 | .then(() => {
61 | console.log('updated Branding logo in Halfwake mode');
62 |
63 | // Switch to Halwake mode
64 | xapi.command('Standby Halfwake')
65 | .then(() => xapi.close());
66 | })
67 | .catch((err) => {
68 | console.error(`could not update Brand logo: ${err.message}`);
69 | xapi.close();
70 | });
71 | });
72 |
--------------------------------------------------------------------------------
/jsxapi/7-feedback-peoplecount.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2018 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | /**
7 | * Listen to realtime events via xAPI's feedback function
8 | * In this example, we display people count changes as they happen
9 | *
10 | * /!\ This example only works when run against a 'RoomKit' type of device
11 | */
12 |
13 | //
14 | // Connect to the device
15 | //
16 |
17 | const jsxapi = require('jsxapi');
18 |
19 | // Check args
20 | if (!process.env.JSXAPI_DEVICE_URL || !process.env.JSXAPI_USERNAME) {
21 | console.log("Please specify info to connect to your device as JSXAPI_DEVICE_URL, JSXAPI_USERNAME, JSXAPI_PASSWORD env variables");
22 | console.log("Bash example: JSXAPI_DEVICE_URL='ssh://192.168.1.34' JSXAPI_USERNAME='integrator' JSXAPI_PASSWORD='integrator' node 7-feedback-peoplecount.js");
23 | process.exit(1);
24 | }
25 |
26 | // Empty passwords are supported
27 | const password = process.env.JSXAPI_PASSWORD ? process.env.JSXAPI_PASSWORD : "";
28 |
29 | // Connect to the device
30 | console.log("connecting to your device...");
31 | const xapi = jsxapi.connect(process.env.JSXAPI_DEVICE_URL, {
32 | username: process.env.JSXAPI_USERNAME,
33 | password: password
34 | });
35 | xapi.on('error', (err) => {
36 | console.error(`connexion failed: ${err}, exiting`);
37 | process.exit(1);
38 | });
39 |
40 |
41 | //
42 | // Code logic
43 | //
44 |
45 | xapi.on('ready', () => {
46 | console.log("connexion successful");
47 |
48 | // Fetch current count
49 | xapi.status
50 | .get('RoomAnalytics PeopleCount')
51 | .then((count) => {
52 | console.log(`Initial count is: ${count.Current}`);
53 |
54 | // Listen to events
55 | console.log('Adding feedback listener to: RoomAnalytics PeopleCount');
56 | xapi.feedback.on('/Status/RoomAnalytics/PeopleCount', (count) => {
57 | console.log(`Updated count to: ${count.Current}`);
58 | });
59 |
60 | })
61 | .catch((err) => {
62 | console.log(`Failed to fetch PeopleCount, err: ${err.message}`);
63 | console.log(`Are you interacting with a RoomKit? exiting...`);
64 | xapi.close();
65 | });
66 | });
67 |
--------------------------------------------------------------------------------
/jsxapi/8-listen-audio-volume.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2018 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | /**
7 | * Listens to "Status Audio Volume" changes as they happen on the device.
8 | */
9 |
10 |
11 | //
12 | // Connect to the device
13 | //
14 |
15 | const jsxapi = require('jsxapi');
16 |
17 | // Check args
18 | if (!process.env.JSXAPI_DEVICE_URL || !process.env.JSXAPI_USERNAME) {
19 | console.log("Please specify info to connect to your device as JSXAPI_URL, JSXAPI_USERNAME, JSXAPI_PASSWORD env variables");
20 | console.log("Bash example: JSXAPI_DEVICE_URL='ssh://192.168.1.34' JSXAPI_USERNAME='integrator' JSXAPI_PASSWORD='integrator' node example.js");
21 | process.exit(1);
22 | }
23 |
24 | // Empty passwords are supported
25 | const password = process.env.JSXAPI_PASSWORD ? process.env.JSXAPI_PASSWORD : "";
26 |
27 | // Connect to the device
28 | console.log("connecting to your device...");
29 | const xapi = jsxapi.connect(process.env.JSXAPI_DEVICE_URL, {
30 | username: process.env.JSXAPI_USERNAME,
31 | password: password
32 | });
33 | xapi.on('error', (err) => {
34 | console.error(`connexion failed: ${err}, exiting`);
35 | process.exit(1);
36 | });
37 |
38 |
39 | //
40 | // Code logic
41 | //
42 |
43 | xapi.on('ready', () => {
44 | console.log("connexion successful");
45 |
46 | // Listen to events
47 | console.log('Please press the Audio (+) / (-) buttons');
48 | const off = xapi.status.on('Audio Volume', (volume) => {
49 | console.log(`Volume changed to: ${volume}`)
50 | });
51 |
52 | // Stop listening after delay
53 | const delay = 20; // in seconds
54 | setTimeout(() => {
55 | console.log('Exiting.');
56 |
57 | // De-register feedback
58 | off();
59 | xapi.close();
60 |
61 | }, delay * 1000);
62 | console.log(`Will stop listening and exit in ${delay} seconds...`);
63 |
64 | });
65 |
--------------------------------------------------------------------------------
/jsxapi/9-listen-all-status.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2018 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 |
7 | /**
8 | * Listens to all "Status" changes as they happen on the device.
9 | */
10 |
11 | //
12 | // Connect to the device
13 | //
14 |
15 | const jsxapi = require('jsxapi');
16 |
17 | // Check args
18 | if (!process.env.JSXAPI_DEVICE_URL || !process.env.JSXAPI_USERNAME) {
19 | console.log("Please specify info to connect to your device as JSXAPI_URL, JSXAPI_USERNAME, JSXAPI_PASSWORD env variables");
20 | console.log("Bash example: JSXAPI_DEVICE_URL='ssh://192.168.1.34' JSXAPI_USERNAME='integrator' JSXAPI_PASSWORD='integrator' node example.js");
21 | process.exit(1);
22 | }
23 |
24 | // Empty passwords are supported
25 | const password = process.env.JSXAPI_PASSWORD ? process.env.JSXAPI_PASSWORD : "";
26 |
27 | // Connect to the device
28 | console.log("connecting to your device...");
29 | const xapi = jsxapi.connect(process.env.JSXAPI_DEVICE_URL, {
30 | username: process.env.JSXAPI_USERNAME,
31 | password: password
32 | });
33 | xapi.on('error', (err) => {
34 | console.error(`connexion failed: ${err}, exiting`);
35 | process.exit(1);
36 | });
37 |
38 |
39 | //
40 | // Code logic
41 | //
42 |
43 | xapi.on('ready', () => {
44 | console.log("connexion successful");
45 |
46 | // Listen to events
47 | console.log('Please start interacting with your device');
48 | const off = xapi.feedback.on('/Status', (data) => {
49 | const type = Object.getOwnPropertyNames(data)[0];
50 | if (type) {
51 | console.log(`Status changed for: ${type}`)
52 | }
53 | });
54 |
55 | // Stop listening after delay
56 | const delay = 20; // in seconds
57 | setTimeout(() => {
58 | console.log('Exiting.');
59 |
60 | // De-register feedback
61 | off();
62 | xapi.close();
63 |
64 | }, delay * 1000);
65 | console.log(`Will stop listening and exit in ${delay} seconds...`);
66 |
67 | });
68 |
--------------------------------------------------------------------------------
/jsxapi/README.md:
--------------------------------------------------------------------------------
1 | # Example scripts using the Node.js jsxapi for Cisco Collaboration Devices
2 |
3 | The [Node.js jsxapi](https://github.com/cisco-ce/jsxapi) lets you create applications that interact with Cisco Collaboration Devices (DX, SX, MX, RoomKit, any CE-powered in fact).
4 |
5 | You'll find here scripts to learn the jsxapi through baby steps.
6 |
7 | **New to Room Devices, Controls & Macros? check the [QuickStart Guide](../docs/QuickStart.md) to learn to connect to your Device's Web Interface, and load Controls & Macros to your device**
8 |
9 | ## Quickstart
10 |
11 | Open a terminal and run the commands below:
12 |
13 | ```shell
14 | git clone https://github.com/ObjectIsAdvantag/xapi-samples
15 | cd xapi-samples
16 | cd jsxapi
17 | npm install
18 |
19 | # Place your device ip-address and credentials
20 | JSXAPI_DEVICE_URL='ssh://192.168.1.34' JSXAPI_USERNAME='integrator' JSXAPI_PASSWORD='integrator' node 8-rolling-messages
21 | ```
22 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.0.0/badge.svg:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.0.0/coverage.json:
--------------------------------------------------------------------------------
1 | {
2 | "coverage": "52.89%",
3 | "expectCount": 121,
4 | "actualCount": 64,
5 | "files": {
6 | "src/backend/index.js": {
7 | "expectCount": 11,
8 | "actualCount": 9,
9 | "undocumentLines": [
10 | 46,
11 | 48
12 | ]
13 | },
14 | "src/xapi/exc.js": {
15 | "expectCount": 18,
16 | "actualCount": 0,
17 | "undocumentLines": [
18 | 2,
19 | 3,
20 | 4,
21 | 6,
22 | 7,
23 | 27,
24 | 34,
25 | 8,
26 | 5,
27 | 42,
28 | 1,
29 | 11,
30 | 22,
31 | 43,
32 | 28,
33 | 35,
34 | 12,
35 | 37
36 | ]
37 | },
38 | "src/xapi/components.js": {
39 | "expectCount": 7,
40 | "actualCount": 7,
41 | "undocumentLines": []
42 | },
43 | "src/xapi/feedback.js": {
44 | "expectCount": 13,
45 | "actualCount": 11,
46 | "undocumentLines": [
47 | 12,
48 | 13
49 | ]
50 | },
51 | "src/xapi/mixins.js": {
52 | "expectCount": 9,
53 | "actualCount": 9,
54 | "undocumentLines": []
55 | },
56 | "src/json-parser.js": {
57 | "expectCount": 8,
58 | "actualCount": 2,
59 | "undocumentLines": [
60 | 15,
61 | 17,
62 | 27,
63 | 32,
64 | 22,
65 | 21
66 | ]
67 | },
68 | "src/transport/stream.js": {
69 | "expectCount": 8,
70 | "actualCount": 4,
71 | "undocumentLines": [
72 | 39,
73 | 17,
74 | 18,
75 | 19
76 | ]
77 | },
78 | "src/backend/tsh.js": {
79 | "expectCount": 17,
80 | "actualCount": 4,
81 | "undocumentLines": [
82 | 44,
83 | 73,
84 | 59,
85 | 38,
86 | 39,
87 | 92,
88 | 102,
89 | 40,
90 | 41,
91 | 81,
92 | 82,
93 | 42,
94 | 145
95 | ]
96 | },
97 | "src/backend/ws.js": {
98 | "expectCount": 9,
99 | "actualCount": 4,
100 | "undocumentLines": [
101 | 45,
102 | 66,
103 | 49,
104 | 57,
105 | 61
106 | ]
107 | },
108 | "src/xapi/index.js": {
109 | "expectCount": 11,
110 | "actualCount": 11,
111 | "undocumentLines": []
112 | },
113 | "src/index.js": {
114 | "expectCount": 1,
115 | "actualCount": 1,
116 | "undocumentLines": []
117 | },
118 | "src/transport/ssh.js": {
119 | "expectCount": 1,
120 | "actualCount": 1,
121 | "undocumentLines": []
122 | },
123 | "src/xapi/rpc.js": {
124 | "expectCount": 7,
125 | "actualCount": 0,
126 | "undocumentLines": [
127 | 130,
128 | 70,
129 | 156,
130 | 40,
131 | 65,
132 | 165,
133 | 93
134 | ]
135 | },
136 | "src/transport/tsh.js": {
137 | "expectCount": 1,
138 | "actualCount": 1,
139 | "undocumentLines": []
140 | }
141 | }
142 | }
--------------------------------------------------------------------------------
/jsxapi/docs/4.0.0/css/prettify-tomorrow.css:
--------------------------------------------------------------------------------
1 | /* Tomorrow Theme */
2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */
3 | /* Pretty printing styles. Used with prettify.js. */
4 | /* SPAN elements with the classes below are added by prettyprint. */
5 | /* plain text */
6 | .pln {
7 | color: #4d4d4c; }
8 |
9 | @media screen {
10 | /* string content */
11 | .str {
12 | color: #718c00; }
13 |
14 | /* a keyword */
15 | .kwd {
16 | color: #8959a8; }
17 |
18 | /* a comment */
19 | .com {
20 | color: #8e908c; }
21 |
22 | /* a type name */
23 | .typ {
24 | color: #4271ae; }
25 |
26 | /* a literal value */
27 | .lit {
28 | color: #f5871f; }
29 |
30 | /* punctuation */
31 | .pun {
32 | color: #4d4d4c; }
33 |
34 | /* lisp open bracket */
35 | .opn {
36 | color: #4d4d4c; }
37 |
38 | /* lisp close bracket */
39 | .clo {
40 | color: #4d4d4c; }
41 |
42 | /* a markup tag name */
43 | .tag {
44 | color: #c82829; }
45 |
46 | /* a markup attribute name */
47 | .atn {
48 | color: #f5871f; }
49 |
50 | /* a markup attribute value */
51 | .atv {
52 | color: #3e999f; }
53 |
54 | /* a declaration */
55 | .dec {
56 | color: #f5871f; }
57 |
58 | /* a variable name */
59 | .var {
60 | color: #c82829; }
61 |
62 | /* a function name */
63 | .fun {
64 | color: #4271ae; } }
65 | /* Use higher contrast and text-weight for printable form. */
66 | @media print, projection {
67 | .str {
68 | color: #060; }
69 |
70 | .kwd {
71 | color: #006;
72 | font-weight: bold; }
73 |
74 | .com {
75 | color: #600;
76 | font-style: italic; }
77 |
78 | .typ {
79 | color: #404;
80 | font-weight: bold; }
81 |
82 | .lit {
83 | color: #044; }
84 |
85 | .pun, .opn, .clo {
86 | color: #440; }
87 |
88 | .tag {
89 | color: #006;
90 | font-weight: bold; }
91 |
92 | .atn {
93 | color: #404; }
94 |
95 | .atv {
96 | color: #060; } }
97 | /* Style */
98 | /*
99 | pre.prettyprint {
100 | background: white;
101 | font-family: Consolas, Monaco, 'Andale Mono', monospace;
102 | font-size: 12px;
103 | line-height: 1.5;
104 | border: 1px solid #ccc;
105 | padding: 10px; }
106 | */
107 |
108 | /* Specify class=linenums on a pre to get line numbering */
109 | ol.linenums {
110 | margin-top: 0;
111 | margin-bottom: 0; }
112 |
113 | /* IE indents via margin-left */
114 | li.L0,
115 | li.L1,
116 | li.L2,
117 | li.L3,
118 | li.L4,
119 | li.L5,
120 | li.L6,
121 | li.L7,
122 | li.L8,
123 | li.L9 {
124 | /* */ }
125 |
126 | /* Alternate shading for lines */
127 | li.L1,
128 | li.L3,
129 | li.L5,
130 | li.L7,
131 | li.L9 {
132 | /* */ }
133 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.0.0/image/badge.svg:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.0.0/image/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/jsxapi/docs/4.0.0/image/github.png
--------------------------------------------------------------------------------
/jsxapi/docs/4.0.0/image/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/jsxapi/docs/4.0.0/image/search.png
--------------------------------------------------------------------------------
/jsxapi/docs/4.0.0/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jsxapi",
3 | "version": "4.0.1",
4 | "description": "JavaScript bindings for XAPI",
5 | "author": {
6 | "name": "Martin Øinæs Myrseth",
7 | "email": "mmyrseth@cisco.com"
8 | },
9 | "license": "MIT",
10 | "engines": {
11 | "node": ">=6.x",
12 | "npm": ">=5.x"
13 | },
14 | "main": "lib/index.js",
15 | "bin": {
16 | "jsxapi": "./lib/cli.js"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "git@github.com:cisco-ce/jsxapi.git"
21 | },
22 | "scripts": {
23 | "build": "npm run build:js && npm run build:docs",
24 | "build:docs": "esdoc -c esdoc.json",
25 | "build:js": "babel -d lib/ src/",
26 | "clean": "rimraf docs lib",
27 | "lint": "eslint .",
28 | "prepare": "npm run build",
29 | "prepublishOnly": "npm test",
30 | "start": "NODE_ENV=development node ./bin/cli.js",
31 | "test": "npm run lint && mocha",
32 | "tdd": "mocha --watch --reporter min"
33 | },
34 | "dependencies": {
35 | "commander": "^2.13.0",
36 | "duplex-passthrough": "^1.0.2",
37 | "duplexer": "^0.1.1",
38 | "jsonparse": "^1.3.1",
39 | "loglevel": "^1.6.1",
40 | "ssh2": "^0.5.5",
41 | "url-parse": "^1.2.0",
42 | "ws": "^4.0.0",
43 | "xml-escape": "^1.1.0"
44 | },
45 | "devDependencies": {
46 | "babel-cli": "^6.23.0",
47 | "babel-eslint": "^7.1.1",
48 | "babel-plugin-transform-builtin-extend": "^1.1.2",
49 | "babel-plugin-transform-class-properties": "^6.23.0",
50 | "babel-preset-es2015": "^6.22.0",
51 | "babel-register": "^6.23.0",
52 | "chai": "^3.5.0",
53 | "chai-as-promised": "^6.0.0",
54 | "chai-properties": "^1.2.3",
55 | "dirty-chai": "^1.2.2",
56 | "esdoc": "^0.4.8",
57 | "esdoc-es7-plugin": "0.0.3",
58 | "esdoc-importpath-plugin": "^0.1.1",
59 | "eslint": ">=4.18.2",
60 | "eslint-config-airbnb-base": "^11.1.0",
61 | "eslint-plugin-import": "^2.2.0",
62 | "json-loader": "^0.5.4",
63 | "mocha": "^3.2.0",
64 | "rimraf": "^2.6.1",
65 | "sinon": "^4.0.1",
66 | "sinon-chai": "^2.14.0"
67 | },
68 | "babel": {
69 | "plugins": [
70 | "transform-class-properties",
71 | [
72 | "transform-builtin-extend",
73 | {
74 | "globals": [
75 | "Error"
76 | ]
77 | }
78 | ]
79 | ],
80 | "presets": [
81 | "es2015"
82 | ]
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.0.0/script/inherited-summary.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | function toggle(ev) {
3 | var button = ev.target;
4 | var parent = ev.target.parentElement;
5 | while(parent) {
6 | if (parent.tagName === 'TABLE' && parent.classList.contains('summary')) break;
7 | parent = parent.parentElement;
8 | }
9 |
10 | if (!parent) return;
11 |
12 | var tbody = parent.querySelector('tbody');
13 | if (button.classList.contains('opened')) {
14 | button.classList.remove('opened');
15 | button.classList.add('closed');
16 | tbody.style.display = 'none';
17 | } else {
18 | button.classList.remove('closed');
19 | button.classList.add('opened');
20 | tbody.style.display = 'block';
21 | }
22 | }
23 |
24 | var buttons = document.querySelectorAll('.inherited-summary thead .toggle');
25 | for (var i = 0; i < buttons.length; i++) {
26 | buttons[i].addEventListener('click', toggle);
27 | }
28 | })();
29 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.0.0/script/inner-link.js:
--------------------------------------------------------------------------------
1 | // inner link(#foo) can not correctly scroll, because page has fixed header,
2 | // so, I manually scroll.
3 | (function(){
4 | var matched = location.hash.match(/errorLines=([\d,]+)/);
5 | if (matched) return;
6 |
7 | function adjust() {
8 | window.scrollBy(0, -55);
9 | var el = document.querySelector('.inner-link-active');
10 | if (el) el.classList.remove('inner-link-active');
11 |
12 | // ``[ ] . ' " @`` are not valid in DOM id. so must escape these.
13 | var id = location.hash.replace(/([\[\].'"@$])/g, '\\$1');
14 | var el = document.querySelector(id);
15 | if (el) el.classList.add('inner-link-active');
16 | }
17 |
18 | window.addEventListener('hashchange', adjust);
19 |
20 | if (location.hash) {
21 | setTimeout(adjust, 0);
22 | }
23 | })();
24 |
25 | (function(){
26 | var els = document.querySelectorAll('[href^="#"]');
27 | for (var i = 0; i < els.length; i++) {
28 | var el = els[i];
29 | el.href = location.href + el.getAttribute('href'); // because el.href is absolute path
30 | }
31 | })();
32 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.0.0/script/manual.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | var matched = location.pathname.match(/([^/]*)\.html$/);
3 | if (!matched) return;
4 |
5 | var currentName = matched[1];
6 | var cssClass = '.navigation [data-toc-name="' + currentName + '"]';
7 | var styleText = cssClass + ' .manual-toc { display: block; }\n';
8 | styleText += cssClass + ' .manual-toc-title { background-color: #039BE5; }\n';
9 | styleText += cssClass + ' .manual-toc-title a { color: white; }\n';
10 | var style = document.createElement('style');
11 | style.textContent = styleText;
12 | document.querySelector('head').appendChild(style);
13 | })();
14 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.0.0/script/patch-for-local.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | if (location.protocol === 'file:') {
3 | var elms = document.querySelectorAll('a[href="./"]');
4 | for (var i = 0; i < elms.length; i++) {
5 | elms[i].href = './index.html';
6 | }
7 | }
8 | })();
9 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.0.0/script/pretty-print.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | prettyPrint();
3 | var lines = document.querySelectorAll('.prettyprint.linenums li[class^="L"]');
4 | for (var i = 0; i < lines.length; i++) {
5 | lines[i].id = 'lineNumber' + (i + 1);
6 | }
7 |
8 | var matched = location.hash.match(/errorLines=([\d,]+)/);
9 | if (matched) {
10 | var lines = matched[1].split(',');
11 | for (var i = 0; i < lines.length; i++) {
12 | var id = '#lineNumber' + lines[i];
13 | var el = document.querySelector(id);
14 | el.classList.add('error-line');
15 | }
16 | return;
17 | }
18 |
19 | if (location.hash) {
20 | // ``[ ] . ' " @`` are not valid in DOM id. so must escape these.
21 | var id = location.hash.replace(/([\[\].'"@$])/g, '\\$1');
22 | var line = document.querySelector(id);
23 | if (line) line.classList.add('active');
24 | }
25 | })();
26 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.0.0/script/search.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | var searchIndex = window.esdocSearchIndex;
3 | var searchBox = document.querySelector('.search-box');
4 | var input = document.querySelector('.search-input');
5 | var result = document.querySelector('.search-result');
6 | var selectedIndex = -1;
7 | var prevText;
8 |
9 | // active search box and focus when mouse enter on search box.
10 | searchBox.addEventListener('mouseenter', function(){
11 | searchBox.classList.add('active');
12 | input.focus();
13 | });
14 |
15 | // search with text when key is upped.
16 | input.addEventListener('keyup', function(ev){
17 | var text = ev.target.value.toLowerCase();
18 | if (!text) {
19 | result.style.display = 'none';
20 | result.innerHTML = '';
21 | return;
22 | }
23 |
24 | if (text === prevText) return;
25 | prevText = text;
26 |
27 | var html = {class: [], method: [], member: [], function: [], variable: [], typedef: [], external: [], file: [], test: [], testFile: []};
28 | var len = searchIndex.length;
29 | var kind;
30 | for (var i = 0; i < len; i++) {
31 | var pair = searchIndex[i];
32 | if (pair[0].indexOf(text) !== -1) {
33 | kind = pair[3];
34 | html[kind].push('' + pair[2] + '');
35 | }
36 | }
37 |
38 | var innerHTML = '';
39 | for (kind in html) {
40 | var list = html[kind];
41 | if (!list.length) continue;
42 | innerHTML += '' + kind + '\n' + list.join('\n');
43 | }
44 | result.innerHTML = innerHTML;
45 | if (innerHTML) result.style.display = 'block';
46 | selectedIndex = -1;
47 | });
48 |
49 | // down, up and enter key are pressed, select search result.
50 | input.addEventListener('keydown', function(ev){
51 | if (ev.keyCode === 40) {
52 | // arrow down
53 | var current = result.children[selectedIndex];
54 | var selected = result.children[selectedIndex + 1];
55 | if (selected && selected.classList.contains('search-separator')) {
56 | var selected = result.children[selectedIndex + 2];
57 | selectedIndex++;
58 | }
59 |
60 | if (selected) {
61 | if (current) current.classList.remove('selected');
62 | selectedIndex++;
63 | selected.classList.add('selected');
64 | }
65 | } else if (ev.keyCode === 38) {
66 | // arrow up
67 | var current = result.children[selectedIndex];
68 | var selected = result.children[selectedIndex - 1];
69 | if (selected && selected.classList.contains('search-separator')) {
70 | var selected = result.children[selectedIndex - 2];
71 | selectedIndex--;
72 | }
73 |
74 | if (selected) {
75 | if (current) current.classList.remove('selected');
76 | selectedIndex--;
77 | selected.classList.add('selected');
78 | }
79 | } else if (ev.keyCode === 13) {
80 | // enter
81 | var current = result.children[selectedIndex];
82 | if (current) {
83 | var link = current.querySelector('a');
84 | if (link) location.href = link.href;
85 | }
86 | } else {
87 | return;
88 | }
89 |
90 | ev.preventDefault();
91 | });
92 |
93 | // select search result when search result is mouse over.
94 | result.addEventListener('mousemove', function(ev){
95 | var current = result.children[selectedIndex];
96 | if (current) current.classList.remove('selected');
97 |
98 | var li = ev.target;
99 | while (li) {
100 | if (li.nodeName === 'LI') break;
101 | li = li.parentElement;
102 | }
103 |
104 | if (li) {
105 | selectedIndex = Array.prototype.indexOf.call(result.children, li);
106 | li.classList.add('selected');
107 | }
108 | });
109 |
110 | // clear search result when body is clicked.
111 | document.body.addEventListener('click', function(ev){
112 | selectedIndex = -1;
113 | result.style.display = 'none';
114 | result.innerHTML = '';
115 | });
116 |
117 | })();
118 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.0.0/script/test-summary.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | function toggle(ev) {
3 | var button = ev.target;
4 | var parent = ev.target.parentElement;
5 | while(parent) {
6 | if (parent.tagName === 'TR' && parent.classList.contains('test-describe')) break;
7 | parent = parent.parentElement;
8 | }
9 |
10 | if (!parent) return;
11 |
12 | var direction;
13 | if (button.classList.contains('opened')) {
14 | button.classList.remove('opened');
15 | button.classList.add('closed');
16 | direction = 'closed';
17 | } else {
18 | button.classList.remove('closed');
19 | button.classList.add('opened');
20 | direction = 'opened';
21 | }
22 |
23 | var targetDepth = parseInt(parent.dataset.testDepth, 10) + 1;
24 | var nextElement = parent.nextElementSibling;
25 | while (nextElement) {
26 | var depth = parseInt(nextElement.dataset.testDepth, 10);
27 | if (depth >= targetDepth) {
28 | if (direction === 'opened') {
29 | if (depth === targetDepth) nextElement.style.display = '';
30 | } else if (direction === 'closed') {
31 | nextElement.style.display = 'none';
32 | var innerButton = nextElement.querySelector('.toggle');
33 | if (innerButton && innerButton.classList.contains('opened')) {
34 | innerButton.classList.remove('opened');
35 | innerButton.classList.add('closed');
36 | }
37 | }
38 | } else {
39 | break;
40 | }
41 | nextElement = nextElement.nextElementSibling;
42 | }
43 | }
44 |
45 | var buttons = document.querySelectorAll('.test-summary tr.test-describe .toggle');
46 | for (var i = 0; i < buttons.length; i++) {
47 | buttons[i].addEventListener('click', toggle);
48 | }
49 |
50 | var topDescribes = document.querySelectorAll('.test-summary tr[data-test-depth="0"]');
51 | for (var i = 0; i < topDescribes.length; i++) {
52 | topDescribes[i].style.display = '';
53 | }
54 | })();
55 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.2.0/badge.svg:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.2.0/coverage.json:
--------------------------------------------------------------------------------
1 | {
2 | "coverage": "49%",
3 | "expectCount": 151,
4 | "actualCount": 74,
5 | "files": {
6 | "src/backend/index.js": {
7 | "expectCount": 11,
8 | "actualCount": 9,
9 | "undocumentLines": [
10 | 46,
11 | 48
12 | ]
13 | },
14 | "src/backend/tsh.js": {
15 | "expectCount": 23,
16 | "actualCount": 9,
17 | "undocumentLines": [
18 | 26,
19 | 46,
20 | 47,
21 | 48,
22 | 49,
23 | 50,
24 | 52,
25 | 67,
26 | 81,
27 | 89,
28 | 90,
29 | 100,
30 | 110,
31 | 153
32 | ]
33 | },
34 | "src/backend/ws.js": {
35 | "expectCount": 9,
36 | "actualCount": 4,
37 | "undocumentLines": [
38 | 45,
39 | 49,
40 | 57,
41 | 61,
42 | 66
43 | ]
44 | },
45 | "src/cli.js": {
46 | "expectCount": 2,
47 | "actualCount": 0,
48 | "undocumentLines": [
49 | 13,
50 | 19
51 | ]
52 | },
53 | "src/index.js": {
54 | "expectCount": 1,
55 | "actualCount": 1,
56 | "undocumentLines": []
57 | },
58 | "src/json-parser.js": {
59 | "expectCount": 10,
60 | "actualCount": 2,
61 | "undocumentLines": [
62 | 15,
63 | 17,
64 | 21,
65 | 22,
66 | 27,
67 | 32,
68 | 38,
69 | 45
70 | ]
71 | },
72 | "src/transport/ssh.js": {
73 | "expectCount": 1,
74 | "actualCount": 1,
75 | "undocumentLines": []
76 | },
77 | "src/transport/stream.js": {
78 | "expectCount": 10,
79 | "actualCount": 4,
80 | "undocumentLines": [
81 | 17,
82 | 18,
83 | 19,
84 | 39,
85 | 46,
86 | 51
87 | ]
88 | },
89 | "src/transport/tsh.js": {
90 | "expectCount": 2,
91 | "actualCount": 1,
92 | "undocumentLines": [
93 | 6
94 | ]
95 | },
96 | "src/xapi/components.js": {
97 | "expectCount": 11,
98 | "actualCount": 8,
99 | "undocumentLines": [
100 | 51,
101 | 62,
102 | 74
103 | ]
104 | },
105 | "src/xapi/exc.js": {
106 | "expectCount": 19,
107 | "actualCount": 0,
108 | "undocumentLines": [
109 | 1,
110 | 2,
111 | 3,
112 | 4,
113 | 5,
114 | 6,
115 | 7,
116 | 8,
117 | 11,
118 | 12,
119 | 22,
120 | 24,
121 | 30,
122 | 31,
123 | 37,
124 | 38,
125 | 40,
126 | 45,
127 | 46
128 | ]
129 | },
130 | "src/xapi/feedback.js": {
131 | "expectCount": 15,
132 | "actualCount": 11,
133 | "undocumentLines": [
134 | 11,
135 | 12,
136 | 51,
137 | 55
138 | ]
139 | },
140 | "src/xapi/index.js": {
141 | "expectCount": 14,
142 | "actualCount": 14,
143 | "undocumentLines": []
144 | },
145 | "src/xapi/mixins.js": {
146 | "expectCount": 9,
147 | "actualCount": 9,
148 | "undocumentLines": []
149 | },
150 | "src/xapi/normalizePath.js": {
151 | "expectCount": 1,
152 | "actualCount": 1,
153 | "undocumentLines": []
154 | },
155 | "src/xapi/rpc.js": {
156 | "expectCount": 13,
157 | "actualCount": 0,
158 | "undocumentLines": [
159 | 11,
160 | 12,
161 | 15,
162 | 21,
163 | 40,
164 | 65,
165 | 70,
166 | 93,
167 | 98,
168 | 132,
169 | 139,
170 | 158,
171 | 167
172 | ]
173 | }
174 | }
175 | }
--------------------------------------------------------------------------------
/jsxapi/docs/4.2.0/css/github.css:
--------------------------------------------------------------------------------
1 | /* github markdown */
2 | .github-markdown {
3 | font-size: 16px;
4 | }
5 |
6 | .github-markdown h1,
7 | .github-markdown h2,
8 | .github-markdown h3,
9 | .github-markdown h4,
10 | .github-markdown h5 {
11 | margin-top: 1em;
12 | margin-bottom: 16px;
13 | font-weight: bold;
14 | padding: 0;
15 | }
16 |
17 | .github-markdown h1:nth-of-type(1) {
18 | margin-top: 0;
19 | }
20 |
21 | .github-markdown h1 {
22 | font-size: 2em;
23 | padding-bottom: 0.3em;
24 | }
25 |
26 | .github-markdown h2 {
27 | font-size: 1.75em;
28 | padding-bottom: 0.3em;
29 | }
30 |
31 | .github-markdown h3 {
32 | font-size: 1.5em;
33 | }
34 |
35 | .github-markdown h4 {
36 | font-size: 1.25em;
37 | }
38 |
39 | .github-markdown h5 {
40 | font-size: 1em;
41 | }
42 |
43 | .github-markdown ul, .github-markdown ol {
44 | padding-left: 2em;
45 | }
46 |
47 | .github-markdown pre > code {
48 | font-size: 0.85em;
49 | }
50 |
51 | .github-markdown table {
52 | margin-bottom: 1em;
53 | border-collapse: collapse;
54 | border-spacing: 0;
55 | }
56 |
57 | .github-markdown table tr {
58 | background-color: #fff;
59 | border-top: 1px solid #ccc;
60 | }
61 |
62 | .github-markdown table th,
63 | .github-markdown table td {
64 | padding: 6px 13px;
65 | border: 1px solid #ddd;
66 | }
67 |
68 | .github-markdown table tr:nth-child(2n) {
69 | background-color: #f8f8f8;
70 | }
71 |
72 | .github-markdown hr {
73 | border-right: 0;
74 | border-bottom: 1px solid #e5e5e5;
75 | border-left: 0;
76 | border-top: 0;
77 | }
78 |
79 | /** badge(.svg) does not have border */
80 | .github-markdown img:not([src*=".svg"]) {
81 | max-width: 100%;
82 | box-shadow: 1px 1px 1px rgba(0,0,0,0.5);
83 | }
84 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.2.0/css/identifiers.css:
--------------------------------------------------------------------------------
1 | .identifiers-wrap {
2 | display: flex;
3 | align-items: flex-start;
4 | }
5 |
6 | .identifier-dir-tree {
7 | background: #fff;
8 | border: solid 1px #ddd;
9 | border-radius: 0.25em;
10 | top: 52px;
11 | position: -webkit-sticky;
12 | position: sticky;
13 | max-height: calc(100vh - 155px);
14 | overflow-y: scroll;
15 | min-width: 200px;
16 | margin-left: 1em;
17 | }
18 |
19 | .identifier-dir-tree-header {
20 | padding: 0.5em;
21 | background-color: #fafafa;
22 | border-bottom: solid 1px #ddd;
23 | }
24 |
25 | .identifier-dir-tree-content {
26 | padding: 0 0.5em 0;
27 | }
28 |
29 | .identifier-dir-tree-content > div {
30 | padding-top: 0.25em;
31 | padding-bottom: 0.25em;
32 | }
33 |
34 | .identifier-dir-tree-content a {
35 | color: inherit;
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.2.0/css/manual.css:
--------------------------------------------------------------------------------
1 | .github-markdown .manual-toc {
2 | padding-left: 0;
3 | }
4 |
5 | .manual-index .manual-cards {
6 | display: flex;
7 | flex-wrap: wrap;
8 | }
9 |
10 | .manual-index .manual-card-wrap {
11 | width: 280px;
12 | padding: 10px 20px 10px 0;
13 | box-sizing: border-box;
14 | }
15 |
16 | .manual-index .manual-card-wrap > h1 {
17 | margin: 0;
18 | font-size: 1em;
19 | font-weight: 600;
20 | padding: 0.2em 0 0.2em 0.5em;
21 | border-radius: 0.1em 0.1em 0 0;
22 | border: none;
23 | }
24 |
25 | .manual-index .manual-card-wrap > h1 span {
26 | color: #555;
27 | }
28 |
29 | .manual-index .manual-card {
30 | height: 200px;
31 | overflow: hidden;
32 | border: solid 1px rgba(230, 230, 230, 0.84);
33 | border-radius: 0 0 0.1em 0.1em;
34 | padding: 8px;
35 | position: relative;
36 | }
37 |
38 | .manual-index .manual-card > div {
39 | transform: scale(0.4);
40 | transform-origin: 0 0;
41 | width: 250%;
42 | }
43 |
44 | .manual-index .manual-card > a {
45 | position: absolute;
46 | top: 0;
47 | left: 0;
48 | width: 100%;
49 | height: 100%;
50 | background: rgba(210, 210, 210, 0.1);
51 | }
52 |
53 | .manual-index .manual-card > a:hover {
54 | background: none;
55 | }
56 |
57 | .manual-index .manual-badge {
58 | margin: 0;
59 | }
60 |
61 | .manual-index .manual-user-index {
62 | margin-bottom: 1em;
63 | border-bottom: solid 1px #ddd;
64 | }
65 |
66 | .manual-root .navigation {
67 | padding-left: 4px;
68 | margin-top: 4px;
69 | }
70 |
71 | .navigation .manual-toc-root > div {
72 | padding-left: 0.25em;
73 | padding-right: 0.75em;
74 | }
75 |
76 | .github-markdown .manual-toc-title a {
77 | color: inherit;
78 | }
79 |
80 | .manual-breadcrumb-list {
81 | font-size: 0.8em;
82 | margin-bottom: 1em;
83 | }
84 |
85 | .manual-toc-title a:hover {
86 | color: #039BE5;
87 | }
88 |
89 | .manual-toc li {
90 | margin: 0.75em 0;
91 | list-style-type: none;
92 | }
93 |
94 | .navigation .manual-toc [class^="indent-h"] a {
95 | color: #666;
96 | }
97 |
98 | .navigation .manual-toc .indent-h1 a {
99 | color: #555;
100 | font-weight: 600;
101 | display: block;
102 | }
103 |
104 | .manual-toc .indent-h1 {
105 | display: block;
106 | margin: 0.4em 0 0 0.25em;
107 | padding: 0.2em 0 0.2em 0.5em;
108 | border-radius: 0.1em;
109 | }
110 |
111 | .manual-root .navigation .manual-toc li:not(.indent-h1) {
112 | margin-top: 0.5em;
113 | }
114 |
115 | .manual-toc .indent-h2 {
116 | display: none;
117 | margin-left: 1.5em;
118 | }
119 | .manual-toc .indent-h3 {
120 | display: none;
121 | margin-left: 2.5em;
122 | }
123 | .manual-toc .indent-h4 {
124 | display: none;
125 | margin-left: 3.5em;
126 | }
127 | .manual-toc .indent-h5 {
128 | display: none;
129 | margin-left: 4.5em;
130 | }
131 |
132 | .manual-nav li {
133 | margin: 0.75em 0;
134 | }
135 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.2.0/css/prettify-tomorrow.css:
--------------------------------------------------------------------------------
1 | /* Tomorrow Theme */
2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */
3 | /* Pretty printing styles. Used with prettify.js. */
4 | /* SPAN elements with the classes below are added by prettyprint. */
5 | /* plain text */
6 | .pln {
7 | color: #4d4d4c; }
8 |
9 | @media screen {
10 | /* string content */
11 | .str {
12 | color: #718c00; }
13 |
14 | /* a keyword */
15 | .kwd {
16 | color: #8959a8; }
17 |
18 | /* a comment */
19 | .com {
20 | color: #8e908c; }
21 |
22 | /* a type name */
23 | .typ {
24 | color: #4271ae; }
25 |
26 | /* a literal value */
27 | .lit {
28 | color: #f5871f; }
29 |
30 | /* punctuation */
31 | .pun {
32 | color: #4d4d4c; }
33 |
34 | /* lisp open bracket */
35 | .opn {
36 | color: #4d4d4c; }
37 |
38 | /* lisp close bracket */
39 | .clo {
40 | color: #4d4d4c; }
41 |
42 | /* a markup tag name */
43 | .tag {
44 | color: #c82829; }
45 |
46 | /* a markup attribute name */
47 | .atn {
48 | color: #f5871f; }
49 |
50 | /* a markup attribute value */
51 | .atv {
52 | color: #3e999f; }
53 |
54 | /* a declaration */
55 | .dec {
56 | color: #f5871f; }
57 |
58 | /* a variable name */
59 | .var {
60 | color: #c82829; }
61 |
62 | /* a function name */
63 | .fun {
64 | color: #4271ae; } }
65 | /* Use higher contrast and text-weight for printable form. */
66 | @media print, projection {
67 | .str {
68 | color: #060; }
69 |
70 | .kwd {
71 | color: #006;
72 | font-weight: bold; }
73 |
74 | .com {
75 | color: #600;
76 | font-style: italic; }
77 |
78 | .typ {
79 | color: #404;
80 | font-weight: bold; }
81 |
82 | .lit {
83 | color: #044; }
84 |
85 | .pun, .opn, .clo {
86 | color: #440; }
87 |
88 | .tag {
89 | color: #006;
90 | font-weight: bold; }
91 |
92 | .atn {
93 | color: #404; }
94 |
95 | .atv {
96 | color: #060; } }
97 | /* Style */
98 | /*
99 | pre.prettyprint {
100 | background: white;
101 | font-family: Consolas, Monaco, 'Andale Mono', monospace;
102 | font-size: 12px;
103 | line-height: 1.5;
104 | border: 1px solid #ccc;
105 | padding: 10px; }
106 | */
107 |
108 | /* Specify class=linenums on a pre to get line numbering */
109 | ol.linenums {
110 | margin-top: 0;
111 | margin-bottom: 0; }
112 |
113 | /* IE indents via margin-left */
114 | li.L0,
115 | li.L1,
116 | li.L2,
117 | li.L3,
118 | li.L4,
119 | li.L5,
120 | li.L6,
121 | li.L7,
122 | li.L8,
123 | li.L9 {
124 | /* */ }
125 |
126 | /* Alternate shading for lines */
127 | li.L1,
128 | li.L3,
129 | li.L5,
130 | li.L7,
131 | li.L9 {
132 | /* */ }
133 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.2.0/css/search.css:
--------------------------------------------------------------------------------
1 | /* search box */
2 | .search-box {
3 | position: absolute;
4 | top: 10px;
5 | right: 50px;
6 | padding-right: 8px;
7 | padding-bottom: 10px;
8 | line-height: normal;
9 | font-size: 12px;
10 | }
11 |
12 | .search-box img {
13 | width: 20px;
14 | vertical-align: top;
15 | }
16 |
17 | .search-input {
18 | display: inline;
19 | visibility: hidden;
20 | width: 0;
21 | padding: 2px;
22 | height: 1.5em;
23 | outline: none;
24 | background: transparent;
25 | border: 1px #0af;
26 | border-style: none none solid none;
27 | vertical-align: bottom;
28 | }
29 |
30 | .search-input-edge {
31 | display: none;
32 | width: 1px;
33 | height: 5px;
34 | background-color: #0af;
35 | vertical-align: bottom;
36 | }
37 |
38 | .search-result {
39 | position: absolute;
40 | display: none;
41 | height: 600px;
42 | width: 100%;
43 | padding: 0;
44 | margin-top: 5px;
45 | margin-left: 24px;
46 | background: white;
47 | box-shadow: 1px 1px 4px rgb(0,0,0);
48 | white-space: nowrap;
49 | overflow-y: scroll;
50 | }
51 |
52 | .search-result-import-path {
53 | color: #aaa;
54 | font-size: 12px;
55 | }
56 |
57 | .search-result li {
58 | list-style: none;
59 | padding: 2px 4px;
60 | }
61 |
62 | .search-result li a {
63 | display: block;
64 | }
65 |
66 | .search-result li.selected {
67 | background: #ddd;
68 | }
69 |
70 | .search-result li.search-separator {
71 | background: rgb(37, 138, 175);
72 | color: white;
73 | }
74 |
75 | .search-box.active .search-input {
76 | visibility: visible;
77 | transition: width 0.2s ease-out;
78 | width: 300px;
79 | }
80 |
81 | .search-box.active .search-input-edge {
82 | display: inline-block;
83 | }
84 |
85 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.2.0/css/source.css:
--------------------------------------------------------------------------------
1 | table.files-summary {
2 | width: 100%;
3 | margin: 10px 0;
4 | border-spacing: 0;
5 | border: 0;
6 | border-collapse: collapse;
7 | text-align: right;
8 | }
9 |
10 | table.files-summary tbody tr:hover {
11 | background: #eee;
12 | }
13 |
14 | table.files-summary td:first-child,
15 | table.files-summary td:nth-of-type(2) {
16 | text-align: left;
17 | }
18 |
19 | table.files-summary[data-use-coverage="false"] td.coverage {
20 | display: none;
21 | }
22 |
23 | table.files-summary thead {
24 | background: #fafafa;
25 | }
26 |
27 | table.files-summary td {
28 | border: solid 1px #ddd;
29 | padding: 4px 10px;
30 | vertical-align: top;
31 | }
32 |
33 | table.files-summary td.identifiers > span {
34 | display: block;
35 | margin-top: 4px;
36 | }
37 | table.files-summary td.identifiers > span:first-child {
38 | margin-top: 0;
39 | }
40 |
41 | table.files-summary .coverage-count {
42 | font-size: 12px;
43 | color: #aaa;
44 | display: inline-block;
45 | min-width: 40px;
46 | }
47 |
48 | .total-coverage-count {
49 | position: relative;
50 | bottom: 2px;
51 | font-size: 12px;
52 | color: #666;
53 | font-weight: 500;
54 | padding-left: 5px;
55 | }
56 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.2.0/css/test.css:
--------------------------------------------------------------------------------
1 | table.test-summary thead {
2 | background: #fafafa;
3 | }
4 |
5 | table.test-summary thead .test-description {
6 | width: 50%;
7 | }
8 |
9 | table.test-summary {
10 | width: 100%;
11 | margin: 10px 0;
12 | border-spacing: 0;
13 | border: 0;
14 | border-collapse: collapse;
15 | }
16 |
17 | table.test-summary thead .test-count {
18 | width: 3em;
19 | }
20 |
21 | table.test-summary tbody tr:hover {
22 | background-color: #eee;
23 | }
24 |
25 | table.test-summary td {
26 | border: solid 1px #ddd;
27 | padding: 4px 10px;
28 | vertical-align: top;
29 | }
30 |
31 | table.test-summary td p {
32 | margin: 0;
33 | }
34 |
35 | table.test-summary tr.test-interface .toggle {
36 | display: inline-block;
37 | float: left;
38 | margin-right: 4px;
39 | cursor: pointer;
40 | font-size: 0.8em;
41 | padding-top: 0.25em;
42 | }
43 |
44 | table.test-summary tr.test-interface .toggle.opened:before {
45 | content: '▼';
46 | }
47 |
48 | table.test-summary tr.test-interface .toggle.closed:before {
49 | content: '▶';
50 | }
51 |
52 | table.test-summary .test-target > span {
53 | display: block;
54 | margin-top: 4px;
55 | }
56 | table.test-summary .test-target > span:first-child {
57 | margin-top: 0;
58 | }
59 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.2.0/image/badge.svg:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.2.0/image/esdoc-logo-mini-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/jsxapi/docs/4.2.0/image/esdoc-logo-mini-black.png
--------------------------------------------------------------------------------
/jsxapi/docs/4.2.0/image/esdoc-logo-mini.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/jsxapi/docs/4.2.0/image/esdoc-logo-mini.png
--------------------------------------------------------------------------------
/jsxapi/docs/4.2.0/image/manual-badge.svg:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.2.0/image/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/jsxapi/docs/4.2.0/image/search.png
--------------------------------------------------------------------------------
/jsxapi/docs/4.2.0/lint.json:
--------------------------------------------------------------------------------
1 | []
--------------------------------------------------------------------------------
/jsxapi/docs/4.2.0/script/inherited-summary.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | function toggle(ev) {
3 | var button = ev.target;
4 | var parent = ev.target.parentElement;
5 | while(parent) {
6 | if (parent.tagName === 'TABLE' && parent.classList.contains('summary')) break;
7 | parent = parent.parentElement;
8 | }
9 |
10 | if (!parent) return;
11 |
12 | var tbody = parent.querySelector('tbody');
13 | if (button.classList.contains('opened')) {
14 | button.classList.remove('opened');
15 | button.classList.add('closed');
16 | tbody.style.display = 'none';
17 | } else {
18 | button.classList.remove('closed');
19 | button.classList.add('opened');
20 | tbody.style.display = 'block';
21 | }
22 | }
23 |
24 | var buttons = document.querySelectorAll('.inherited-summary thead .toggle');
25 | for (var i = 0; i < buttons.length; i++) {
26 | buttons[i].addEventListener('click', toggle);
27 | }
28 | })();
29 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.2.0/script/inner-link.js:
--------------------------------------------------------------------------------
1 | // inner link(#foo) can not correctly scroll, because page has fixed header,
2 | // so, I manually scroll.
3 | (function(){
4 | var matched = location.hash.match(/errorLines=([\d,]+)/);
5 | if (matched) return;
6 |
7 | function adjust() {
8 | window.scrollBy(0, -55);
9 | var el = document.querySelector('.inner-link-active');
10 | if (el) el.classList.remove('inner-link-active');
11 |
12 | // ``[ ] . ' " @`` are not valid in DOM id. so must escape these.
13 | var id = location.hash.replace(/([\[\].'"@$])/g, '\\$1');
14 | var el = document.querySelector(id);
15 | if (el) el.classList.add('inner-link-active');
16 | }
17 |
18 | window.addEventListener('hashchange', adjust);
19 |
20 | if (location.hash) {
21 | setTimeout(adjust, 0);
22 | }
23 | })();
24 |
25 | (function(){
26 | var els = document.querySelectorAll('[href^="#"]');
27 | var href = location.href.replace(/#.*$/, ''); // remove existed hash
28 | for (var i = 0; i < els.length; i++) {
29 | var el = els[i];
30 | el.href = href + el.getAttribute('href'); // because el.href is absolute path
31 | }
32 | })();
33 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.2.0/script/manual.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | var matched = location.pathname.match(/\/(manual\/.*\.html)$/);
3 | if (!matched) return;
4 |
5 | var currentName = matched[1];
6 | var cssClass = '.navigation .manual-toc li[data-link="' + currentName + '"]';
7 | var styleText = cssClass + '{ display: block; }\n';
8 | styleText += cssClass + '.indent-h1 a { color: #039BE5 }';
9 | var style = document.createElement('style');
10 | style.textContent = styleText;
11 | document.querySelector('head').appendChild(style);
12 | })();
13 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.2.0/script/patch-for-local.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | if (location.protocol === 'file:') {
3 | var elms = document.querySelectorAll('a[href="./"]');
4 | for (var i = 0; i < elms.length; i++) {
5 | elms[i].href = './index.html';
6 | }
7 | }
8 | })();
9 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.2.0/script/pretty-print.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | prettyPrint();
3 | var lines = document.querySelectorAll('.prettyprint.linenums li[class^="L"]');
4 | for (var i = 0; i < lines.length; i++) {
5 | lines[i].id = 'lineNumber' + (i + 1);
6 | }
7 |
8 | var matched = location.hash.match(/errorLines=([\d,]+)/);
9 | if (matched) {
10 | var lines = matched[1].split(',');
11 | for (var i = 0; i < lines.length; i++) {
12 | var id = '#lineNumber' + lines[i];
13 | var el = document.querySelector(id);
14 | el.classList.add('error-line');
15 | }
16 | return;
17 | }
18 |
19 | if (location.hash) {
20 | // ``[ ] . ' " @`` are not valid in DOM id. so must escape these.
21 | var id = location.hash.replace(/([\[\].'"@$])/g, '\\$1');
22 | var line = document.querySelector(id);
23 | if (line) line.classList.add('active');
24 | }
25 | })();
26 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.2.0/script/search.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | var searchIndex = window.esdocSearchIndex;
3 | var searchBox = document.querySelector('.search-box');
4 | var input = document.querySelector('.search-input');
5 | var result = document.querySelector('.search-result');
6 | var selectedIndex = -1;
7 | var prevText;
8 |
9 | // active search box and focus when mouse enter on search box.
10 | searchBox.addEventListener('mouseenter', function(){
11 | searchBox.classList.add('active');
12 | input.focus();
13 | });
14 |
15 | // search with text when key is upped.
16 | input.addEventListener('keyup', function(ev){
17 | var text = ev.target.value.toLowerCase();
18 | if (!text) {
19 | result.style.display = 'none';
20 | result.innerHTML = '';
21 | return;
22 | }
23 |
24 | if (text === prevText) return;
25 | prevText = text;
26 |
27 | var html = {class: [], method: [], member: [], function: [], variable: [], typedef: [], external: [], file: [], test: [], testFile: []};
28 | var len = searchIndex.length;
29 | var kind;
30 | for (var i = 0; i < len; i++) {
31 | var pair = searchIndex[i];
32 | if (pair[0].indexOf(text) !== -1) {
33 | kind = pair[3];
34 | html[kind].push('' + pair[2] + '');
35 | }
36 | }
37 |
38 | var innerHTML = '';
39 | for (kind in html) {
40 | var list = html[kind];
41 | if (!list.length) continue;
42 | innerHTML += '' + kind + '\n' + list.join('\n');
43 | }
44 | result.innerHTML = innerHTML;
45 | if (innerHTML) result.style.display = 'block';
46 | selectedIndex = -1;
47 | });
48 |
49 | // down, up and enter key are pressed, select search result.
50 | input.addEventListener('keydown', function(ev){
51 | if (ev.keyCode === 40) {
52 | // arrow down
53 | var current = result.children[selectedIndex];
54 | var selected = result.children[selectedIndex + 1];
55 | if (selected && selected.classList.contains('search-separator')) {
56 | var selected = result.children[selectedIndex + 2];
57 | selectedIndex++;
58 | }
59 |
60 | if (selected) {
61 | if (current) current.classList.remove('selected');
62 | selectedIndex++;
63 | selected.classList.add('selected');
64 | }
65 | } else if (ev.keyCode === 38) {
66 | // arrow up
67 | var current = result.children[selectedIndex];
68 | var selected = result.children[selectedIndex - 1];
69 | if (selected && selected.classList.contains('search-separator')) {
70 | var selected = result.children[selectedIndex - 2];
71 | selectedIndex--;
72 | }
73 |
74 | if (selected) {
75 | if (current) current.classList.remove('selected');
76 | selectedIndex--;
77 | selected.classList.add('selected');
78 | }
79 | } else if (ev.keyCode === 13) {
80 | // enter
81 | var current = result.children[selectedIndex];
82 | if (current) {
83 | var link = current.querySelector('a');
84 | if (link) location.href = link.href;
85 | }
86 | } else {
87 | return;
88 | }
89 |
90 | ev.preventDefault();
91 | });
92 |
93 | // select search result when search result is mouse over.
94 | result.addEventListener('mousemove', function(ev){
95 | var current = result.children[selectedIndex];
96 | if (current) current.classList.remove('selected');
97 |
98 | var li = ev.target;
99 | while (li) {
100 | if (li.nodeName === 'LI') break;
101 | li = li.parentElement;
102 | }
103 |
104 | if (li) {
105 | selectedIndex = Array.prototype.indexOf.call(result.children, li);
106 | li.classList.add('selected');
107 | }
108 | });
109 |
110 | // clear search result when body is clicked.
111 | document.body.addEventListener('click', function(ev){
112 | selectedIndex = -1;
113 | result.style.display = 'none';
114 | result.innerHTML = '';
115 | });
116 |
117 | })();
118 |
--------------------------------------------------------------------------------
/jsxapi/docs/4.2.0/script/test-summary.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | function toggle(ev) {
3 | var button = ev.target;
4 | var parent = ev.target.parentElement;
5 | while(parent) {
6 | if (parent.tagName === 'TR' && parent.classList.contains('test-interface')) break;
7 | parent = parent.parentElement;
8 | }
9 |
10 | if (!parent) return;
11 |
12 | var direction;
13 | if (button.classList.contains('opened')) {
14 | button.classList.remove('opened');
15 | button.classList.add('closed');
16 | direction = 'closed';
17 | } else {
18 | button.classList.remove('closed');
19 | button.classList.add('opened');
20 | direction = 'opened';
21 | }
22 |
23 | var targetDepth = parseInt(parent.dataset.testDepth, 10) + 1;
24 | var nextElement = parent.nextElementSibling;
25 | while (nextElement) {
26 | var depth = parseInt(nextElement.dataset.testDepth, 10);
27 | if (depth >= targetDepth) {
28 | if (direction === 'opened') {
29 | if (depth === targetDepth) nextElement.style.display = '';
30 | } else if (direction === 'closed') {
31 | nextElement.style.display = 'none';
32 | var innerButton = nextElement.querySelector('.toggle');
33 | if (innerButton && innerButton.classList.contains('opened')) {
34 | innerButton.classList.remove('opened');
35 | innerButton.classList.add('closed');
36 | }
37 | }
38 | } else {
39 | break;
40 | }
41 | nextElement = nextElement.nextElementSibling;
42 | }
43 | }
44 |
45 | var buttons = document.querySelectorAll('.test-summary tr.test-interface .toggle');
46 | for (var i = 0; i < buttons.length; i++) {
47 | buttons[i].addEventListener('click', toggle);
48 | }
49 |
50 | var topDescribes = document.querySelectorAll('.test-summary tr[data-test-depth="0"]');
51 | for (var i = 0; i < topDescribes.length; i++) {
52 | topDescribes[i].style.display = '';
53 | }
54 | })();
55 |
--------------------------------------------------------------------------------
/jsxapi/httpfeedback/README.md:
--------------------------------------------------------------------------------
1 | # Service to follow people count metrics in real time
2 |
3 | Start an HTTP Server that registers as a Webhook to your Room Device (via xAPI 's HttpFeedback)
4 |
5 | Open a terminal and run the commands as described below:
6 |
7 | ```shell
8 | git clone
9 | cd
10 | cd jsxapi
11 | cd httpfeedback
12 | npm install
13 |
14 | JSXAPI_DEVICE_URL='ssh://192.168.1.34' JSXAPI_USERNAME='admin' JSXAPI_PASSWORD='' WEBHOOK_URL="http://192.168.1.34:8080" node server.js
15 | ```
16 |
17 | Check the service is running by hitting its healthcheck at http://localhost:8080 and http://192.168.1.34:8080 in the example above.
18 |
--------------------------------------------------------------------------------
/jsxapi/httpfeedback/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "httpfeedback",
3 | "version": "0.1.0",
4 | "description": "HTTP Service to register for real-time HTTPFeedback events",
5 | "main": "server.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "Stève Sfartz ",
10 | "license": "MIT",
11 | "dependencies": {
12 | "express": "^4.16.2",
13 | "jsxapi": "^4.0.1"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/jsxapi/httpfeedback/server.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2018 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | /**
7 | * Registers an HTTP Webhook to receive People Count events from the Room Devices.
8 | * Note that HttpFeedback is the exact xAPI terminology.
9 | *
10 | * /!\ The user must have admin access to the device. Integrators cannot register httpfeedbacks
11 | *
12 | */
13 |
14 |
15 | //
16 | // Connect to a RoomKit device
17 | //
18 |
19 | const jsxapi = require('jsxapi');
20 |
21 | // Check args
22 | if (!process.env.JSXAPI_DEVICE_URL || !process.env.JSXAPI_USERNAME) {
23 | console.info("Please specify info to connect to your device as JSXAPI_URL, JSXAPI_USERNAME, JSXAPI_PASSWORD env variables");
24 | console.info("Bash example: JSXAPI_DEVICE_URL='ssh://192.168.1.34' JSXAPI_USERNAME='integrator' JSXAPI_PASSWORD='integrator' node example.js");
25 | process.exit(1);
26 | }
27 | if (!process.env.WEBHOOK_URL) {
28 | console.info("Please specify the URL to which events will be posted as a WEBHOOK_URL env variable");
29 | process.exit(1);
30 | }
31 | // Empty passwords are supported
32 | const password = process.env.JSXAPI_PASSWORD ? process.env.JSXAPI_PASSWORD : "";
33 |
34 | // Connect to the device
35 | const xapi = jsxapi.connect(process.env.JSXAPI_DEVICE_URL, {
36 | username: process.env.JSXAPI_USERNAME,
37 | password: password,
38 | });
39 | xapi.on('error', (error) => {
40 | console.debug(`connexion failed: ${error.message}, exiting`);
41 | process.exit(1);
42 | });
43 | xapi.on('ready', () => {
44 | console.debug("connexion successful");
45 |
46 | // Registering webhook
47 | xapi.command('HttpFeedback Register', {
48 | FeedbackSlot: "2",
49 | ServerUrl: process.env.WEBHOOK_URL,
50 | Format: "JSON",
51 | Expression: "/Status/RoomAnalytics/PeopleCount"
52 | })
53 | .then((result) => {
54 | console.log(`successfully registered Webhook for PeopleCount, result: ${result.status}, on slot: ${result.FeedbackSlot}`);
55 | })
56 | .catch((error) => {
57 | console.log(`Could not register webhook against the device, reason: ${error.message}`);
58 | console.log(`Exiting...`);
59 | process.exit(1);
60 | });
61 | });
62 |
63 |
64 | //
65 | // Server receive the events
66 | //
67 |
68 | var express = require("express");
69 | var app = express();
70 |
71 | var bodyParser = require("body-parser");
72 | app.use(bodyParser.urlencoded({ extended: true }));
73 | app.use(bodyParser.json());
74 |
75 | var debug = require("debug")("samples");
76 |
77 |
78 | var started = Date.now();
79 | app.route("/")
80 | // healthcheck
81 | .get(function (req, res) {
82 | res.json({
83 | message: "Congrats, your Room Analytics Service is up and running",
84 | since: new Date(started).toISOString()
85 | });
86 | })
87 |
88 | // webhook endpoint
89 | .post(function (req, res) {
90 |
91 | // analyse incoming payload, should conform to Spark webhook trigger specifications
92 | debug("DEBUG: webhook invoked");
93 | if (!req.body) {
94 | console.log("WARNING: Unexpected payload received, aborting...");
95 | res.status(400).json({ message: "Bad payload" });
96 | return;
97 | }
98 |
99 | // event is ready to be processed, let's respond without waiting any longer
100 | res.status(200).json({ message: "message is being processed..." });
101 |
102 | // process incoming resource/event, see https://developer.ciscospark.com/webhooks-explained.html
103 | processEvent(req.body);
104 | });
105 |
106 |
107 | // Starts the Bot service
108 | //
109 | // [WORKAROUND] in some container situation (ie, Cisco Shipped), we need to use an OVERRIDE_PORT to force our bot to start and listen to the port defined in the Dockerfile (ie, EXPOSE),
110 | // and not the PORT dynamically assigned by the host or scheduler.
111 | var port = process.env.OVERRIDE_PORT || process.env.PORT || 8080;
112 | app.listen(port, function () {
113 | console.log("Cisco Spark Bot started at http://localhost:" + port + "/");
114 | console.log(" GET / for health checks");
115 | console.log(" POST / to send Webhook events");
116 | });
117 |
118 |
119 | // Invoked every time an event flies to the webhook
120 | function processEvent(payload) {
121 | //console.debug(`received new event`);
122 |
123 | const count =parseInt(payload.Status.RoomAnalytics.PeopleCount.Current.Value);
124 | console.log(`Count: ${count}`);
125 | }
--------------------------------------------------------------------------------
/jsxapi/img/cisco-logo-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/jsxapi/img/cisco-logo-white.png
--------------------------------------------------------------------------------
/jsxapi/img/create-logo-transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/jsxapi/img/create-logo-transparent.png
--------------------------------------------------------------------------------
/jsxapi/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tutorial",
3 | "version": "0.2.1",
4 | "description": "Example Scripts using the Node.js jsxapi for Cisco Collaboration Devices",
5 | "scripts": {
6 | "test": "echo \"Error: no test specified\" && exit 1"
7 | },
8 | "author": "Stève Sfartz ",
9 | "license": "MIT",
10 | "dependencies": {
11 | "jsxapi": "^4.1.3"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/macros/1-helloworld.js:
--------------------------------------------------------------------------------
1 | console.log("Hello world")
--------------------------------------------------------------------------------
/macros/10-httpclient.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Posts a message to a Webex Teams space via a Bot account
3 | *
4 | * Prep work:
5 | * from a ssh session, type the commands below:
6 | * > xConfiguration HttpClient Mode: On
7 | * > xConfiguration HttpClient AllowInsecureHTTPS: True
8 | *
9 | * Example for tshell:
10 | * > xcommand HttpClient Post
11 | * Url: https://api.ciscospark.com/v1/messages
12 | * Header: "Content-Type: application/json"
13 | * Header: "Authorization: Bearer BOT_TOKEN"
14 | * ResultBody: "plaintext"
15 | * AllowInsecureHTTPS: True
16 | * > {"markdown":"hey, this is Steve","roomId":"ROOM_ID"}
17 | * > .
18 | *
19 | *
20 | */
21 |
22 | const xapi = require('xapi');
23 |
24 | // Replace with your bot token
25 | const token = "BOT_TOKEN";
26 | // replace with a space your bot is part of
27 | const roomId = "ROOM_ID";
28 |
29 | function push(msg, cb) {
30 |
31 | // Post message
32 | let payload = {
33 | "markdown": msg,
34 | "roomId": roomId
35 | }
36 | xapi.command(
37 | 'HttpClient Post',
38 | {
39 | Header: ["Content-Type: application/json", "Authorization: Bearer " + token],
40 | Url: "https://api.ciscospark.com/v1/messages",
41 | AllowInsecureHTTPS: "True",
42 | ResultBody: 'plaintext'
43 | },
44 | JSON.stringify(payload))
45 | .then((response) => {
46 | console.debug(`received response with status code: ${response.StatusCode}`);
47 |
48 | if (response.StatusCode == 200) {
49 | console.log("message pushed to Webex Teams");
50 |
51 | // Retrieve message id
52 | let result = JSON.parse(response.Body);
53 | console.log(`message id: ${result.id}`);
54 | if (cb) cb(null, result.id);
55 | return;
56 | }
57 |
58 | // This should not happen as Webex REST API always return 200 OK for POST requests
59 | console.log("failed with status code: " + response.StatusCode);
60 | if (cb) cb("failed with status code: " + response.StatusCode, response.StatusCode);
61 | })
62 | .catch((err) => {
63 | console.log(`failed with err message: ${err.message}`);
64 |
65 | switch (err.message) {
66 | case 'Unknown command':
67 | // Can be caught at coding time
68 | console.log("seems a wrong HttpClient Verb is used");
69 | break;
70 |
71 | case 'HttpClientPostResult':
72 | case 'HttpClientDeleteResult':
73 | console.log(`failed with err status: ${err.data.status}`);
74 | console.log(Object.keys(err.data));
75 | if (err.data.status === 'Error') {
76 |
77 | // Typically: hostname not found
78 | if (err.data.Message) {
79 | console.log("data message: " + err.data.Message);
80 | break;
81 | }
82 |
83 | // Typically: the response status code is 4xx or 5xx
84 | if (err.data.StatusCode) {
85 | console.log("status code: " + err.data.StatusCode);
86 |
87 | // Note: err.data.Headers can also be retrieved, though not the body of the response (no ResponseBody attribute here)
88 | break;
89 | }
90 | }
91 | }
92 |
93 | if (cb) cb("Could not post message to Webex Teams", null);
94 | })
95 | }
96 |
97 | push('Hey, this is Steve', console.log);
98 |
99 | /*
100 | xapi.event.on('UserInterface Extensions Event Clicked', (event) => {
101 | console.log(`new event from: ${event.Signal}`)
102 |
103 | // Push info about the session
104 | push('Hello World')
105 | })
106 | */
--------------------------------------------------------------------------------
/macros/11-philips-hue.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Post API requests to a Hue Bridge
3 | *
4 | */
5 |
6 | const xapi = require("xapi")
7 |
8 | // Update for your Hue deployment
9 | const BRIDGE_IP = "192.168.1.33";
10 | const BRIDGE_USER = "SECRET";
11 | const LIGHT_ID = 1; // number of your Bulb as registered at your Hue Bridge
12 |
13 | // Change color
14 | const color = 25500;
15 | sendToBridge(BRIDGE_IP, BRIDGE_USER, LIGHT_ID, "Put", { "hue": color });
16 |
17 |
18 | function sendToBridge(bridgeip, username, light, verb, payload) {
19 |
20 | xapi.command("HttpClient " + verb, {
21 | Header: ["Content-Type: application/json"],
22 | Url: `http://${bridgeip}/api/${username}/lights/${light}/state`,
23 | AllowInsecureHTTPS: "True",
24 | ResultBody: "plaintext"
25 | },
26 | JSON.stringify(payload))
27 | .then((response) => {
28 |
29 | console.log(`request sent to the bridge, status code: ${response.StatusCode}`);
30 | console.debug(`received response: ${response.Body}`);
31 |
32 | // Check response
33 | let result = JSON.parse(response.Body);
34 | if (result[0] && (result[0].success)) {
35 | console.log("success");
36 | return;
37 | }
38 |
39 | if (result[0] && (result[0].error)) {
40 | console.log("error");
41 | console.debug(result[0].error.Message);
42 | return;
43 | }
44 |
45 | console.warn(`unexpected response: ${Object.keys(result[0])}`);
46 | return;
47 | })
48 | .catch((err) => {
49 | console.log("Could not contact the bridge");
50 | console.debug("failed with err: " + err.message);
51 | })
52 | }
53 |
--------------------------------------------------------------------------------
/macros/12a-primary.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2019 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | /**
7 | * Pushes an event to a remote Codec using the Message command and an HttpClient POST
8 | * This code needs a listener: install the secondary.js macro on the remote Codec
9 | *
10 | */
11 |
12 | const xapi = require('xapi');
13 |
14 | const SECONDARY_IP = "192.168.1.32";
15 | const SECONDARY_BASICAUTH = "bG9jYWxhZG1pbjpjaXNjb3BzZHQ=";
16 |
17 | var EVENTURL = `http://${SECONDARY_IP}/putxml`;
18 | var HEADERS = ['Content-Type: text/xml', `Authorization: Basic ${SECONDARY_BASICAUTH}`];
19 |
20 | function postRequest(url, payload, headers) {
21 | xapi.command('HttpClient Post', {
22 | Url: url,
23 | Header: headers
24 | }, payload).then((response) => { console.log(JSON.stringify(response)) });
25 | console.log(payload);
26 | }
27 |
28 | function sendEvent(event) {
29 | event = event.replace(/"/g, "\'");
30 | var payload = "" + event + "";
31 | postRequest(EVENTURL, payload, HEADERS);
32 | }
33 |
34 | function volumeSync(volume) {
35 | sendEvent(JSON.stringify({ "Audio Volume Set": { "Level": volume } }));
36 | }
37 |
38 | xapi.status.on('Audio Volume', volumeSync);
39 |
40 | console.log('primary started')
--------------------------------------------------------------------------------
/macros/12b-secondary.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2019 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | /**
7 | * Listen to events sent from a remote Codec using the Message command and an HttpClient POST
8 | * This code needs a primary: install the primary.js macro on the remote Codec
9 | *
10 | */
11 | const xapi = require('xapi');
12 |
13 | function parse(event) {
14 | return JSON.parse(event);
15 | }
16 |
17 | xapi.event.on('Message Send Text', event => {
18 | event = event.replace(/'/g, '"');
19 | try {
20 | var command = parse(event);
21 | for (var key in command) {
22 | console.debug(`processing command: ${key}`)
23 | xapi.command(key, command[key]);
24 | }
25 | } catch (err) {
26 | console.log(`cannot parse event: ${err.message}`);
27 | }
28 | });
29 |
30 | console.log('secondary started');
--------------------------------------------------------------------------------
/macros/13a-environment.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | /**
7 | * Implements Environment Variables for Macros:
8 | * A* environment.js : this macro manages volatile ENV variables (stores/updates and returns the current values for an environment variable)
9 | * B. getenv.js : this is not a macro but a code snippet that you insert into existing macros to retrieve ENV variables (local to your device)
10 | *
11 | * Quick start:
12 | * - customize the default list of ENV variables for your device
13 | * - deploy the 'environment.js' macro to your device
14 | * - insert the 'getenv()' code snipped into other macros on the same device
15 | * - access ENV variables from macros once 'env-ready' event is emmitted
16 | *
17 | * Note: if a variable is not found in ENV, '' is returned (empty)
18 | *
19 | * Example of a Macro using the getenv() function:
20 | *
21 | * // Fired when the environnment Macro is ready to provide ENV variables
22 | * xapi.on('env-ready', async (ready) => {
23 | * if (!ready) {
24 | * console.log('Environment macro is not responding! aborting...');
25 | * return;
26 | * }
27 | *
28 | * let variable = 'DEVICE_SECRET'
29 | * let secret = await getenv(variable);
30 | * console.log(`echo \$${variable} = ${secret}`);
31 | * });
32 | *
33 | */
34 |
35 | const xapi = require('xapi');
36 |
37 | // List of variables for the environment
38 | const ENV = {
39 | 'PING': 'PONG',
40 |
41 | // ADD environment variables below
42 | 'DEVICE_SECRET' : 1234
43 | };
44 |
45 | xapi.event.on('Message Send Text', (msg) => {
46 | console.debug(`new "Message Send" Event with text: ${msg}`);
47 |
48 | // WORKAROUND: needed if message was sent from the cloud via POST https://api.ciscospark.com/v1/xapi/command/message.send
49 | msg = msg.replace(/\'/g, '"');
50 | let parsed;
51 | try {
52 | parsed = JSON.parse(msg);
53 | }
54 | catch (err) {
55 | console.err(`cannot JSON parse the text in 'Message Send' Event: ${msg}, ignoring...`);
56 | return
57 | }
58 |
59 | let data = parsed;
60 | if (!data.env) {
61 | console.debug(`"Message Send" Event is not about Env: ${msg}, ignoring`);
62 | return;
63 | }
64 |
65 | console.debug(`"Message Sent" event concerns env variable: "${data.env}"`);
66 |
67 | // GET ENV
68 | if (data.operation && (data.operation == 'get')) {
69 |
70 | if (!data.env) {
71 | console.warn(`no ENV variable specified in: "${msg}", ignoring...`);
72 | return;
73 | }
74 |
75 | console.debug(`requested value for env variable: "${data.env}"`);
76 | let response = {
77 | 'env': data.env,
78 | 'operation': 'get_response',
79 | 'value': '' // default to '' if variable is not found
80 | }
81 | let value = ENV[data.env];
82 | if (value) {
83 | response.value = value;
84 | }
85 |
86 | // Publish value for ENV variable
87 | xapi.command('Message Send', { Text: JSON.stringify(response) });
88 | return;
89 | }
90 |
91 | // RESPONSE provided => ignore
92 | if (data.operation && (data.operation == 'get_response')) {
93 | console.debug(`ignoring get_response type of "Message Sent" event: ${msg}`);
94 | return;
95 | }
96 |
97 | // SET ENV
98 | // Note that the storage is volatile (dynamically added variables will be lost every time the macro or runtime is restared)
99 | if (data.operation && (data.operation == 'set')) {
100 | console.log(`setting value: "${data.value}" for env variable: "${data.env}"`);
101 |
102 | ENV[data.env] = data.value;
103 | return;
104 | }
105 |
106 | console.warn(`operation for "Message Sent" event is not supported:"${msg}, ignoring...`);
107 | return;
108 | })
109 |
--------------------------------------------------------------------------------
/macros/14-persist.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | /**
7 | * JSON Database as a Macro: persists JSON to a macro with read and write functions
8 | *
9 | * examples:
10 | * write({'congrats':'you did it'})
11 | *
12 | * read().then((data) => console.log(data.foo))
13 | *
14 | * async () => {
15 | * await write({ player : 'steve', score: 5 });
16 | * let data = await read();
17 | * console.log(`player score is: ${data.score}`)
18 | * }
19 | *
20 | */
21 |
22 | const xapi = require('xapi');
23 |
24 | const PREFIX = 'const json = ';
25 | const DATABASE_NAME = 'DB'; // name of the macro with db contents
26 |
27 | // Read database contents
28 | async function read() {
29 | // Load contents
30 | let contents;
31 | try {
32 | let macro = await xapi.command('Macros Macro Get', { Name: DATABASE_NAME, Content: true })
33 | contents = macro.Macro[0].Content.substring(PREFIX.length);
34 | }
35 | catch(err) {
36 | console.error(`cannot load contents from macro: ${DATABASE_NAME}`);
37 | throw new Error("DB_ACCESS_ERROR");
38 | }
39 |
40 | // Parse contents
41 | try {
42 | console.debug(`DB contains: ${contents}`);
43 | let data = JSON.parse(contents);
44 | console.debug('DB successfully parsed');
45 | return data;
46 | }
47 | catch (err) {
48 | console.error('DB is corrupted, cannot JSON parse the DB');
49 | throw new Error('DB_PARSE_ERROR');
50 | }
51 | }
52 |
53 | // Write database contents
54 | async function write(data) {
55 | // Serialize data as JSON and append prefix
56 | let contents;
57 | try {
58 | contents = PREFIX + JSON.stringify(data);
59 | }
60 | catch (err) {
61 | console.debug('Contents cannot be serialized to JSON');
62 | throw new Error('DB_SERIALIZE_ERROR');
63 | }
64 |
65 | // Write
66 | try {
67 | let res = await xapi.command('Macros Macro Save', { Name: DATABASE_NAME, OverWrite: true, body: contents });
68 | return (res.status == 'OK');
69 | }
70 | catch (err) {
71 | console.error(`cannot write contents to macro: ${DATABASE_NAME}`);
72 | throw new Error('DB_ACCESS_ERROR');
73 | }
74 | }
75 |
76 | // read example
77 | /*
78 | read()
79 | .then((data) => console.log(data.foo))
80 | .catch((err) => {
81 | switch (err.message) {
82 | case 'DB_ACCESS_ERROR':
83 | console.log("db not found: is the DB macro deployed ?");
84 | break;
85 | case 'DB_PARSE_ERROR':
86 | console.log(`cannot parse: check the contents are JSON with prefix: ${PREFIX}, in macro with name: ${DATABASE_NAME}`);
87 | break;
88 | }
89 | });
90 | */
91 |
92 | // write example
93 | /*
94 | write({'congrats':'you did it'})
95 | .then((success) => {
96 | if (success) {
97 | console.log('contents persisted');
98 | }
99 | })
100 | .catch((err) => {
101 | switch (err.message) {
102 | case 'DB_ACCESS_ERROR':
103 | console.log(`cannot write to DB: ${DATABASE_NAME}`);
104 | break;
105 | case 'DB_SERIALIZE_ERROR':
106 | console.log(`cannot serialize contents as JSON`);
107 | break;
108 | }
109 | });
110 | */
111 |
112 | xapi.on('ready', async () => {
113 | await write({ player : 'steve', score: 5 });
114 | let data = await read();
115 | console.log(`player score is: ${data.score}`)
116 | });
117 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/macros/15-cipher.js:
--------------------------------------------------------------------------------
1 | //
2 | // Extract from https://stackoverflow.com/questions/18279141/javascript-string-encryption-and-decryption
3 | // Contributed by https://stackoverflow.com/users/2861702/jorgeblom
4 | //
5 |
6 | /*
7 | * Simplist symetric encoding using a shared secret
8 | *
9 | */
10 | const cipher = salt => {
11 | const textToChars = text => text.split('').map(c => c.charCodeAt(0));
12 | const byteHex = n => ("0" + Number(n).toString(16)).substr(-2);
13 | const applySaltToChar = code => textToChars(salt).reduce((a,b) => a ^ b, code);
14 |
15 | return text => text.split('')
16 | .map(textToChars)
17 | .map(applySaltToChar)
18 | .map(byteHex)
19 | .join('');
20 | }
21 |
22 | const decipher = salt => {
23 | const textToChars = text => text.split('').map(c => c.charCodeAt(0));
24 | const applySaltToChar = code => textToChars(salt).reduce((a,b) => a ^ b, code);
25 | return encoded => encoded.match(/.{1,2}/g)
26 | .map(hex => parseInt(hex, 16))
27 | .map(applySaltToChar)
28 | .map(charCode => String.fromCharCode(charCode))
29 | .join('');
30 | }
31 |
32 | // Example
33 | const text = 'Vision without execution is hallucination';
34 | console.log(`BEFORE: ${text}`);
35 | const encrypted = cipher('not so secret')(text);
36 | console.log(`ENCRYPTED: ${encrypted}`);
37 |
38 | const decrypted = decipher('not so secret')(encrypted);
39 | console.log(`DECRYPTED: ${decrypted}`);
40 |
41 | if (text !== decrypted) {
42 | console.error('failed');
43 | }
--------------------------------------------------------------------------------
/macros/17-persistenv.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | // An advanced implementation for the Environment Macro that persists ENV values to the local device (using an 'ENV' Macro to serialize JSON)
7 |
8 | const xapi = require('xapi');
9 |
10 | // Read ENV from local storage
11 | const DEFAULT_ENV = {PING:'PONG'};
12 | let ENV;
13 | xapi.on('ready', async () => {
14 | let data = await read();
15 |
16 | // if env is empty, create a new storage with default env
17 | if (!data || (!data.PING)) {
18 | console.info('No existing ENV, creating default');
19 | ENV = DEFAULT_ENV;
20 | await write(ENV);
21 | }
22 | else {
23 | ENV = data;
24 | }
25 | });
26 |
27 | xapi.event.on('Message Send Text', (msg) => {
28 | console.debug(`new "Message Send" Event with text: ${msg}`);
29 |
30 | // WORKAROUND: needed if message was sent from the cloud via POST https://api.ciscospark.com/v1/xapi/command/message.send
31 | msg = msg.replace(/\'/g, '"');
32 | let parsed;
33 | try {
34 | parsed = JSON.parse(msg);
35 | }
36 | catch (err) {
37 | console.err(`cannot JSON parse the text in 'Message Send' Event: ${msg}, ignoring...`);
38 | return
39 | }
40 |
41 | let data = parsed;
42 | if (!data.env) {
43 | console.debug(`"Message Send" Event is not about Env: ${msg}, ignoring`);
44 | return;
45 | }
46 |
47 | console.debug(`"Message Sent" event concerns env variable: "${data.env}"`);
48 |
49 | // GET ENV
50 | if (data.operation && (data.operation == 'get')) {
51 |
52 | if (!data.env) {
53 | console.warn(`no ENV variable specified in: "${msg}", ignoring...`);
54 | return;
55 | }
56 |
57 | console.debug(`requested value for env variable: "${data.env}"`);
58 | let response = {
59 | 'env': data.env,
60 | 'operation': 'get_response',
61 | 'value': '' // default to '' if variable is not found
62 | }
63 | let value = ENV[data.env];
64 | if (value) {
65 | response.value = value;
66 | }
67 |
68 | // Publish value for ENV variable
69 | xapi.command('Message Send', { Text: JSON.stringify(response) });
70 | return;
71 | }
72 |
73 | // RESPONSE provided => ignore
74 | if (data.operation && (data.operation == 'get_response')) {
75 | console.debug(`ignoring get_response type of "Message Sent" event: ${msg}`);
76 | return;
77 | }
78 |
79 | // SET ENV
80 | // Note that the storage is volatile (dynamically added variables will be lost every time the macro or runtime is restared)
81 | if (data.operation && (data.operation == 'set')) {
82 |
83 | console.log(`setting value: "${data.value}" for env variable: "${data.env}"`);
84 | ENV[data.env] = data.value;
85 |
86 | // Write to local storage
87 | try {
88 | write(ENV);
89 | }
90 | catch(err) {
91 | console.warn(`could not write variable to ENV, err: ${err.message}`)
92 | }
93 | return;
94 | }
95 |
96 | console.warn(`operation for "Message Sent" event is not supported:"${msg}, ignoring...`);
97 | return;
98 | })
99 |
100 |
101 | const PREFIX = 'const json = ';
102 | const DATABASE_NAME = 'ENV'; // name of the macro with db contents
103 |
104 | // Read database contents
105 | async function read() {
106 | // Load contents
107 | let contents;
108 | try {
109 | let macro = await xapi.command('Macros Macro Get', { Name: DATABASE_NAME, Content: true })
110 | contents = macro.Macro[0].Content.substring(PREFIX.length);
111 | }
112 | catch(err) {
113 | console.error(`cannot load contents from macro: ${DATABASE_NAME}`);
114 | throw new Error("DB_ACCESS_ERROR");
115 | }
116 |
117 | // Parse contents
118 | try {
119 | console.debug(`DB contains: ${contents}`);
120 | let data = JSON.parse(contents);
121 | console.debug('DB successfully parsed');
122 | return data;
123 | }
124 | catch (err) {
125 | console.error('DB is corrupted, cannot JSON parse the DB');
126 | throw new Error('DB_PARSE_ERROR');
127 | }
128 | }
129 |
130 | // Write database contents
131 | async function write(data) {
132 | // Serialize data as JSON and append prefix
133 | let contents;
134 | try {
135 | contents = PREFIX + JSON.stringify(data);
136 | }
137 | catch (err) {
138 | console.debug('Contents cannot be serialized to JSON');
139 | throw new Error('DB_SERIALIZE_ERROR');
140 | }
141 |
142 | // Write
143 | try {
144 | let res = await xapi.command('Macros Macro Save', { Name: DATABASE_NAME, OverWrite: true, body: contents });
145 | return (res.status == 'OK');
146 | }
147 | catch (err) {
148 | console.error(`cannot write contents to macro: ${DATABASE_NAME}`);
149 | throw new Error('DB_ACCESS_ERROR');
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/macros/18-hue_disco.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | /*
7 | * This macro will enlighten your meeting room,
8 | * no complementary In-Room control needed:
9 | *
10 | * pre-req:
11 | * - configure the macro with your Hue Bridge info and you're good to go.
12 | * - configure your device for HttpClient
13 | *
14 | */
15 |
16 | const xapi = require('xapi');
17 |
18 | xapi.on('ready', () => {
19 | disco1(1000, false);
20 | //disco2(0, 1000, false);
21 | });
22 |
23 | // Blinks at a regular delay, with changing color
24 | // example: disco1(1000, false);
25 |
26 | function disco1(delay, black) {
27 | let color = COLOR_BLACK;
28 | if (!black) {
29 | color = {
30 | hue: Math.round(Math.random() * 65535),
31 | on: true,
32 | sat: 255
33 | };
34 | }
35 |
36 | // comment this line to remove blinking effect
37 | black = !black;
38 | changeColor(color);
39 |
40 | const MIN_DELAY = 300;
41 | setTimeout(function () {
42 | disco1(delay + MIN_DELAY, black);
43 | }, delay);
44 | }
45 |
46 | // Blinks at random delays and random colors
47 | // example: disco2(300, 1000, false);
48 | function disco2(delay, maxDelay, black) {
49 | // Randomize color
50 | let color = COLOR_BLACK;
51 | if (delay < 500 || !black) {
52 | color = {
53 | hue: Math.round(Math.random() * 65535),
54 | on: true,
55 | sat: 255
56 | };
57 | }
58 |
59 | // comment this line to remove blinking effect
60 | black = !black;
61 | changeColor(color);
62 |
63 | // Randomize delay
64 | const MIN_DELAY = 300;
65 | let nextDelay = Math.round(Math.random() * maxDelay) + MIN_DELAY;
66 | setTimeout(function () {
67 | disco2(nextDelay, maxDelay + MIN_DELAY, black);
68 | }, delay);
69 | }
70 |
71 |
72 | //
73 | // Hue Library
74 | //
75 |
76 | // ACTION: Configure for your Philips Hue deployment
77 | const HUE_BRIDGE = '192.168.1.33';
78 | const HUE_USERNAME = 'EM2Vg2GtNUqAASukv47wm1pWY0FayFe48D03f6Cb';
79 | const HUE_LIGHT = 1; // number of the light in your deployment
80 |
81 | console.info(`Hue Bridge with ip: ${HUE_BRIDGE}, bulb: ${HUE_LIGHT}`);
82 | const hue_url = `http://${HUE_BRIDGE}/api/${HUE_USERNAME}/lights/${HUE_LIGHT}/state`;
83 |
84 | const COLOR_RED = { on: true, hue: 65535, sat: 255 };
85 | const COLOR_BLUE = { on: true, hue: 46920, sat: 255 };
86 | const COLOR_GREEN = { on: true, hue: 25500, sat: 255 };
87 | const COLOR_WHITE = { on: true, hue: 0, sat: 0 };
88 | const COLOR_BLACK = { on: false };
89 |
90 | function changeColor(color) {
91 |
92 | console.debug(`posting to hue bulb: ${JSON.stringify(color)}`)
93 |
94 | // Post message
95 | xapi.command(
96 | 'HttpClient Put',
97 | {
98 | Header: ["Content-Type: application/json"],
99 | Url: hue_url,
100 | AllowInsecureHTTPS: "True"
101 | },
102 | JSON.stringify(color))
103 | .catch((err) => {
104 | console.error('could not contact the Hue Bridge');
105 | });
106 | }
107 |
--------------------------------------------------------------------------------
/macros/19-hue_onair.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2020 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | /*
7 | * Turns red a designated Hue bulb when a call is in progress,
8 | * and keep green when out of call.
9 | *
10 | * pre-req:
11 | * - configure the macro with your Hue Bridge info and you're good to go.
12 | * - configure your device for HttpClient
13 | *
14 | */
15 |
16 | const xapi = require('xapi');
17 |
18 | //
19 | // Hue Library
20 | //
21 |
22 | // ACTION: Update for your Philips Hue deployment
23 | const HUE_BRIDGE = '192.168.1.33';
24 | const HUE_USERNAME = 'EM2Vg2GtNUqAASukv47wm1pWY0FayFe48D03f6Cb';
25 | const HUE_LIGHT = 1; // number of the light in your deployment
26 |
27 | console.info(`Hue Bridge with ip: ${HUE_BRIDGE}, bulb: ${HUE_LIGHT}`);
28 | const hue_url = `http://${HUE_BRIDGE}/api/${HUE_USERNAME}/lights/${HUE_LIGHT}/state`;
29 |
30 | const COLOR_RED = { on: true, hue: 65535, sat: 255};
31 | const COLOR_BLUE = { on: true, hue: 46920, sat: 255};
32 | const COLOR_GREEN = { on: true, hue: 25500, sat: 255};
33 | const COLOR_WHITE = { on: true, hue: 0, sat: 0};
34 | const COLOR_BLACK = { on: false};
35 | function changeColor(color) {
36 |
37 | // Post message
38 | xapi.command('HttpClient Put', {
39 | Header: ["Content-Type: application/json"],
40 | Url: hue_url,
41 | AllowInsecureHTTPS: "True"
42 | },
43 | JSON.stringify(color))
44 | .catch((err) => {
45 | console.error('could not contact the Hue Bridge');
46 | });
47 | }
48 |
49 | //
50 | // Macro
51 | //
52 |
53 | xapi.on('ready', init);
54 |
55 | function init() {
56 | // Green at launch
57 | changeColor(COLOR_GREEN);
58 |
59 | // Listen to active calls and update color accordingly
60 | xapi.status.on("SystemUnit State NumberOfActiveCalls", (activeCalls) => {
61 | console.debug(`NumberOfActiveCalls is: ${activeCalls}`)
62 |
63 | // Turn red if call in progress
64 | if (activeCalls > 0) {
65 | console.info('call in progress');
66 | changeColor(COLOR_RED);
67 | return;
68 | }
69 |
70 | // Turn green if out of call
71 | console.log('no active call');
72 | changeColor(COLOR_GREEN);
73 | });
74 |
75 |
76 | }
--------------------------------------------------------------------------------
/macros/2-showVolume.js:
--------------------------------------------------------------------------------
1 | const xapi = require('xapi');
2 |
3 | xapi.status
4 | .get('Audio Volume')
5 | .then((volume) => { console.log(volume); })
--------------------------------------------------------------------------------
/macros/20-import_main.js:
--------------------------------------------------------------------------------
1 | // utils macro
2 | import xapi from 'xapi';
3 |
4 | export async function getVolume() {
5 | return await xapi.Status.Audio.Volume.get();
6 | }
7 |
8 | export function sleepFor(timeout) {
9 | return new Promise((resolve) => {
10 | setTimeout(resolve, timeout);
11 | });
12 | }
13 |
--------------------------------------------------------------------------------
/macros/20-import_util.js:
--------------------------------------------------------------------------------
1 | // main macro
2 | import { getVolume, sleepFor } from './import_utils';
3 |
4 | async function main() {
5 | const timeout = 2000;
6 | console.log(`fetching volume + sleeping for ${timeout}ms`);
7 |
8 | const [volume] = await Promise.all([
9 | getVolume(),
10 | sleepFor(timeout),
11 | ]);
12 | console.log(`DONE: volume is ${volume}`);
13 | }
14 |
15 | main();
16 |
--------------------------------------------------------------------------------
/macros/3-changeLanguage.js:
--------------------------------------------------------------------------------
1 | const xapi = require('xapi');
2 |
3 |
4 | function onGui(event) {
5 |
6 | console.log("new event");
7 |
8 | if ((event.Type == 'clicked') && (event.WidgetId == 'toFrench')) {
9 | xapi.config.set('UserInterface Language', 'French');
10 | }
11 |
12 | if ((event.Type == 'clicked') && (event.WidgetId == 'toEnglish')) {
13 | xapi.config.set('UserInterface Language', 'English');
14 | }
15 | }
16 |
17 |
18 | xapi.event.on('UserInterface Extensions Widget Action', onGui);
19 |
--------------------------------------------------------------------------------
/macros/4-webhook.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Sends HTTP payload everytime People Analytics are pushed
3 | */
4 | const xapi = require('xapi');
5 |
6 | xapi.command('HttpFeedback Register', {
7 | FeedbackSlot: 2,
8 | ServerUrl: "https://requestb.in/szua2usz",
9 | Format: "JSON",
10 | Expression: ["/Status/RoomAnalytics/PeoplePresence", "/Status/RoomAnalytics/PeopleCount"]
11 | })
12 | .then(console.log("successfully registered Webhook for PeoplePresence & PeopleCount"))
13 | .catch(console.error);
--------------------------------------------------------------------------------
/macros/4b-webhook-controls.js:
--------------------------------------------------------------------------------
1 | const xapi = require('xapi');
2 |
3 | xapi.command('HttpFeedback Register', {
4 | FeedbackSlot: 2,
5 | ServerUrl: "https://requestb.in/156r6651",
6 | Format: "JSON",
7 | Expression: ["/Event/UserInterface/Extensions/Widget/Action"]
8 | })
9 | .then(console.log("successfully registered Webhook for In-Room controls"))
10 | .catch(console.error);
11 |
--------------------------------------------------------------------------------
/macros/5-checkEmail.js:
--------------------------------------------------------------------------------
1 | function isEmail(email) {
2 | // extract from http://stackoverflow.com/questions/46155/validate-email-address-in-javascript
3 | let re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
4 | return re.test(email);
5 | }
6 |
7 | let email1 = "stsfartz@cisco"
8 | console.log(`is an email? ${email1}: ${isEmail(email1)}`)
9 |
10 | let email2 = "stsfartz@cisco.com"
11 | console.log(`is an email? ${email2}: ${isEmail(email2)}`)
12 |
--------------------------------------------------------------------------------
/macros/7-showProgress.js:
--------------------------------------------------------------------------------
1 | function showProgress(count, delay, cb, final, initial) {
2 | let counter = 0;
3 | if (!initial) initial = "";
4 | let progress = ".....".padEnd(count, '.');
5 | setInterval(function (event) {
6 | cb(initial + progress.substring(0, counter + 1));
7 | counter++;
8 | if (counter >= count) {
9 | clearInterval(timer);
10 | cb(final);
11 | }
12 | }, delay);
13 | }
14 |
15 | showProgress(20, 100, console.log, "completed", "working");
16 |
--------------------------------------------------------------------------------
/macros/8-message.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Illustrates how to pass messages among local macros
3 | *
4 | * see 12-primary and 12-secondary examples for communication through remote codecs
5 | */
6 |
7 | const xapi = require('xapi');
8 |
9 |
10 | xapi.event.on("Message Send Text", (text) => {
11 |
12 | // Decode if necessary (example: message sent over xAPI cloud)
13 | let decoded = text.replace(/\'/g, '"');
14 |
15 | // Parse JSON and print
16 | let data = JSON.parse(decoded);
17 | console.log(`Received score: ${data.score}, for player: ${data.player}`);
18 | });
19 |
20 |
21 | let data = {
22 | score: 5,
23 | player: "Stève"
24 | }
25 |
26 | // Serialize as JSON
27 | const serialized = JSON.stringify(data);
28 |
29 | // [CONFIRM] Encoding is needed only if sending over HTTP
30 | const encoded = serialized.replace(/"/g, "\'");
31 |
32 | xapi.command("Message Send", { Text: encoded });
--------------------------------------------------------------------------------
/macros/9-timer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Illustrates a personalized message coupled with a timer
3 | *
4 | */
5 |
6 | const xapi = require('xapi');
7 |
8 |
9 | function update(value) {
10 | //
11 | // Custom logic
12 | //
13 | console.log(value);
14 | }
15 |
16 | let current = {};
17 | current.options = [];
18 | current.iterations = 0;
19 | current.iterate = function (original, max) {
20 | // Reset options if array is empty
21 | if (this.options.length === 0) {
22 | this.options = original;
23 | }
24 |
25 | this.iterations++;
26 | if (this.iterations > max) {
27 | clearInterval(current.timer);
28 | return;
29 | }
30 |
31 | // Pop next option
32 | let choice = this.options.pop();
33 | update(choice)
34 | }
35 |
36 | current.timer = setInterval(function () {
37 | current.iterate(["Read a tutorial", "Launch a Sandbox", "Attend an event", "Pick a DevNet activity among:"], 10);
38 | }, 1000);
39 |
40 |
--------------------------------------------------------------------------------
/macros/README.md:
--------------------------------------------------------------------------------
1 | # Javascript Macros
2 |
3 | Read through the code of these macros to quickly ramp up with the xAPI of your Room Devices.
4 |
5 | Then, reach to the [controls](../controls) to learn more of your Room Device programmability.
6 |
7 | **New to Controls & Macros? check the [QuickStart Guide](../docs/QuickStart.md) to learn to load Controls and Macros to your device**
8 |
--------------------------------------------------------------------------------
/macros/pdf/macro-tutorial.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/xapi-samples/4457a1c4f9f8c6715830fae314b0b721cc612f3f/macros/pdf/macro-tutorial.pdf
--------------------------------------------------------------------------------
/python/.gitignore:
--------------------------------------------------------------------------------
1 | env/
2 |
--------------------------------------------------------------------------------
/python/advanced.py:
--------------------------------------------------------------------------------
1 | # Extract from xows article
2 | # https://community.cisco.com/t5/collaboration-voice-and-video/xapi-over-websocket-xows-ce9-7-x/ba-p/3831553
3 |
4 | import websockets
5 | import ssl
6 | import asyncio
7 | import base64
8 | import json
9 |
10 | count = 0
11 |
12 | async def connect():
13 | return await websockets.connect('wss://{}/ws/'.format('10.10.10.1'), ssl=ssl._create_unverified_context(), extra_headers={
14 | 'Authorization': 'Basic {}'.format(base64.b64encode('{}:{}'.format('admin', '').encode()).decode('utf-8'))})
15 |
16 | def construct(method):
17 | global count
18 | count += 1
19 | return {'jsonrpc': '2.0', 'id': str(count), 'method': method}
20 |
21 | def query(params):
22 | payload = construct('xQuery')
23 | payload['params'] = {'Query': params.split()}
24 | return payload
25 |
26 | def get(params):
27 | payload = construct('xGet')
28 | params = [i if not i.isnumeric() else int(i) for i in params.split()]
29 | payload['params'] = {'Path': params}
30 | return payload
31 |
32 | def command(path, params=None):
33 | payload = construct('{}{}'.format('xCommand/', '/'.join(path.split(' '))))
34 |
35 | # Params are for multiline commands and other command parameters {'ConfigId':'example', 'body':'1.0...'}
36 | if params != None:
37 | payload['params'] = params
38 |
39 | return payload
40 |
41 | def config(path, value):
42 | payload = construct('xSet')
43 | payload['params'] = {
44 | "Path": ['Configuration'] + path.split(' '),
45 | "Value": value
46 | }
47 | return payload
48 |
49 | def feedbackSubscribe(path=None, notify=False):
50 | payload = construct('xFeedback/Subscribe')
51 | payload['params'] = {
52 | "Query": path.split(' '),
53 | "NotifyCurrentValue": notify
54 | }
55 | return payload
56 |
57 | async def send(ws, message):
58 | await ws.send(json.dumps(message))
59 | print('Sending:', message)
60 |
61 | async def receive(ws):
62 | result = await ws.recv()
63 | print('Receive:', result)
64 |
65 | async def task():
66 | ws = await connect()
67 | try:
68 | await send(ws, get('Status SystemUnit Uptime'))
69 | await receive(ws)
70 | finally:
71 | asyncio.run(ws.close())
72 |
73 | asyncio.run(task())
--------------------------------------------------------------------------------
/python/requirements.txt:
--------------------------------------------------------------------------------
1 | websockets==8.1
2 |
--------------------------------------------------------------------------------
/python/websocket.py:
--------------------------------------------------------------------------------
1 | # Extract from xows article
2 | # https://community.cisco.com/t5/collaboration-voice-and-video/xapi-over-websocket-xows-ce9-7-x/ba-p/3831553
3 |
4 | import websockets
5 | import ssl
6 | import asyncio
7 | import base64
8 |
9 |
10 | async def connect():
11 | return await websockets.connect('wss://{}/ws/'.format('192.168.1.3'), ssl=ssl._create_unverified_context(), extra_headers={
12 | 'Authorization': 'Basic {}'.format(base64.b64encode('{}:{}'.format('localadmin', 'ciscopsdt').encode()).decode('utf-8'))})
13 |
14 |
15 | async def send(ws, message):
16 | await ws.send(message)
17 | print('Sending:', message)
18 |
19 |
20 | async def receive(ws):
21 | result = await ws.recv()
22 | print('Receive:', result)
23 |
24 |
25 | async def task():
26 | try:
27 | ws = await connect()
28 | except TimeoutError as timeout:
29 | print('could not connect to device, error: ', timeout)
30 | else:
31 | try:
32 | await send(ws, '{"jsonrpc": "2.0","id": "0","method": "xGet","params": {"Path": ["Status", "SystemUnit", "State"]}}')
33 | await receive(ws)
34 | finally:
35 | await ws.close()
36 |
37 | asyncio.run(task())
38 |
--------------------------------------------------------------------------------
/pyxows/README.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | Before running any of these examples, you need to first install the 'xows' package on your local machine.
4 |
5 | To add the 'xows' package to your environment, type:
6 |
7 | > Note: by default, the commands below adds the 'xows' package (e.g. xows-1.0.2-py3.7.egg) to the site packages of the current user. Use the following command to install the package for another user `python setup.py install [--user]`
8 |
9 | ```bash
10 | git clone https://github.com/cisco-ce/pyxows
11 | cd pyxows
12 | python setup.py install
13 | ```
14 |
15 |
16 | To check the 'xows' package is installed on your machine, type:
17 |
18 | ```bash
19 | pip show xows
20 | ```
21 |
--------------------------------------------------------------------------------
/pyxows/connection_timeout.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2019 Cisco Systems
2 | # Licensed under the MIT License
3 |
4 | #
5 | # Script to keep the audio volume level set to a maximum value
6 | #
7 |
8 | import xows
9 | import aiohttp
10 | import asyncio
11 |
12 |
13 | async def task():
14 | try:
15 | async with xows.XoWSClient('192.168.1.3', username='localadmin', password='ciscopsdt') as client:
16 | def callback(data, id):
17 | print(f'New event received with id: ({id}): {data}')
18 |
19 | print('Created subscription with number: ',
20 | await client.subscribe(['Status', 'Audio', 'Volume'], callback, True))
21 |
22 | await client.wait_until_closed()
23 |
24 | except xows.XoWSError as error:
25 | print('Timeout, could not connect to device. Exiting...')
26 | print('error', error)
27 | except TimeoutError:
28 | print('TimeoutError')
29 | except aiohttp.client_exceptions.ClientConnectorError:
30 | print('ClientConnectorError')
31 |
32 |
33 | try:
34 | asyncio.run(task())
35 | except KeyboardInterrupt:
36 | print('Interrupted, exiting...')
37 |
--------------------------------------------------------------------------------
/pyxows/control_volume.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2019 Cisco Systems
2 | # Licensed under the MIT License
3 |
4 | #
5 | # Control the volume of a CE-powered device
6 | #
7 |
8 | import xows
9 | import asyncio
10 | import os
11 |
12 | async def control_volume(device_url, user, passwd):
13 |
14 | async with xows.XoWSClient(url_or_host=device_url, username=user, password=passwd) as client:
15 |
16 | MAX_VOLUME = 60
17 | def callback(event, id):
18 | if id == callback_id:
19 | volume = event['Status']['Audio']['Volume']
20 | print(f'Volume currently set at level: {volume}')
21 |
22 | if volume > MAX_VOLUME:
23 | print(f'Volume above max authorized, turning down to: {MAX_VOLUME}')
24 | asyncio.create_task(client.xCommand(['Audio', 'Volume', 'Set'], Level=MAX_VOLUME))
25 |
26 | callback_id = await client.subscribe(['Status', 'Audio', 'Volume'], callback, True)
27 |
28 | await client.wait_until_closed()
29 |
30 | try:
31 | device = os.environ.get('DEVICE_IP', default='192.168.1.32')
32 | user = os.environ.get('DEVICE_USER', default='localadmin')
33 | passwd = os.environ.get('DEVICE_PASSWD', default='ciscopsdt')
34 | asyncio.run(control_volume(device, user, passwd))
35 | except AttributeError:
36 | print('invalid credentials')
37 | except KeyboardInterrupt:
38 | print('exiting')
39 |
--------------------------------------------------------------------------------
/pyxows/usage.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2019 Cisco Systems
2 | # Licensed under the MIT License
3 |
4 | #
5 | # Usage sample from README.md at https://github.com/cisco-ce/pyxows
6 | #
7 |
8 | import xows
9 | import asyncio
10 |
11 | async def start():
12 | async with xows.XoWSClient(url_or_host='192.168.1.32', username='localadmin', password='ciscopsdt') as client:
13 | def callback(data, id_):
14 | print(f'Feedback (Id {id_}): {data}')
15 |
16 | print('Status Query:',
17 | await client.xQuery(['Status', '**', 'DisplayName']))
18 |
19 | print('Get:',
20 | await client.xGet(['Status', 'Audio', 'Volume']))
21 |
22 | print('Command:',
23 | await client.xCommand(['Audio', 'Volume', 'Set'], Level=60))
24 |
25 | print('Configuration:',
26 | await client.xSet(['Configuration', 'Audio', 'DefaultVolume'], 50))
27 |
28 | print('Subscription 1:',
29 | await client.subscribe(['Status', 'Audio', 'Volume'], callback, True))
30 |
31 | await client.wait_until_closed()
32 |
33 | asyncio.run(start())
--------------------------------------------------------------------------------
/web/README.md:
--------------------------------------------------------------------------------
1 | # xAPI from Web pages
2 |
3 | To invoke xAPI from an HTML page, you first need to generate a packaged distribution for the jsxapi.
4 |
5 | From a bash terminal, run the commands below to generate a `jsxapi.min.js` file:
6 |
7 | ```shell
8 | git clone https://github.com/cisco-ce/jsxapi
9 | cd jsxapi
10 | npm run build:dist:min
11 | cd dist
12 | ```
13 |
14 |
--------------------------------------------------------------------------------
/web/example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | xAPI from a Web Page
8 |
9 |
10 |
11 |
12 |
20 |
21 |
22 |
42 |
43 |
44 |
--------------------------------------------------------------------------------