├── 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 | --------------------------------------------------------------------------------