├── Webex Board Browser
├── README.md
└── script.js
├── .DS_Store
├── Sample Code
├── roomcontrolconfig.xml
└── sample_code.js
├── Camera Mode Toggle
├── roomcontrolconfig.xml
├── Disconnect_Camera_Reset.js
└── Camera_Mode_Toggle.js
├── MSTF Teams CVI Manual Join
├── MSFT_ui.xml
└── MSFT_join.js
├── No DND
└── no_DND.js
├── Banking Assistant 2.0
├── banking_2.0.xml
└── banking_2.0.js
├── Banking Assistant
├── banking_assistant.js
└── banking_room_config.xml
├── PIN
└── PinCodeOriginal.js
├── LICENSE
├── Support Tool
├── support_tab.js
└── roomcontrolconfig.xml
├── Zoom DTMF Controls
└── Zoom Dialer.js
├── OBTP Meeting Auto Start
└── obtp_meeting_auto_start.js
├── README.md
├── Calculator
├── Calculator.js
└── calculator.xml
└── Zoom Button
└── zoom_dial.js
/Webex Board Browser/README.md:
--------------------------------------------------------------------------------
1 | Webex Board Web Browser Macro
2 |
--------------------------------------------------------------------------------
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thetechcatalyst/CE9-projects/HEAD/.DS_Store
--------------------------------------------------------------------------------
/Sample Code/roomcontrolconfig.xml:
--------------------------------------------------------------------------------
1 |
2 | 1.5
3 |
4 | button1
5 | Statusbar
6 | Lightbulb
7 | 1
8 | #FF7033
9 | Button1
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Camera Mode Toggle/roomcontrolconfig.xml:
--------------------------------------------------------------------------------
1 |
2 | 1.5
3 |
4 | CameraToggle
5 | Statusbar
6 | Camera
7 | 1
8 | #FF503C
9 | Presenter Track
10 |
11 |
12 |
--------------------------------------------------------------------------------
/MSTF Teams CVI Manual Join/MSFT_ui.xml:
--------------------------------------------------------------------------------
1 |
2 | 1.7
3 |
4 | 1
5 | MSFT
6 | local
7 | Home
8 | Camera
9 | #A866FF
10 | Microsoft CVI Join
11 | Custom
12 |
13 |
14 |
--------------------------------------------------------------------------------
/No DND/no_DND.js:
--------------------------------------------------------------------------------
1 | //This code listens for a DND button press and then turns DND back off
2 | //esentially voiding the DND feature
3 |
4 | const xapi = require('xapi');
5 |
6 | function DND_off() {
7 | xapi.status.on('Conference DoNotDisturb', (status) => {
8 | //console.log(`Status is: ${status}`);
9 | if(status == 'Active'){
10 | console.log('Active');
11 | xapi.command('Conference DoNotDisturb Deactivate');
12 |
13 | }
14 | });
15 | }
16 |
17 | DND_off();
18 |
--------------------------------------------------------------------------------
/Banking Assistant 2.0/banking_2.0.xml:
--------------------------------------------------------------------------------
1 |
2 | 1.5
3 |
4 | general
5 | Statusbar
6 | Helpdesk
7 | 1
8 | #D541D8
9 | General Support
10 |
11 |
12 | mortgage
13 | Statusbar
14 | Home
15 | 2
16 | #07C1E4
17 | Mortgage
18 |
19 |
20 | wealth
21 | Statusbar
22 | Briefing
23 | 3
24 | #FFB400
25 | Wealth Management
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Banking Assistant 2.0/banking_2.0.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2018 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | const xapi = require('xapi');
7 |
8 | //change number / SIP URI below for your requirements
9 | const dial_general = '1000';
10 | const dial_mortgage = '2000';
11 | const dial_wealth = '3000';
12 |
13 |
14 | function quickDial(event) {
15 | //console.log(event); //logs the information passed in the "event" variable
16 |
17 | if (event.PanelId === 'general') {
18 | xapi.command('Dial', { Number: dial_general });
19 | }
20 |
21 | if (event.PanelId === 'mortgage') {
22 | xapi.command('Dial', { Number: dial_mortgage });
23 | }
24 |
25 | if (event.PanelId === 'wealth') {
26 | xapi.command('Dial', { Number: dial_wealth });
27 | }
28 |
29 | }
30 |
31 | //Listen for event to happen.
32 | xapi.event.on('UserInterface Extensions Panel Clicked', quickDial);
33 |
--------------------------------------------------------------------------------
/Banking Assistant/banking_assistant.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2018 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | /**
7 | * A small In-Room controls panel with 4 quick dial numbers
8 | */
9 | const xapi = require('xapi');
10 |
11 | // These match the widget ids of the In-Room control buttons
12 | // change the numbers here to match your organization. SIP URI's are also acceptable
13 | const numbers = {
14 | dial_lending: '1000',
15 | dial_mortgage: '1000',
16 | dial_wealth: '1000',
17 | dial_reception: '1000',
18 | };
19 |
20 | function dial(number) {
21 | console.log('dial', number);
22 | xapi.command('dial', { Number: number });
23 | }
24 |
25 | function listenToGui() {
26 | xapi.event.on('UserInterface Extensions Widget Action', (event) => {
27 | if (event.Type === 'clicked') {
28 | const number = numbers[event.WidgetId];
29 | if (number) dial(number);
30 | else console.log('Unknown button pressed', event.WidgetId);
31 | }
32 | });
33 | }
34 |
35 | listenToGui();
36 |
--------------------------------------------------------------------------------
/Sample Code/sample_code.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2018 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | /**
7 | * Sample Code written by Adam Schaeffer
8 | * http://technologyordie.com
9 | */
10 | const xapi = require('xapi');
11 |
12 | //print out the full content of the event object
13 | function test1(event) {
14 | console.log(event);
15 | }
16 |
17 | //print panel id to log file
18 | function test2(event) {
19 | console.log("The button pressed was " + event.PanelId);
20 | }
21 |
22 | //put an on screen popup for 10 seconds with panel id
23 | //Equal to: xcommand UserInterface Message Alert Display Duration: 10 Text: "The event id is: panel1" Title: "eventid"
24 | function test3(event) {
25 | xapi.command('UserInterface Message Alert Display', {
26 | Title: 'Event ID',
27 | Text: 'The event ID is: ' + event.PanelId,
28 | Duration: 10,
29 | });
30 | }
31 |
32 | //Listen for event to happen. Change callback function to experiment
33 | xapi.event.on('UserInterface Extensions Panel Clicked', test1);
34 |
--------------------------------------------------------------------------------
/PIN/PinCodeOriginal.js:
--------------------------------------------------------------------------------
1 | const xapi = require('xapi');
2 |
3 | const PIN_CODE = '1234';
4 |
5 | function askForPin(text = 'Enter PIN code') {
6 | xapi.command('UserInterface Message TextInput Display', {
7 | FeedbackId: 'pin-code',
8 | Text: text,
9 | InputType: 'PIN',
10 | Placeholder: ' ',
11 | Duration: 0,
12 | });
13 | }
14 |
15 | function onResponse(code) {
16 | console.log('try pin', code);
17 | if (code === PIN_CODE)
18 | {
19 | console.log('pin correct');
20 | }
21 | else {
22 | console.log('pin failed');
23 | askForPin('Incorrect PIN, try again');
24 | }
25 | }
26 |
27 | function listenToEvents() {
28 | xapi.event.on('UserInterface Message TextInput Response', (event) => {
29 | if (event.FeedbackId === 'pin-code')
30 | onResponse(event.Text);
31 | });
32 | xapi.event.on('UserInterface Message TextInput Clear', (event) => {
33 | if (event.FeedbackId === 'pin-code') {
34 | xapi.command('Standby Halfwake');
35 | }
36 | });
37 | xapi.status.on('Standby State', (state) => {
38 | if (state === 'Off') askForPin();
39 | });
40 | }
41 |
42 | listenToEvents();
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 |
3 | MIT License
4 |
5 | Copyright (c) 2018 Cisco Systems
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.
24 |
--------------------------------------------------------------------------------
/Support Tool/support_tab.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2018 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | /**
7 | * A small In-Room controls panel with 4 quick dial numbers
8 | */
9 | const xapi = require('xapi');
10 |
11 |
12 |
13 | //Configured options for Email support popup box
14 | const email_popup = {
15 | FeedbackId: 1,
16 | Placeholder: "Type your notes here",
17 | SubmitText: "Submit",
18 | Text: "Breifly describe the issue you are having with this system.",
19 | Title: "What seems to be the problem?",
20 | };
21 |
22 | //Phone number or SIP URI for support (or support hunt group)
23 | const support_number = '1000';
24 |
25 |
26 | function dial(number) {
27 | xapi.command('dial', { Number: number });
28 | }
29 |
30 |
31 | function listenToUI() {
32 | xapi.event.on('UserInterface Extensions Widget Action', (event) => {
33 |
34 | if (event.WidgetId === 'support_email'){
35 | xapi.command('UserInterface Message TextInput Display', email_popup);
36 | }
37 |
38 | if (event.WidgetId === 'support_call'){
39 | dial(support_number);
40 | }
41 |
42 | });
43 | }
44 |
45 |
46 | listenToUI();
47 |
--------------------------------------------------------------------------------
/Camera Mode Toggle/Disconnect_Camera_Reset.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2018 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 | // See additional details at
6 | //http://technologyordie.com/cisco-sx80-presenter-track-speaker-track-toggle-macro
7 | //
8 |
9 | const xapi = require('xapi');
10 |
11 | xapi.event.on('CallDisconnect', (event) => {
12 | console.log('Resetting cameras to Speaker Track mode for the next caller.');
13 | enableSpeakerTrack();
14 | });
15 |
16 | function handleError(error){
17 | console.log('Error', error);
18 | }
19 |
20 | function setButtonPresenterTrack(){
21 | xapi.command('UserInterface Extensions Panel Save', {PanelId: 'CameraToggle'},
22 | `
23 |
24 | 1.5
25 |
26 | CameraToggle
27 | Statusbar
28 | Camera
29 | 1
30 | #FF503C
31 | Presenter Track
32 |
33 |
34 | `)
35 | .catch(handleError);
36 | }
37 |
38 | function enableSpeakerTrack(){
39 | xapi.command('Cameras SpeakerTrack Activate').catch(handleError);
40 | setButtonPresenterTrack();
41 | }
42 |
--------------------------------------------------------------------------------
/Banking Assistant/banking_room_config.xml:
--------------------------------------------------------------------------------
1 |
2 | 1.4
3 |
4 | Handset
5 | Home
6 |
7 | Virtual Support
8 |
9 | Row
10 |
11 | dial_reception
12 | Reception
13 | Button
14 | size=4
15 |
16 |
17 | dial_lending
18 | Lending
19 | Button
20 | size=2
21 |
22 |
23 | dial_mortgage
24 | Mortgages
25 | Button
26 | size=2
27 |
28 |
29 | dial_wealth
30 | Wealth Management
31 | Button
32 | size=3
33 |
34 |
35 | hideRowNames=1
36 |
37 | Quick-dial
38 |
39 |
--------------------------------------------------------------------------------
/MSTF Teams CVI Manual Join/MSFT_join.js:
--------------------------------------------------------------------------------
1 | const xapi = require('xapi')
2 | const DIALPAD_ID = 'dailpad';
3 | const INROOMCONTROL_WEBCONTROL_PANELID = "MSFT";
4 | const videoDomain = 'sitename'; //this is the site name associated with your CVI instance
5 | const webexDomain = '@m.webex.com'
6 | const finalDomain = '.' + videoDomain + webexDomain
7 | var teamsnumbertodial = '';
8 |
9 | function showDialPad(){
10 | xapi.command("UserInterface Message TextInput Display", {
11 | InputType: 'Numeric'
12 | , Placeholder: 'Conference ID'
13 | , Title: "Microsoft Teams Call"
14 | , Text: "Enter the VTC Conference ID"
15 | , SubmitText: "Dial"
16 | , FeedbackId: DIALPAD_ID
17 | }).catch((error) => { console.error(error); });
18 | }
19 |
20 | function listenToGui(){
21 | xapi.event.on('UserInterface Extensions Panel Clicked', (event) => {
22 | JSON.stringify(event);
23 | if(event.PanelId === INROOMCONTROL_WEBCONTROL_PANELID) {
24 | showDialPad();
25 | }
26 | }
27 | );
28 | }
29 |
30 | xapi.event.on('UserInterface Message TextInput Response', (event) => {
31 | switch(event.FeedbackId){
32 | case DIALPAD_ID:
33 | teamsnumbertodial = event.Text + finalDomain;
34 | xapi.command("dial",{Number: teamsnumbertodial}).catch((error) =>
35 | {console.error(error); });
36 | break;
37 | }
38 | });
39 |
40 | listenToGui();
41 |
--------------------------------------------------------------------------------
/Support Tool/roomcontrolconfig.xml:
--------------------------------------------------------------------------------
1 |
2 | 1.4
3 |
4 | Helpdesk
5 | Statusbar
6 |
7 | *** Contact Support ***
8 |
9 | Support Options:
10 |
11 | support_call
12 | Call Support
13 | Button
14 | size=2
15 |
16 |
17 | support_email
18 | Email Support
19 | Button
20 | size=2
21 |
22 |
23 |
24 | Support Information:
25 |
26 | widget_1
27 | Email: support@domain.local
28 | Text
29 | size=4;fontSize=normal;align=left
30 |
31 |
32 | widget_2
33 | Phone: +18005551234
34 | Text
35 | size=4
36 |
37 |
38 | support_options
39 |
40 |
41 | Panel
42 |
43 |
--------------------------------------------------------------------------------
/Zoom DTMF Controls/Zoom Dialer.js:
--------------------------------------------------------------------------------
1 | import xapi from 'xapi';
2 |
3 |
4 | async function sendDTMF(code){
5 | try {
6 | await
7 | xapi.command("Call DTMFSend", {DTMFString: code});
8 | } catch (error) {
9 | console.error(error);
10 | }
11 | }
12 |
13 |
14 | xapi.Event.UserInterface.Extensions.Widget.Action.on((event) => {
15 | if (event.Type !== 'pressed'){
16 | return;
17 | }
18 | var DTMF;
19 |
20 | switch (event.WidgetId) {
21 | case "changelayout":
22 | DTMF = "11";
23 | console.log('Change Layout was Pressed');
24 | break;
25 | case "audiomute":
26 | DTMF = "12";
27 | console.log('Audio was Pressed');
28 | break;
29 | case "videomute":
30 | DTMF = "14";
31 | console.log('Video Mute was Pressed');
32 | break;
33 | case "record":
34 | DTMF = "15";
35 | console.log('Record was Pressed');
36 | break;
37 | case "videonames":
38 | DTMF = "102";
39 | console.log('Show/Hide Names was Pressed');
40 | break;
41 | case "mute_on_entry":
42 | DTMF = "103";
43 | console.log('Mute on Entry was Pressed');
44 | break;
45 | case "participants_show":
46 | DTMF = "16";
47 | console.log('Show Participants was Pressed');
48 | break;
49 | case "exit":
50 | DTMF = "*";
51 | console.log('Exit was Pressed');
52 | break;
53 | }
54 |
55 | sendDTMF(DTMF);
56 |
57 | });
58 |
59 |
--------------------------------------------------------------------------------
/Webex Board Browser/script.js:
--------------------------------------------------------------------------------
1 | const xapi = require('xapi');
2 |
3 | /*
4 | Note, you will need ot create a UI object with the panelId of "browser" for this to work
5 | */
6 |
7 | //JSON settings to the text input window
8 | const textInput = {
9 | "Text": "Enter URL or Search String",
10 | "FeedbackId": "url",
11 | "Title": "Browse the Internet"
12 | };
13 |
14 | //verify string is a URL
15 | function validURL(str) {
16 | var pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol
17 | '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name
18 | '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address
19 | '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path
20 | '(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string
21 | '(\\#[-a-z\\d_]*)?$','i'); // fragment locator
22 | return !!pattern.test(str);
23 | }
24 |
25 | //launch browser with properly formatted URL
26 | function open(url) {
27 | console.log('Real URL is: ' + url);
28 | xapi.command('UserInterface WebView Display', { Url: url })
29 | .catch(e => console.log('Not able to open url', e.toString()))
30 | }
31 |
32 | //launch URL input window when browse button is pressed
33 | function browseButton(event) {
34 | //console.log('Pressed button', event.PanelId);
35 | console.log(event);
36 | if (event.PanelId === 'browser') {
37 |
38 | xapi.command('UserInterface Message TextInput Display', textInput);
39 |
40 | }
41 | }
42 |
43 | function browseEvent(event){
44 |
45 | if (event.FeedbackId === 'url') {
46 |
47 | //open(event.Text);
48 | //console.log(validURL(event.Text));
49 | if (validURL(event.Text)) {
50 | //console.log('Opening: ' + event.Text);
51 | open('http://' + event.Text); //open the url directly
52 | }
53 | else {
54 | //console.log('Opening: ' + 'https://www.google.com/search?q=' + event.Text);
55 | open('https://www.google.com/search?q=' + event.Text); //Perform search on google
56 | }
57 | }
58 | }
59 |
60 |
61 | xapi.event.on('UserInterface Extensions Panel Clicked', browseButton);
62 | xapi.event.on('UserInterface Message TextInput Response', browseEvent);
63 |
--------------------------------------------------------------------------------
/Camera Mode Toggle/Camera_Mode_Toggle.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2018 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 | // Writen by Adam Schaeffer
6 | //
7 | // See additional details at
8 | //http://technologyordie.com/cisco-sx80-presenter-track-speaker-track-toggle-macro
9 | //
10 |
11 | const xapi = require('xapi');
12 |
13 | const presenterTrackConnectorID = 3;
14 |
15 | function handleError(error){
16 | console.log('Error:', error);
17 | }
18 |
19 | function setButtonSpeakerTrack(){
20 | xapi.command('UserInterface Extensions Panel Save', {PanelId: 'CameraToggle'},
21 | `
22 |
23 | 1.5
24 |
25 | CameraToggle
26 | Statusbar
27 | Camera
28 | 1
29 | #FF503C
30 | Speaker Track
31 |
32 |
33 | `)
34 | .catch(handleError);
35 | }
36 |
37 | function setButtonPresenterTrack(){
38 | xapi.command('UserInterface Extensions Panel Save', {PanelId: 'CameraToggle'},
39 | `
40 |
41 | 1.5
42 |
43 | CameraToggle
44 | Statusbar
45 | Camera
46 | 1
47 | #FF503C
48 | Presenter Track
49 |
50 |
51 | `)
52 | .catch(handleError);
53 | }
54 |
55 |
56 | function changeCameraInput(){
57 | xapi.command('Video Input SetMainVideoSource', {
58 | ConnectorId: presenterTrackConnectorID,
59 | }).catch(handleError);
60 | }
61 |
62 | function enablePresenterTrack(){
63 | xapi.command('Cameras PresenterTrack Set', {
64 | Mode: 'Follow',
65 | }).catch(handleError);
66 | setButtonSpeakerTrack();
67 | }
68 |
69 | function enableSpeakerTrack(){
70 | xapi.command('Cameras SpeakerTrack Activate').catch(handleError);
71 | setButtonPresenterTrack();
72 | }
73 |
74 | function presenterTrackChanger(event){
75 | //console.log(event);//for debugging
76 |
77 | xapi.status
78 | .get('Cameras PresenterTrack Status')
79 | .then((value) => {
80 | //console.log(value);
81 |
82 | if(value === 'Off'){
83 | changeCameraInput();
84 | enablePresenterTrack();
85 | console.log('Presenter Track Enabled');
86 | }else{
87 | enableSpeakerTrack();
88 | console.log('Speaker Track Enabled');
89 | }
90 |
91 | });
92 | }
93 |
94 | xapi.event.on('UserInterface Extensions Panel Clicked',presenterTrackChanger);
95 |
--------------------------------------------------------------------------------
/OBTP Meeting Auto Start/obtp_meeting_auto_start.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2018 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | const xapi = require('xapi');
7 |
8 | //dial the number or SIP URI
9 | function dial(number){
10 | //xapi.command('dial', number).catch(e => console.error('Failed to Dial...'));
11 | xapi.command('dial',number).catch(e => console.error('Failed to Dial...'));
12 |
13 | }
14 |
15 |
16 | function bookstart(Id){
17 |
18 | //get the details of the bookings on the system
19 | xapi.command('Bookings List').then((output) => {
20 |
21 | const bookings = output.Booking;
22 |
23 | //loop through all bookings on the system
24 | for (var index = 0; index < bookings.length; ++index) {
25 |
26 | //find the booking that has the same ID as the one that just went active
27 | //(its posible to have multiple bookings living on the system at one time)
28 | if(bookings[index].Id == Id){
29 |
30 | //make number value to pass to dial funtion
31 | var number = {Number: bookings[index].DialInfo.Calls.Call[0].Number};
32 | console.log("Placing call to: " + JSON.stringify(number));
33 | dial(number);
34 |
35 | }
36 | }
37 | });
38 | }
39 |
40 | //Ensures the active called URI matches the URI for the booking that just ended and ends the call if they match
41 | function verify_call_terminate(booking){
42 | xapi.status.get('Call').then((status) => {
43 |
44 | var callbacknumber = status[0].CallbackNumber ;
45 | callbacknumber = callbacknumber.split(":")[1];
46 |
47 | console.log(booking.DialInfo.Calls.Call[0].Number);
48 |
49 | if(callbacknumber == booking.DialInfo.Calls.Call[0].Number){
50 | xapi.command('Call Disconnect').catch(e => console.error('Could not end the call but we tried...'));
51 | }
52 |
53 | });
54 | }
55 |
56 | //When a meeting ends this function is called. It gets the details about the booking that just ended and
57 | //calls the function ultimatly ends the call. The two could be consolidated but the environment warns
58 | //against nesting functions inside of for loops...
59 | function bookend(Id){
60 |
61 | xapi.command('Bookings List').then((output) => {
62 |
63 | const bookings = output.Booking;
64 | for (var index = 0; index < bookings.length; ++index) {
65 | if(bookings[index].Id == Id){
66 | verify_call_terminate(bookings[0]);
67 | }
68 | }
69 | });
70 | }
71 |
72 | //listen for "bookings start" events. May need to register xfeedback /bookings/start event
73 | //I used "xfeedback register /Event" to capture all events (probably not a good idea in production)
74 | xapi.event.on('Bookings Start', (event) => {
75 |
76 | bookstart(event.Id);
77 |
78 | });
79 |
80 | //When a meeting comes to an end on the calendar call the function that ends it
81 | xapi.event.on('Bookings End', (event) => {
82 |
83 | bookend(event.Id);
84 |
85 | });
86 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## CE9-projects
2 | Projects for Cisco DX, MX, SX and Room Systems
3 |
4 | Further examples and descriptions of some of these projects can be found at
5 | [http://technologyordie.com](http://technologyordie.com)
6 |
7 | ### Sample Code
8 | The Sample Code is the code used in the XAPI / Macro intro
9 | video on [YouTube](https://youtu.be/9QHb05iSPBI)
10 |
11 |
12 | ### OBTP Meeting Auto start
13 |
14 | This macro listens for a OBTP booking event to start / stop on the endpoint and starts and stops the meeting
15 | automatically based on this booked meeting. Tested on a DX80 registered to Webex teams.
16 |
17 | ### Support Tools
18 | Support Tools is a menu that can be added to your system to allow users to contact support via call or through a brief email.
19 |
20 | In addition to the XML and JS files you will also need to configure a feedback
21 | mechanism to forward along information submitted with the email ticket.
22 |
23 | You can configure this from the codec CLI as follows
24 |
25 | ```
26 | xcommand HttpFeedback Register FeedbackSlot: 1 Expression: event/userinterface/message/textinput/response Format: JSON ServerUrl: http:///
27 | ```
28 |
29 | And then verify it with
30 |
31 | ```
32 | xstatus HttpFeedback
33 | *s HttpFeedback 1 Expression: "event/userinterface/message/textinput/response"
34 | *s HttpFeedback 1 Format: "JSON"
35 | *s HttpFeedback 1 URL: "http:///"
36 | ** end
37 |
38 | OK
39 | ```
40 |
41 |
42 |
43 | ### Banking Assistant
44 | Dial shortcuts based on a banking use case. You can make simple changes to get
45 | functionality and appearances to suit your applications needs.
46 |
47 |
48 | ### Banking Assistant 2.0
49 | The first version of the banking assistant actually had a windows that opened
50 | options the end use would select. This was problematic because it created a 2
51 | click work flow. In this version I added multiple buttons directly to the Home
52 | Screen that can be pressed directly and the call dialed.
53 |
54 | This Version of the assistant was tested with the CE 9.3 version of software.
55 |
56 | ### Calculator
57 | This is a calculator for use on screen on the DX80.
58 |
59 | Currently this is in early BETA with very limited features.
60 |
61 | ### Camera Mode Toggle
62 |
63 | Check out the blog article on this project [HERE](http://technologyordie.com/cisco-sx80-presenter-track-speaker-track-toggle-macro)
64 |
65 | The camera mode toggle application is for SX80 codecs running CE 9.3 or later.
66 | This code will allow you toggle between Speaker Track and Presenter Track with
67 | only a single touch of the screen.
68 |
69 | Typically the Camera settings are inside a menu and are not 100% intuitive for
70 | all users. There is also the optional "Disconnect_Camera_Reset.js" script that
71 | will set the codec back to Speaker Track after a call concludes so the systems
72 | is ready for the next user and the user knows what to expect.
73 |
74 | ### No DND
75 |
76 | This macro simply watches for a DND Activate event and then again returns the
77 | system to the DND Deactivate state in effect making the DND not available to
78 | the end users ensuring calls always ring through to the endpoint.
79 |
80 | ### Webex Board Browser
81 |
82 | This macro gives the user an interface to key in either a URL or a search
83 | phrase. This then launches the web page in the browser.
84 |
85 | If you would like to see a demo of this check it out [HERE](https://www.youtube.com/watch?v=bKLtaOU1hxc)
86 |
--------------------------------------------------------------------------------
/Calculator/Calculator.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2018 Cisco Systems
3 | // Licensed under the MIT License
4 | //
5 |
6 | //
7 | // Calculator written by Adam Schaeffer
8 | //http://technologyordie.com
9 | //
10 |
11 | const xapi = require('xapi');
12 |
13 | var formula ="";
14 |
15 | function error_handle(error){
16 | console.log('Error',error);
17 | }
18 |
19 | /* write the calulator screen with currnet data or solution */
20 | function write_screen(data){
21 | xapi.command("UserInterface Extensions Widget SetValue", { WidgetId: "display", Value: data}).catch(error_handle);
22 | }
23 |
24 | /* Unset the button's blue background when its press is released */
25 | function clear_button(event) {
26 | xapi.command('UserInterface Extensions Widget UnsetValue', {WidgetId: event.WidgetId}).catch(error_handle);
27 | }
28 |
29 | /* Clear the "screen" of the calculator */
30 | function clear_screen() {
31 | formula = "";
32 | write_screen("");
33 | }
34 |
35 | /*builds the formula and outputs it as text to display */
36 | function formula_builder(event){
37 | formula = formula + event.Value;
38 | console.log(formula);
39 | xapi.command("UserInterface Extensions Widget SetValue", { WidgetId: "display", Value: formula}).catch(error_handle);
40 | }
41 |
42 | /* parse string into values used to execute the math */
43 | function parse_calculation_string(s) {
44 | // --- Parse a calculation string into an array of numbers and operators
45 | var calculation = [],
46 | current = '';
47 | for (var i = 0, ch; ch = s.charAt(i); i++) {
48 | if ('^*/+-'.indexOf(ch) > -1) {
49 | if (current === '' && ch === '-') {
50 | current = '-';
51 | } else {
52 | calculation.push(parseFloat(current), ch);
53 | current = '';
54 | }
55 | } else {
56 | current += s.charAt(i);
57 | }
58 | }
59 | if (current !== '') {
60 | calculation.push(parseFloat(current));
61 | }
62 | return calculation;
63 | }
64 |
65 | /* calulates answer from the array generated by the parsing function */
66 | function calculate(calc) {
67 | var ops = [{'^': (a, b) => Math.pow(a, b)},
68 | {'*': (a, b) => a * b, '/': (a, b) => a / b},
69 | {'+': (a, b) => a + b, '-': (a, b) => a - b}],
70 | newCalc = [],
71 | currentOp;
72 | for (var i = 0; i < ops.length; i++) {
73 | for (var j = 0; j < calc.length; j++) {
74 | if (ops[i][calc[j]]) {
75 | currentOp = ops[i][calc[j]];
76 | } else if (currentOp) {
77 | newCalc[newCalc.length - 1] =
78 | currentOp(newCalc[newCalc.length - 1], calc[j]);
79 | currentOp = null;
80 | } else {
81 | newCalc.push(calc[j]);
82 | }
83 | console.log(newCalc);
84 | }
85 | calc = newCalc;
86 | newCalc = [];
87 | }
88 | if (calc.length > 1) {
89 | console.log('Error: unable to resolve calculation');
90 | return calc;
91 | } else {
92 | return calc[0];
93 | }
94 | }
95 |
96 |
97 | /* listen for events from the calculator widget */
98 | xapi.event.on('UserInterface Extensions Widget Action', (event) => {
99 |
100 | /* If a button is pressed */
101 | if(event.Type === 'pressed'){
102 | switch(event.Value) {
103 | case "C":
104 | clear_screen();
105 | break;
106 | case "=":
107 | //Solve the Problem
108 | formula = calculate(parse_calculation_string(formula));
109 | write_screen(formula);
110 | break;
111 | case "÷":
112 | //replace ÷ with /
113 | event.Value = "/"
114 | formula_builder(event);
115 | break;
116 | default:
117 | formula_builder(event);
118 | //code block
119 | }
120 | }
121 |
122 | /* Clear the blue background of the button when released */
123 | if (event.Type === 'released'){
124 | clear_button(event);
125 | }
126 |
127 | });
128 |
--------------------------------------------------------------------------------
/Calculator/calculator.xml:
--------------------------------------------------------------------------------
1 |
2 | 1.5
3 |
4 | panel_2
5 | Statusbar
6 | Tv
7 | 1
8 | Calculator
9 |
10 | Calculator
11 |
12 |
13 |
14 | display
15 |
16 | Text
17 | size=4;fontSize=small;align=left
18 |
19 |
20 | calc_1
21 | GroupButton
22 | size=4
23 |
24 |
25 | C
26 | C
27 |
28 |
29 | ()
30 |
31 |
32 |
33 | %
34 |
35 |
36 |
37 | ÷
38 | ÷
39 |
40 |
41 |
42 |
43 | calc_2
44 | GroupButton
45 | size=4
46 |
47 |
48 | 7
49 | 7
50 |
51 |
52 | 8
53 | 8
54 |
55 |
56 | 9
57 | 9
58 |
59 |
60 | *
61 | X
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | calc_3
70 | GroupButton
71 | size=4
72 |
73 |
74 | 4
75 | 4
76 |
77 |
78 | 5
79 | 5
80 |
81 |
82 | 6
83 | 6
84 |
85 |
86 | -
87 | -
88 |
89 |
90 |
91 |
92 |
93 | Row
94 |
95 | calc_4
96 | GroupButton
97 | size=4
98 |
99 |
100 | 1
101 | 1
102 |
103 |
104 | 2
105 | 2
106 |
107 |
108 | 3
109 | 3
110 |
111 |
112 | +
113 | +
114 |
115 |
116 |
117 |
118 | calc_5
119 | GroupButton
120 | size=4
121 |
122 |
123 | +/-
124 |
125 |
126 |
127 | 0
128 | 0
129 |
130 |
131 | .
132 | .
133 |
134 |
135 | =
136 | =
137 |
138 |
139 |
140 |
141 | calculator
142 | hideRowNames=1
143 |
144 |
145 |
146 |
--------------------------------------------------------------------------------
/Zoom Button/zoom_dial.js:
--------------------------------------------------------------------------------
1 | /* This macro accomodates the URI format documented here:
2 | https://support.zoom.us/hc/en-us/articles/201854563-Start-a-Meeting-from-an-H-323-SIP-Endpoint
3 | allowing you to key in a password and / or host a Zoom meeting from a Cisco Endpoint */
4 |
5 | const xapi = require('xapi');
6 |
7 | const KEYBOARD_TYPES = {
8 | NUMERIC : 'Numeric'
9 | , SINGLELINE : 'SingleLine'
10 | , PASSWORD : 'Password'
11 | , PIN : 'PIN'
12 | };
13 | const CALL_TYPES = {
14 | AUDIO : 'Audio'
15 | , VIDEO : 'Video'
16 | };
17 |
18 | const DIALPAD_ID = 'zoomdialpad';
19 | const DIALPAD_PASSWORD = 'zoompassword';
20 | const DIALHOSTPIN_ID = 'zoomhostpin';
21 |
22 | const INROOMCONTROL_ZOOMCONTROL_PANELID = 'zoomdial';
23 |
24 | /* Use these to check that its a valid number (depending on what you want to allow users to call */
25 | const REGEXP_URLDIALER = /([a-zA-Z0-9@_\-\.]+)/; /* . Use this one if you want to allow URL dialling */
26 | const REGEXP_NUMERICDIALER = /^([0-9]{9,11})$/; /* Use this one if you want to limit calls to numeric only. In this example, require number to be between 9 and 11 digits. */
27 | //const REGEXP_MEETING_PASSWORD = /^([0-9]{6})$/; /* Use this for meeting passwords that are numeric and 6 charaters in length. Update for your orgs settings */
28 |
29 | const DIALPREFIX_AUDIO_GATEWAY = '0';
30 | const DIALPOSTFIX_ZOOMURL = '@zoomcrc.com';
31 |
32 | var zoomnumbertodial = '';
33 | var zoompassword = '';
34 | var zoomhostpin = '';
35 | var hostpin = '';
36 | var isInWebexCall = 0;
37 |
38 | function sleep(ms) {
39 | return new Promise(resolve => setTimeout(resolve, ms));
40 | }
41 |
42 | xapi.event.on('CallDisconnect', (event) => {
43 | isInWebexCall = 0;
44 | });
45 |
46 |
47 | /* show meeting dial menu - step 1 */
48 | function showDialPad(text){
49 |
50 | xapi.command("UserInterface Message TextInput Display", {
51 | InputType: KEYBOARD_TYPES.NUMERIC
52 | , Placeholder: '10 or 11 digit or full dial string'
53 | , Title: "Zoom Meeting"
54 | , Text: text
55 | , SubmitText: "Next"
56 | , FeedbackId: DIALPAD_ID
57 | }).catch((error) => { console.error(error); });
58 | }
59 |
60 | /* Show password entry box - step 2 */
61 | function showPassword(text){
62 | xapi.command("UserInterface Message TextInput Display", {
63 | InputType: KEYBOARD_TYPES.PIN
64 | , Placeholder: "Meeting Password (numeric)"
65 | , Title: "Meeting Password"
66 | , Text: text
67 | , SubmitText: "Next"
68 | , FeedbackId: DIALPAD_PASSWORD
69 | } ).catch((error) => { console.error(error); });
70 | }
71 |
72 | /* Show password entry box - step 3 */
73 | function showHostpin(text){
74 | xapi.command("UserInterface Message TextInput Display", {
75 | InputType: KEYBOARD_TYPES.PIN
76 | , Placeholder: "Host Pin"
77 | , Title: "Host Pin"
78 | , Text: text
79 | , SubmitText: "Dial"
80 | , FeedbackId: DIALHOSTPIN_ID
81 | } ).catch((error) => { console.error(error); });
82 | }
83 |
84 |
85 |
86 | /* This is the listener for the in-room control panel button that will trigger the dial panel to appear */
87 | xapi.event.on('UserInterface Extensions Panel Clicked', (event) => {
88 | if(event.PanelId === INROOMCONTROL_ZOOMCONTROL_PANELID){
89 | showDialPad("Enter the Zoom 10 or 11 Digit Meeting ID:" );
90 | }
91 | });
92 |
93 |
94 | /* When input is recieved this block of code processes it */
95 | xapi.event.on('UserInterface Message TextInput Response', (event) => {
96 | switch(event.FeedbackId){
97 | case DIALPAD_ID:
98 | console.log("here1");
99 | let regex =REGEXP_URLDIALER; // First check, is it a valid number to dial
100 | let match = regex.exec(event.Text);
101 | if (match !== null) {
102 | let contains_at_regex = /@/;
103 | let contains_at_in_dialstring = contains_at_regex.exec(event.Text);
104 | if (contains_at_in_dialstring !== null) {
105 | zoomnumbertodial = match[1];
106 | }
107 | else{
108 | zoomnumbertodial = match[1];
109 | //zoomnumbertodial = zoomnumbertodial + DIALPOSTFIX_ZOOMURL ; // Here we add the default hostname to the SIP number
110 | }
111 | 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
112 |
113 | showPassword('Enter Numberic Meeting Password (If one is set)');
114 | });
115 |
116 | }
117 | else{
118 | showDialPad("You typed in an invalid number. Please try again. Format is blablabla..." );
119 | }
120 | break;
121 |
122 | case DIALPAD_PASSWORD:
123 | /* todo: verify password format / length */
124 | zoompassword = event.Text;
125 | showHostpin("If you are the host enter your host pin");
126 | break;
127 |
128 | case DIALHOSTPIN_ID:
129 | zoomhostpin = event.Text;
130 |
131 | /* assemble the URI to call */
132 | if (zoompassword !== ''){
133 | zoomnumbertodial = zoomnumbertodial + '.' + zoompassword;
134 | }
135 | if (zoomhostpin !== ''){
136 | zoomnumbertodial = zoomnumbertodial + '..' + zoomhostpin;
137 | }
138 | zoomnumbertodial = zoomnumbertodial + DIALPOSTFIX_ZOOMURL;
139 |
140 | console.log('Dialing: ' + zoomnumbertodial);
141 | xapi.command("dial", {Number: zoomnumbertodial}).catch((error) => { console.error(error); });
142 | break;
143 | }
144 | });
145 |
--------------------------------------------------------------------------------