Hey everybody! Thanks for checking out another episode of "Forging Titanium". Today is the 3rd and final part of the Twisti app series. In part 1 we created a native Android module that leveraged accelerometer, magnetic field, and 3D calculations to determine the physical orientation of a mobile device. In part 2 we used that data to render an animated, 3D representation of the device in a WebView canvas using Three.js. You'll definitely want to check out these past episodes if you haven't already, as they lay the foundation for this final part.
Today, we're going to create a sensor proxy, sending its physical orientation data to multiple clients, in realtime, via Titanium TCP sockets. Since it might not be totally clear what I mean by "sensor proxy", let's dive right into a demo of Twisti's current and soon-to-be-added functionality...
LIVE DEMO
Now that we've seen what Twisti can do, let's fire up Titanium Studio and take a look at the code... If you look at the project structure, you'll notice we've expanded beyond the app.js file. We now have a module for both the sensor, which is the device that is the source of the data, and the client, which are the devices that display the 3D representation of that data.
In the app.js file we start with a little configuration, setting the IP address and port for the sensor. In a more complex demo you could have these values input by the user, but let's keep it basic so the focus remains on the client/sensor interaction coming up. And just in case it isn't obvious, you'll need to make sure all the devices running Twisti are on the same network. At the very least, the sensor needs to have an IP address accessible by all your clients, just like in any other client/server system.
After that little bit of setup, we give the user an option as to whether they want their device to be a sensor or a client. This choice is only available on Android as the sensor relies on the native Android module we created in part 1. At this point, control of our app is handed off to either the sensor or client javascript modules.
Let's start with the sensor. Right off the bat you'll see the clients array defined. This is the array that will hold a reference to all the client sockets that will eventually be connected to our sensor. In just a bit we'll see exactly how those sockets will be used to send data to the clients.
Let's go down to the createsSensorWindow()
function, as it was the first one accessed in the app.js. Here we create the UI and event handling for the main sensor window. This is done very similarly to the app.js from part 1 of this series. Let's skip down to the new stuff found in the twisti update
event listener... Everything looks the same as in past episodes, except we now call updateClients() on every update. If we go back up to the updateClients()
... we see that this is the function responsible for sending our sensor's physical orientation data to the connected clients. We create the orientation buffer, which will look at in a moment... iterate through the current list of client sockets... write the orientation data to the socket... and close any sockets that are no longer receiving updates...
You'll probably note how simplistic this execution cycle is. Well, when it comes to realtime socket communication among multiple clients it has to be. The simpler the update, the faster they get to your clients. And when you are sending updates multiple times a second to many clients, that speed is critical. To that end, let's see how we sent the bare minimum of data back to the clients with the createOrientationBuffer()
function...
We first create a temporary array of our physical orientation values, then create a Titanium buffer large enough to hold those encoded values. For each orientation value, we encode it using Titanium.Codec.encodeNumber()
. To this function we pass a series of properties that define what we want encoded, and how. The source
property is the number value to encode... dest
is the Titanium buffer to which the encoded value will be copied... position
is the position within that buffer that the encoded value will be placed... type
defines the type of data that we are encoding... and finally byteOrder
allows us to specify a specific byte order for encoding, not relying on the byte order to be the same on all sensor and client devices. After each call of encodeNumber()
, the next position is returned and used to position the next number.
OK, so now that we have our sensor window setup and ready to send data to clients, let's fire up a listening socket. Here in the listen()
function we create a basic server socket using just our sensor port and event handlers for accepted connections and errors. With the socket created, We simply need to call listen()
on the socket, then accept our first client connection. You'll notice in the accepted
handler that after our client socket is saved, accept()
is called again. Incoming connections are handled synchronously, and therefore you need to specify after each one you receive that you will like to accept another.
Now that the sensor is listening and ready to roll, let's take a look at client.js file. We start with the exposed function createClientWindow()... which is nearly identical to the webview code from part 2 of this series. The important change is the call to the connect()
function, which, given an IP and port, will connect to a listening socket. Looking at the connect()
function we see its extremely basic... We use the IP and port, along with event handlers for success and error, to create a client socket. Once the socket is created, we simply call its connect()
function and we are good to go.
When a connection is successfully made, we call the handleConnection()
function... In here we perform an asynchronous read of any data made available to us by the server. In this case, we're waiting for the sensor to send us physical orientation data so we can represent it as a 3D model in our webview. If you look at how the read buffer and resulting object are decoded, you'll notice it's just about identical to how the write buffer and object were encoded on the sensor side. We use all the same sizes and properties to define the object that will receive the data.
When the data is received and decoded, we make it available to our webview via an application level event, seen here as app:updateRotation
... This is exactly how we did it in part 2, but just as a refresher, here's the code that handles that event in the webview...
And with that, we have everything we need to send physical orientation data from one sensor device to multiple client devices on varying platforms. And the most important point is that we did it in realtime with a high volume of messages, thanks to Titanium's socket API...
In this episode of Forging Titanium we finished up Twisti, an app that demonstrates powerful Titanium functionality, including native modules, 3D rendering in webviews, and realtime socket communication. The true beauty of showing off these advanced features, though, is that they can be seamlessly integrated with the simplicity of the rest of the cross-platform Titanium API. All of these features, even the platform specific ones, are delivered from a single codebase.
Thanks for watching this episode of Forging Titanium. See ya next time.