Categories
Weekend Builds

Weekend Build – Cisco Jabber Light

At work we use Cisco Jabber for Instant Messaging and Presence. This works great if someone wants to start a chat with you or send an email. But what happens if someone walks over and wants to talk IRL?

If only Peter had a Jabber Status Light

I thought it would be a cool little project to bring the Jabber status light into the real world.

This is what I came up with:

Now of course systems that do exactly this exist in the corporate world. However, these products also come with a corporate price tag. Some implementations I have seen require a whole server for middleware, some require a physical device to “beam” your presence around the office. Also, where is the fun in buying something that you could make yourself?

A cursary search of the Cisco forums suggested an API existed to get Jabber Status (or “Presence”) for a given user. It looks like it was made available as a SOAP API. Great.

I first wanted to create the actual light and then worry about the integration later. I wanted the light to mimick the Jabber status “orb” as much as possible. I found a cheap mood lamp online and bought this as a base.

This light really got me in the mood

The mood lamp was actully very dim in daylight, so sadly the LEDs needed to go. I first tried a matrix of “superbright” LEDs I had in the cupboard, but they didn’t perform much better than the ones they were replacing, and it soon got very cramped.

I sourced a much more powerful 3W RGB LED on eBay. It was a perfect fit, but still not quite bright enough.

Back to eBay, and I found a 10W unit that would surely do the trick. This LED would require more than 5v to drive it which would pose a problem.

It was however so bright at full power that you couldn’t look directly at it.

LED brightness is usually measured in millicandela (mcd). Candelas are an SI unit of light intensity, similar to Lumens but taking into account the direction of the emitted light.

LEDBrightness (mcd)
Regular LED500-800
“Superbright” LED14000
3W RGB LED60000
10W RGB LED300000

The 10W unit was certainly man enough for the task.

After a certain point it’s hard to photograph the difference of very bright lights

Using a step-up converter to turn my 5v source into 12v I was able to drive the 10W module perfectly.

To control the LED status I selected a Particle Photon board. This is an internet connected microcontroller designed for rapidly prototyping IoT devices. Perfect.

Tiny board with integrated cloud connectivity

I used some protoboard, and cobbled together the Photon and boost convertor in a package that would then fit inside the light assembly. Two digitial pins on the Photon each connect to an NPN transistor which in turn switches the 12v to the LEDs.

Using the excellent online IDE for the Photon, I put together a simple program that exposes a function “ledToggle” that can be called remotely to change the LED colour. This powers the Red and Green pins on the LED as needed (blue not required).

Now I needed somewhere to house the circuitry. I modeled a cylindrical enclosure using Fusion 360. The cylinder was designed to fit atop the monitor arm on my desk. This was finished off with a small adaptor to hold the LED and attach the orb.

The parts were printed with black PLA plastic on my Prusa i3 MK2. The components fit cosily inside. I used a USB cable to provide power to the device.

The assembled unit looked great, but all it did was flash colours.

Next I needed to write the integration with Jabber. I decided early-on that the logic for doing this would happen on my computer, and not on the Photon board.

My first attempt used the SOAP API and relied on polling. It was slow and clunky and it soon became clear that the Cisco Unified Presence server would rate-limit you after a while if you made too many requests.

I then found the CAXL library. This is a JavaScript library with all the tools you need to build essentially a web-based version of Jabber.

With this I was able to subscribe to presence change events, and as a bonus I could do this for multiple users (should I make anymore of these).

I stripped back the demo code to the bare minimum. I added a bit of logic to handle working hours and then added the integration with the Photon via their REST API.

<html>
    <head>
        <script type='text/javascript' src='caxl/jabberwerx.js'></script>
        <script type='text/javascript' src='caxl/jabberwerx.cisco.js'></script>
        <script type="text/javascript">
		document.addEventListener("DOMContentLoaded", function(event) { 
	
            var demo_config = {
                httpBindingURL: "https://cup-server.corp.domain.com:5280/httpbinding",
                domain: "corp.domain.com",
                username: "presencereader",
                password: "password"
            };
			
			var jids = [];
			jids.push('user_to_monitor' + "@" + demo_config.domain);
			
			var $ = jabberwerx.$;
			var cur_s = new Object();
			var prev_s = new Object();
			var lp_timeout;
			var t_timeout;
            var client = new jabberwerx.Client();
            var qcController = new jabberwerx.cisco.QuickContactController(client);
            new jabberwerx.RosterController(client);


            function subscribe() {
                try {
                    qcController.subscribeAll(jids);
                } catch (ex) {
                    console.log("Exception thrown by subscribeAll: " + ex.message);
                }
            }
		
		   client.entitySet.event("entityCreated").bind(function(evt) {
			   var qcontact = evt.data;
			   _roster._updateRosterItem(qcontact);
			   qcontact.event("primaryPresenceChanged").bind(function(evt) {
				   _roster._updateRosterItem(qcontact);
				});
		   });

			client.entitySet.event("entityDestroyed").bind(function(evt) {
				_roster._removeRosterItem(evt.data);
			});

            var _roster = {
                
                _updateRosterItem: function(contact) {

					var name = "";
					name = contact.getDisplayName().replace("@"+demo_config.domain, '');
					if (name){
						var presence = contact.getPrimaryPresence();
						var show = "unknown";
						var info = "";
						if (presence) {
							show = presence.getType() ||
									presence.getShow() ||
									"available";
						}

						cur_s[name] = show;
						window.clearTimeout(lp_timeout);
						lp_timeout = window.setTimeout(listpres, 500);
					}
                }
            };
			
			var connectArgs = {
         
            httpBindingURL: demo_config.httpBindingURL,
            successCallback: function() {
				console.log("Connected");
				subscribe();
            },
           
            errorCallback: function(error) {
                console.log(jabberwerx.errorReporter.getMessage(error));
            }
        };

        try {
            client.connect(demo_config.username + "@" + demo_config.domain, demo_config.password, connectArgs);
        } catch (ex) {
            console.log(ex.message);
        }
		
		function tick(){
					var name;
					var led;
					var str;
					var dn = new Date();
						
					if ((dn.getHours() < 7) || (dn.getHours() >= 19)){
						jids.forEach(function(jid) {
			
							name = jid.replace("@"+demo_config.domain, '');
							cur_s[name] = "dark";
							if (cur_s[name] != prev_s[name]){
								led = "off";
								 
								console.log("User: "+name+" Status: "+cur_s[name])
								$.post('https://api.particle.io/v1/devices/DEVICEID/led/?access_token=ACCESSTOKEN', { arg: led },
									function(returnedData){
										 console.log(returnedData);
										if (returnedData.return_value == 1){
											console.log("ledToggle OK");
										 }else{
											 console.log("ledToggle Failed");
										 }
								}).fail(function(){
									  console.log("Particle API Failed");
								});
							
								prev_s[name] = cur_s[name];
							
							}
						});
					}
					
					window.clearTimeout(t_timeout);
					t_timeout = window.setTimeout(tick, 10000);

		}
		
		function listpres(){
			window.clearTimeout(lp_timeout);
			var name;
			var led;
			var str;


			jids.forEach(function(jid) {
			
				name = jid.replace("@"+demo_config.domain, '');
				
				if (cur_s[name] != prev_s[name]){

					if (cur_s[name] == "available"){
						led = "green";
					}else if(cur_s[name] == "away"){
						led = "yellow";
					}else if(cur_s[name] == "dnd"){
						led = "red"
					}else{
						led = "off"				
					}

					console.log("User: "+name+" Status: "+cur_s[name])
					$.post('https://api.particle.io/v1/devices/DEVICEID/led/?access_token=ACCESSTOKEN', { arg: led },
						function(returnedData){
							 console.log(returnedData);
							if (returnedData.return_value == 1){
								console.log("ledToggle OK");
							 }else{
								 console.log("ledToggle Failed");
							 }
					}).fail(function(){
						  console.log("Particle API Failed");
					});
				
					prev_s[name] = cur_s[name];
				}
			
			});			
		}

		t_timeout = window.setTimeout(tick, 10000);
		
		});
		
        </script>
    </head>
    <body>
    </body>
</html>

This works well, by opening this page in a browser located inside the corporate network it is able to connect to the Presence server and status changes are pushed in real-time.

Video showing the application in action

If this were to be scaled out further it could be turned into a node.js service. The CAXL library from Cisco would require a significant piece of re-work though.

All in all it works great. Now if only I could engineer a way of making people actually honour “Do Not Disturb”…