![]() |
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
INTRODUCTION |
The Teleo Application SDK is the way programmers can use Teleo modules in their programs. The SDK has been designed to provide the absolutely easiest way to interact with Teleo modules and at the same time permit advanced programmers as much flexibility and power as possible. It is currently provided in completely generic C to give access to as many platforms as possible, and plans exist to provide it in Java and C++ in the future.
As closely as possible, the Teleo Application SDK provides proxies that mirror the Teleo entities that the hardware provides. (See the Teleo System user guide document for the details.) Therefore users of the SDK should first be familiar with the basic architecture of the Teleo System.
Modules, Devices and Properties
To summarize, the module is the central addressable unit of the system. To allow unambiguous communication, each module has a unique address. The application and modules communicate by exchanging short messages.
A single module may be capable of many different functions. The abstraction used to represent this is the device. Modules implement as many devices as they need - one for each function. MakingThings modules have as few as one device or as many as 14 of different kinds (e.g. the MultiIO has 4 Analog In, 4 Digital In, 4 Digital Out and 2 Pwm devices). In order to communicate with devices, each is given an index, so that the address of the module and the device index uniques specifies the device.
Each device has an associated list of properties that govern their operation. These properties may be read from to gain information about the device or written to control it. At the simplest level, the SDK is designed to make setting and getting device properties easy.
In order to satisfy the twin SDK goals of easy of use and complete control, we offer two ways to use the SDK. We call these Teleo Easy and the Teleo Advanced.
Teleo Easy Teleo Advanced Audience Anyone wanting quick simple access to Teleo Advanced programmers who need access and minute control over every aspect of the Teleo system Primary Focus Primarily concerned with the manipulation of properties on devices Provides access at the communication, message, module, device and property level Advantages Very easy
Few lines of code
Lower layers hidden
Platform implementation choices made.
Complete control
Access to all layers of the software.
Ability to change implementation assumptions.
Higher performance by avoiding upper layers.
Can use all the abstractions from the easy method.
Smaller code footprint.
Easy to port to new systems.
Required to add a new module and its devices to the Teleo Easy SDK.Disadvantages May perform more slowly.
Not able to easily access all features of the system.
Platform implementation choices made.
Smaller number of supported platforms.More to understand.
Greater platform knowledge required.
All the code has been documented by Doxygen. Doxygen is an excellent open-source documentation tool that takes C (and other language) source code and produces documentation automatically. Where possible throughout this introduction, links have been made to this documentation. (pronunciation? "... Docs-ee-gen ... with a long 'e' " - from their site).
The Application SDK is designed for people who want to write programs to control networks of Teleo Modules. For those wanting to program the microcontrollers on the modules themselves, there is another SDK - the Teleo Module SDK.
|
TELEO EASY |
Teleo Easy focuses on devices and their properties. In your program, there are a small number of setup steps you need to take to get the Teleo system running. After that the SDK permits the creation of software proxies, objects that shadow the actual module devices. These device proxies provide an interface to their hardware devices, from which you can get and set the property values, set up callbacks for when property values change and so on.
Proxies are bound to the corresponding real devices. Let's look at one example device proxy in a little detail - below are some of the function calls for the TeleoMioDin proxy object which is the software proxy for the Digital In device on the MultiIO modules:
The first function is the proxy_Create( ) function. Call it to create a new device proxy. It takes a Teleo Device Manager (which is returned from the first Teleo Easy initialization function). It also takes an address that lets the system know which MultiIO you want to connect to. It can be passed a NULL, which implies that you will get connected to the first MultiIO the system finds, which is handy if you know there's only one on the network. (See the Module Addressing section below for more details) The next parameter is the device index - it describes which of the Digital In devices on the MultiIO that you want to connect to. The bound parameter permits the Create function to inform the caller that the module was already bound at create time. We'll discuss this more in the Binding Issues section. Finally there's the return parameter into which the pointer to the proxy will be placed if all went well. When this function returns successfully, you'll have a functional proxy object for the Digital In you are interested in. All of the functions in the SDK return a TeleoError - which is TELEO_OK if all was well or something else if an error of some kind occured.
TeleoError TMioDin_Create ( TeleoDeviceManager * tdm, cchar * address, uint8 dinDeviceIndex, bool * bound, TMioDin ** tmdin ) The next function is the simplest way to read the current state of the input - you call this function, passing in the pointer to the TMioDin object, and a variable into which to return the value and that's it. When the function returns, you'll be able to query the value variable to see what the input state was.
TeleoError TMioDin_valueGet ( TMioDin * tmdin, bool * value ) Using the function above you can read the Digital In value any time you like. Sometimes it's more convenient (and efficient) to arrange for the system to call you back to tell you that the value has changed - this obviates the need for repetitive checking. The function below lets you nominate one of your functions that will be called whenever the Digital Input value changes. You pass in the object pointer and the pointer to your function and your function will be called with the new value whenever it changes. Note that you're passed back the object pointer in this function so you know which object called you.
TeleoError TMioDin_valueCallbackSet ( TMioDin * tmdin, TeleoError(* valueUpdate)(TMioDin *tmdin, bool value) ) The Digital In device permits you to set how frequently the input pin is sampled. You are permitted to change and read back the sampling period here too.
TeleoError TMioDin_sampleRateSet ( TMioDin * tmdin, int16 rate )
TeleoError TMioDin_sampleRateGet ( TMioDin * tmdin, int16 * rate ) You may also receive updates whenever this value changes - in practice this is mostly used once a module is bound. See the discussion in th Binding Issues section on this subject.
TeleoError TMioDin_sampleRateCallbackSet ( TMioDin * tmdin, TeleoError(* sampleRateUpdate)(TMioDin *tmdin, int16 sampleRate) ) Here are the steps to creating a Teleo Easy program:
I - Include the Teleo headers
You need to include the TeleoEasy header file for the small handful of TeleoEasy routines
#include "TeleoEasy.h"
And you also need to include the header file for each of the devices you intend to use (see the list of device software objects in the Teleo Device Proxy List section below)
#include "TeleoMioDin.h"
II - Set up the teleo device environment
Within your initialization code, you need to call TeleoEasy_Init( ). This takes an optional device name and a device type, creates all the layers required to get the SDK functioning and finally creates a TeleoDeviceManager (passed back in the tdm parameter) which you need for the rest of the program. See the Teleo Interface section for more details about the interface parameters.
TeleoError TeleoEasy_Init ( cchar * interfaceName, TI_InterfaceType interfaceTypeId, TeleoDeviceManager ** tdm ) The interface type parameter allows you to specify what kind of interface you want to open - TI_USB specifies the first USB interface, exactly which one is the correct one will depend on your operating system.
III - Create the software device proxies that correspond to the hardware devices you're interested in
To create the proxy for a device, you need to call the corerct proxy_Create( ) function. (See the Teleo Device Proxy List section below for the whole list.)
TeleoError teleoError = TeleoMioDin_Create( tdm, NULL, 0, NULL &button );
The tdm parameter specifies the Teleo Device Manager that was created by the TeleoEasy_Init( ) call in Step II. The first NULL says that you don't care what the address of the MultiIO is that you want to talk to. See the Module Addressing section below for more details. The 0 in the TeleoMioDin_Create( ) call specifies that out of the 4 Digital In devices on the MultiIO, you want to be connected to device 0. The next NULL says that you don't need to know if the module is already connected to the hardware (bound) The bound concept is explained further in the Binding Issues section below. Finally there is the output parameter, button which will hold the new TMioDin object if the create function succeeded
If all is well (if the function returned TELEO_OK) the object will have been created and is ready to start communicating with the hardware.
You may create as many objects as you need here.
IV - Set Up Update Callbacks
It is very convenient to have a way to know when a property has changed. Much easier (and more efficient) than polling continuously or even polling and sleeping.
If you define a function as follows:
TeleoError valueUpdate ( TMioDin* tmdin, bool value ){ ...your code here }You can arrange for it to be called back by calling the appropriate proxy_propertyCallbackSet( ) function.
TMioDin_valueCallbackSet( button, valueUpdate );
Thereafter, whenever the Device sends a message saying that the value has changed your function will be called.
IV - Start
Once you have all the objects you need you need to request that the Teleo Easy software start the program threads that deal with input and timing issues.
TeleoEasy_Start( );
Now the system is running. You may perform the functions your program needs to perform - getting and setting property values on the device proxies. If you elect to program this way, you do need to call lock and unlock functions before and after you do so - see the Coding Style and Threading Issues sections below).
TeleoEasy_Lock( );
TeleoMioDin_valueGet( )
TeleoEasy_Unlock( );Alternatively, you can rely on callbacks to trigger your code execution by putting most of your program logic into your property callback functions. If you do this, once your main thread is done initializing, you can put it to sleep by calling TeleoEasy_Continue( ). After this your programs callbacks will be called until something causes the program to exit.
V - Compilation & Linking
You can use the makefiles provided with the samples as a template for developing with the Teleo Easy Application SDK.
The first example we'll show is just about as simple as possible - a proxy is set up for a Digital Input connected to a button.
button.c running
When the button is pressed, a message is printed out.
/* Copyright 2003 MakingThings LLC. */ #include <stdio.h> #include "TeleoMioDin.h" #include "TeleoEasy.h" // local function for callbacks TeleoError valueUpdate ( TMioDin* tmdin, bool value ); // Device Manager TeleoDeviceManager* teleoDeviceManager; // Devices TMioDin* button; int main( int argc, char* argv ) { TeleoError teleoError; if ( TeleoEasy_Init( NULL, TI_USB, &teleoDeviceManager ) != TELEO_OK ) exit( 1 ); // create the device teleoError = TMioDin_Create( teleoDeviceManager, NULL, // no address specified - use any 0, // use MioDin 0 NULL, // no need to know bound status &button ); // the device if ( teleoError ) exit( 1 ); // set up callback: valueUpdate will be called whenever there's a new // value from the button. TMioDin_valueCallbackSet( button, valueUpdate ); // Start the system up TeleoEasy_Start(); printf( "Simple Button Running\n" ); // Wait until all threads die TeleoEasy_Continue(); } TeleoError valueUpdate ( TMioDin* tmdin, bool value ) { // if the button is being pressed if ( value ) printf( "Hello, button\n" ); return TELEO_OK; }This is a very simple program - note that it relies on a callback to do its work. Once the program has finished setting up (creating the button proxy and setting the callback) it calls TeleoEasy_Continue( )which just waits. When someone presses (or releases) the button, the valueUpdate( ) function is called with the new value.
The proxy_Create( ) functions all take an address parameter that specifies the address of the module on which the system will find the device you wish to interact with. If the address is not specified at all, (i.e. NULL passed in), the system will match to any module which has the correct type. For example if you're creating a TeleoMioAin device proxy, the module that will implement this device is a Teleo MultiIO. So if you passed NULL for the address parameter of the create function, the system will connect you to the first Teleo MultiIO module that it finds.
This is very convenient if you know that there's only one multiIO on the network - you never have to worry what its address is, but it is inadequate when there is more than one multiIO since there would be no way to reliably match the one you actually want. In this case you would specify the exact module address so that the device proxies you create bind to the correct module.
Addresses you can pass into the proxy create functions are strings, and can be in any of the following forms:
NULL - a null address matches any module on the system with the specified type.
[teleo network address] - (e.g. "3" or "10") the teleo network address of the module - it can be any number from 2 to 63. This can be on any channel.
[channel] - (e.g. "usb", "serial") matches any module on the specified channel.
[channel]/[teleo network address] - (e.g. "usb/3", "serial/10") the channel portion specifies which channel is being used. The teleo network address portion is the same as the previous form: specifying the teleo network address of the module.
It can sometime take some time (up to several seconds in the worst case) for the SDK to find all the modules that are connected to the Teleo Network.
If you did not specify the module address at create time, the system doesn't know it so sending messages out onto the network is pointless. When a matching module is identified as being present on the network and its address is known, only then do we say that the device proxy is bound.
You can check to see if a proxy is bound to its destination module and device by checking the state directly with the proxy_boundGet( ) function...
TeleoError TMioDin_boundGet ( TMioDin * tmdin, bool * bound ) Or you can elect to be called back when the module becomes bound.
TeleoError TMioDin_boundCallbackSet ( TMioDin * tmdin, TeleoError(* boundUpdate)(TMioDin *tmdin, bool bound) ) If you fully specified the module address when you created the Device, you can communicate with it immediately. The module is bound immediately. This gives rise to a new problem: when to issue the bound callback described above? It can't be called from inside the proxy create function since this might give rise to a race condition - most obviously, within the proxy create function the proxy_boundUpdate( ) function won't even have been assigned. The solution to this problem is to pass a variable back to the caller of the proxy create function to inform it of the bound state. If the device was bound at create time, the flag is set and it is the caller's responsibility to call the bound function when the proxy is fully initialized. The pseudo-code is as follows:
proxy_Create( ... &xxxBound, &xxx ); // do the rest of the initialization of xxx - and of ... // now, if the module was bound during the create sequence, call the bound function if ( xxxBound ) xxxBoundUpdate( xxx, true );The device proxy tries to maintain an accurate reflection of the state of its target device and an efficent conduit of information to and from it, but at initialization time, there are some things that the proxy can't know about the target device. Some devices have values that control their operation - like the SamplePeriod property on the TeleoMioDin device that we saw above and we'd like to be able to read what those values are from the proxy. Unfortunately once the proxy is bound to the real device we still don't know these values. Once the proxy is bound, we can send off messages to the device requesting that the properties like SamplePeriod be sent to the proxy, but this takes time. So if we care about the current values we really need to wait until the data arrives back - which can only really be done by implementing an update function and assigning it to the values we are concerned with.
This is why some properties (like SamplePeriod on the TeleMioDin device and also many other properties on other devices) have callback notification functions. Once the value returns, your function is called back and processing can continue.
One of the things that the Teleo Easy interface tries to do is hide operating system details. A program built with Teleo Easy checks interfaces for new characters, runs timers, and runs your code all at the same time. The secret to this is that (where possible) Teleo Easy creates program threads for each of the above activies. The ideal situation is that you can do whatever you want in your code without ever having to pause to check for incoming characters from the Teleo Network or send timer events to the layers beneath. Similarly, you should be able to call all the functions you like without concern that they'll be interrupted half way through by another thread.
Almost all thread safety issues are dealt with by the TeleoEasy framework. If your code is being activated by an update callback it is guaranteed to be not interrupted by another thread. The only case we can't help you with is the main program thread. If you call TeleoEasy routines from within your main program thread you need to call TeleoEasy_Lock( ) first and TeleoEasy_Unlock( ) afterwards. This is a small sacrifice for having to never worry about checking files or timers.
TeleoEasy_Lock( ); proxy_valueGet( ... &value ); TeleoEasy_Unlock( );Users of the SDK can chose between two different kinds of coding style. Code can be written that predominantly relies on polling - periodically checking values within some application operation loop to assess what should be done next. The pseudo-code looks something like this:
... TeleoEasy_Start( ); while ( some condition ) { TeleoEasy_Lock( );
proxy_propertyGet( ... &value ); TeleoEasy_Unlock( );
if ( check( value ) )
do something }Remember, when proxy functions are called from within the main program thread, the Teleo Easy system has to be locked before and unlocked after every invocation.
The other coding alternative is to use event-based programming. In this case callbacks are set up for the values that are important to the program. The bulk of the programming logic is put into these callbacks and then the program calls TeleoEasy_Continue( ) to wait for events to happen. The following pseudo-code illustrates this kind of programming.
int main( ) { ... TMioDin_valueCallbackSet( button, buttonStateChange ); ... TeleoEasy_Continue( ); } ... TeleoError buttonStateChange( TMioDin *device, bool state ) { if ( state ) do something else do something else returnTELEO_OK; }This style has two distinct advantages. First the program isn't running at all times - the callback is called only when a value changes - CPU time is conserved. Second, any proxy function calls invoked from within the callback do not have to be protected with the TeleoEasy_Lock( ) and TeleoEasy_Unlock( ) functions.
Platform Independent Interface
The platforms that share a common (or near common) C environment unfortunately do not often share a common means for opening usb, serial or network devices. It is our hope to hide all these details so that you don't have to worry about how to open a USB port, for example, on your platform. You should just have to be able to start the Teleo Easy software passing in simple values like TI_USB to tell it that you want to open a USB port. This often works very smoothly.On some platforms, however, this is not so smooth. Make sure you check the read me note for your operating system to make sure the examples are all correct.
There are two other simple examples to illustrate different coding techniques: Light.c shows reading and writing property values with more than one proxy. Chart.c shows programming using polling.
Next
Teleo Easy is the easiest way to implement a Teleo program, but how does it work? The way all ease-of-use issues are handled in computer systems - by hiding the details. Teleo Easy (one for each platform) wraps all the setup and program running issues up into a single place. If you need to understand how it works, its inner workings are reviewed in detail in the Teleo Advanced section.
|
Teleo Advanced |
SDK LayersThe SDK is layered so that programmers can access the network at whatever level of abstraction they choose. When using the Teleo Easy layer, all of these layers are hidden, but for more advanced use it is necessary to understand what the layers are and how they are used. In the diagram below, you can see how a Teleo application is structured inside.
Teleo Application Layers These layers perform the following functions:
Application - the application contains the logic to make your system work the way you want it to. You write the application from scratch - or modify one of the samples - to get what you want.
Teleo Easy - Teleo Easy can be thought of as a set of helper functions that do a lot of the fiddly setting up of the system. Part of this process includes making choices about implementation details, like whether and how to do threading, how to communicate with the Teleo Network, etc. This is the good news and the bad news about Teleo Easy. The good news is that by using it, you don't have to worry about those details. If the choices are OK for you, you will have a very easy time using Teleo. The bad news is that If the choices were not OK for you, you will have to go deeper into the API to learn exactly how it works.
Teleo Device Proxies - The Device Proxies are used by programmers in the Teleo Easy style, but are actually general API components that can be used by any application. They leverage knowledge about the devices to provide an interpretation of their properties. In addition they provide protection against module resets by resending important values to modules if they ever are forced to reboot. This means that operation is not interrupted.
Teleo Device Manager - The Device Manager handles raw devices - it caches property values and handles binding devices to modules in the case where the device's address is not fully specified.
Teleo Module Manager - The Module Manager maintains a list of modules that are known to the network. It handles the transformation of data send requests into actual messages that can be sent. It also interprets a wide variety of incoming messages.
Teleo Channel - The Channel is the layer that takes message structures and converts them into byte streams and vice versa. It handles the addition and removal of start and stop bytes, and does all CRC calculations.
You can see a little of how these layers all work together by tracing the paths of an outgoing and an incoming message all the way. The first activity results from the application setting a property on a device proxy, say by calling TMioDout_valueSet( ). The Proxy works out which property (by index) is being refered to and passes the message on to the Device Manager. The device manager looks-up the module that the device is connected to and calls the Module Manager with the request to send data to the located module, device, and port. The Module Manager sends the message, now completely filled out on to the Channel. The channel adds the start, CRC and end bytes to the message and sends it on to the Interface. The Interface finally writes the message out to the correct place (USB, Serial, Network, etc.) In actual fact the Application intervenes in a minor way between the Teleo Interface and the Channel. We'll see how in the Teleo Interface section.
![]() |
| Layer Interaction |
The messages travelling the other way are initiated from the hardware. Characters arriving from the hardware are reported by the Teleo Interface and sent on (via the Application code - see the Teleo Interface section below) to the Channel. The Channel collects these characters into messages. When a full message has arrived and the checksum is OK, it is passed up to the Module Manager. The module manager interprets the message. Depending on the message it will do different things - if it's a data message on a module, the message is sent to the Device manager. The Device Manager stores the new data value and sends the message on to the right device proxy. If the application requested updates, the final step the software takes is to call back the application provided function to let the application know the data has changed.
The level at which you chose to interface with the Teleo Application SDK depends on your application. If you're running on a very low-end processor, you might chose to do your own interpretation of only some messages - seeking to minimize the processor load - and thus elect to only use the Teleo Channel and Teleo Interface. On the other extreme, you might want to know as little as possible about the internals of the toolkit and therefore decide to use the Teleo Device Proxies or even the Teleo Easy Application SDK.
We decided to create the SDK in as neutral way as possible. It would have been foolish, for example, to introduce a requirement that the underlying file system was Unix-y, for example, thereby excluding all the other platforms. Similarly, although a multithreaded implementation would be optimal in some situations, requiring it would preclude the use of the SDK in non-multi-threaded environments. The price of all this agnosticism is that users of the Teleo Advanced Application SDK have to make some of the low-level decisions that in an ideal world would be hidden from them. These decisions include how to do the low level IO to open, read, write and close the device connected to the module, and how to arrange for timer events. Both of these are very different depending on the platform chosen. We have example code to make this process as easy as possible.
External References
The first parameter to most of the calls in the SDK is a reference to the object. For example, when the Channel is opened it returns a TeleoChannel*, this pointer should be passed in as the first parameter for all calls to the Channel:
TeleoChannel* teleoChannel; ... // Create the channel TC_Create( &teleoChannel ); ... // send a character to the Channel TC_input( teleoChannel, c );This is very convenient for both objects - your application can presumably easily locate the TeleoChannel pointer, and the Teleo Channel function can use it immediately (since the object was the Teleo Channel's in the first place).
For callbacks (when the subsystem - TeleoChannel in this case - needs to call your application back), the first parameter will be the subsystem pointer TeleoChannel* and the second will be a void* called an external reference.
TeleoError receive( TeleoChannel* tc, void* tcmExt, TeleoMessage *message ) { ... }In the simple cases both the TeleoChannel* and the external Reference can be ignored. In these applications, there is only one source of each kind of callback so there's no need to disabiguate them. In more complex systems, where there might be more than one source of messages (i.e. if there is more than one interface being used) then the application becomes interested in distinguishing between them. Note that in other parts of the SDK this problem of identifying the source of a message or function call is important too - for example in the device portion of the SDK, you will certainly need to know which device is originating a propertyUpdate( ) call.
We could have implemented this in at least two other ways: The first would involve having n different functions - one for each instance of the lower level object. You would pass the appropriate function to each object at initialization time so a unique function is called for each event. But what if you had hundreds of objects?
The next method involves doing a lookup on the prime object. You would take the first pointer and then look it up in a hash table or other data structure. This would work, but it is a lot of searching to do for every callback in the case above where you might have hundreds of objects.
So we settled on the method of external references. To use external references, once you've created the object / subsystem, etc. you call an externalSet( ) function on the created object passing the item itself and also a void* object. Whenever the new subsystem or object needs to call you back with a event, in addition to passing itself, it also passed you the void *object you gave it. When you get the void* object back, you simply cast it into the right kind of object and begin working with it immediately.
Subsystem_externalSet( subsystem, object );The first parameter is the reference to the sub system and the second parameter is the external reference that you'd like the object to refer to in it's callbacks. This new object is usually an object you've created in your application code. Now when you get the callback, you can just cast the object into the one you need.
... Subsystem_externalSet( subsystem[ i ], object[ i ] ); ...TeleoError callback( Subsystem* subsystem, void* sExt, ... ) { // Cast the void* into an Object Object* o = (Object*)sExt; // now use your object without look-ups etc. ... }This technique is used widely throughout the SDK.
Callback refering to an external object
Remember, regardless of whether you assign external references for the callbacks or not, you will always need to pass in the original object to calls you make into the various SDK objects.
Built-in Type Aliases
Since we run on a variety of platforms, we need to be careful of the sizes and characteristics of the various data types. For this reason and also to avoid unsightly long instrinsic declarations like unsigned char we define the following in TeleoTypes.h
typedef signed char int8; typedef unsigned char uint8; typedef unsigned char uchar; typedef signed short int16; typedef unsigned short uint16; typedef signed int int32; typedef unsigned int uint32; typedef uint8 bool; typedef const char cchar;The Teleo Channel handles the task of formatting and interpretting messages. It gets characters from the application then calls back with completed messages. In the other direction, messages sent to it will be packetized them and sent out.
If you need to communicate with the network at a low level, this and the Teleo Interface are all you'll need. From the outside, you can send messages via the TC_send( ) function and it will invoke its (*receive)( ) callback when an incoming message is received.
The Channel exchanges messages with the TeleoNetwork that are structured as follows:
Serial Packet The STX character signals the start of the packet. The addr byte is the network address (0-63) of the receiving module. The count byte specified how many data items there are in the packet and is followed by exactly that many data bytes. The crc16 provides a check sum to enable the receiver to detect and deal with packet errors. The final ETX character signals the end of the packet.
The Channel exchanges messages with the software layers above it using the TeleoMessage data structure. This structure is what you will send and receive messages with if you use the SDK at this level.
#define TM_DATAMAX 20 typedef struct { uint8 address; uint8 count; uint8 data[ TM_DATAMAX ]; } TeleoMessage;It is important to observe that the Channel doesn't interpret messages that it passes through. That task is left to higher layers. All the Channel does is deal with the Start and End bytes, and the checksumming process.
There is a sample program, TeleoChannelSimple.c, supplied with the SDK that demonstrates the principles of the Teleo Channel code and which can be read alongside the following detailed description.
The first step is to create an instance of the Channel. This is achieved with the TC_Create( ) call. Then the owner of the Channel (your application in this case) can set up the various callbacks that are necessary.
TeleoError TC_Create ( TeleoChannel ** tc ) There are three callback functions to assign: one requests the owner of the Channel write a block of data (*output)( ), another (*timerRequest)( ) instructs the owner to start or stop sending timer events. The third (*receive)( ) is called when the Channel wishes to inform something that it's received a message. We'll see how all of these functions work in the next section. The process of starting a Channel up is depicted as follows:
Creating a new Channel Once you have an initialized Channel, you can start feeding it characters you get from the device connected to the Teleo Network. The Channel will accept these characters, parsing out messages as it goes. When finally one of the characters completes a good packet, the Channel will send the message to the application's (*receive)( ) function
Receiving a message through the Channel When you want to send a message to the Teleo Network, create and initialize an instance of TeleoMessage, then send it to the Channel by calling TC_send( ). The Channel will packetize it (add start and end bytes, calculate a CRC, etc.) and call the (*output)( ) callback. In this case, this means that your output( ) routine will get the request which you code will need to honor, however it wants to.
If the output( ) routine reports an error, an error is reported back to the application as a return value to the TC_send( ) call unless TC_sendQueueEnable( ) has been called enabling message queueing, in which case the outgoing message will be added to a queue and the message send attempted again later. If another message is sent by the owner before the old one is cleared it is added to the end of the queue to preserve message order. The size of this queue can be determined by calling TC_sendQueueSize( ).
Sending a message through the Channel The last methods to consider on the Channel are those to do with timer events. The owner of the Channel has the responsibility at certain times to call the Channel back with timer events. The Channel uses the (*timerRequest)( ) callback to tell its owner when it wants these events. When the events are activated, the owner should call the Channel using the TC_timerEvent( ) function. A parameter passed to the TC_timerEvent( ) function is ms - an estimate of roughly the timer interval. This is to create some flexibility for the implemention of the timer. Internally, the Channel is designed with a 10ms event timer in mind, this should be considered the optimal timer interval.
Internally the Channel uses the event for two purposes: the first is to check for abandoned incoming messages. Say, for example, the Channel is parsing characters in the middle of receiving a message, but the lower layers lose the rest of the incoming packet's characters. Without some kind of timer, the parser would just sit there mid-message waiting for the rest. What's worse is that when the next (presumably good) message comes along, it would be lost too since the parser would think the first characters of the new message really were the rest of the old one. So to prevent this situation, each time the Channel receives a timerEvent it increments an internal clock. Whenever a character is received by the Channel it clears that clock. So if the clock value ever exceeds a certain time, it is decided that the message has been corrupted and the parser is reset.
The other thing timer events are used for is to manage the outgoing message queue (if enabled via TC_sendQueueEnable( ) ). If the message queue is enabled and if ever a write error occurs when sending a message, the message is copied and queued. The send is attempted again later during one of the subsequent timer events calls.
So the timer is enabled when either one of the following is true: the parser is not in it's initial waiting state (i.e. it's processing an incoming message) or there was a send failure and a message is waiting in the send queue.
The use of timer events is not compulsory. The consequences of not using them are two-fold: first, there is no way to detect dead messages in the parser - so messages that get corrupted will probably also corrupt the next message. Second, there is no way to implement a send queue (since there is never any time allocated to retry the send. So if you're planning to not use the timers you will need not use the send queue.
Timers are not necessary until they are requested, and can stop once they are unrequested again, although it is not an error to ignore the (*timerRequest)( ) function call and run the timerEvents all the time.
The Teleo Module Manager has two main tasks - maintaining a record of the modules available on the network and exchanging messages with those modules.
All the Modules the manager knows about are kept in a list which may be queried, searched, and so on. Modules can be added to the list directly by your code, or they can be added automatically when a module is discovered on the network.
Incoming Messages are decoded and turned into callback invocations. Outgoing commands are formatted into messages and sent on to the Channel for packetization and final sending.
You would use this layer if you need some mechanism for managing connected modules and access to module messages. Each message a module can send is returned in its own callback, and Module Manager functions exist to send all the messages a hardware module can receive.
There is a sample program, TeleoModuleSimple.c, supplied with the SDK that demonstrates the principles of the Teleo Module Manager code and which can be read alongside the following detailed description.
Before describing the API to the Module Manager in detail, we need to quickly examine the specification of module addresses and types.
Modules on the network have two essential characteristics: they have an address which specifies how they may be reached and they have a type which describes exactly what kind of module they are. These specifications are both strings to permit a great degree of flexibility in their use.
Addresses can be in any of the following forms:
NULL - a null address matches any module on the system with the specified type.
[teleo network address] - (e.g. "3" or "10") the teleo network address of the module - it can be any number from 2 to 63. This can be on any channel.
[channel] - (e.g. "usb", "serial") matches any address on the specified channel The teleo network address portion is the same as the previous form: specifying the teleo network address of the module.
[channel]/[teleo network address] - (e.g. "usb/3", "serial/10") the channel portion specifies which channel is being used. The teleo network address portion is the same as the previous form: specifying the teleo network address of the module.
Type can be in the following forms:
[module type] - (e.g. "3" or "4") specifies the module type by MakingThings - the default manufacturer.
[manufacturer].[module type] - (e.g. "com.makingthings.3", or "com.makingthings.10") the manufacturer portion is the domain and server name of the manufacturer and the module type is their numeric type number for the module
[manufacturer].[module type name] - (e.g. "com.makingthings.multiio" or "com.makingthings.servo") the manufacturer is in the same form as above and the module type name is used instead of the type number to identify the module.
Creation
Creating a Module Manager involves creating a Channel (for details see the Channel section) and a Module Manager, then informing the Module Manager about the new Channel. Finally, those callbacks your application is interested in can be assigned so the Module Manager can communicate back to your application.
Creating a new Module Manager Module Found
When everything's running, the Channel is passed incoming characters by the application and sends outgoing characters out via application code just as described earlier in the Channel section on Incoming Characters.
Eventually, the Channel may receive and unpacketize a Module Identification message for a module it has not yet seen. When this is passed to the Module Manager, the Module Manager creates a TMM_Module object corresponding to the object described in the message, adds it to the Module Manager's internal list and makes a (*moduleBound)( ) callback. The owner of the Module Manager is thus informed of the new module.
Identification message leading to a (*moduleBound)( ) callback. When the Module Manager's owner receives this notification, it may do with the information whatever it likes. If it is more than trivially complex, it may chose to create a object that corresponds to the new module. To avoid lenghty look-up processes it sets the new module's external reference so that all future callbacks from the Module Manager are prefixed with the more owner-friendly reference to the owner's object. See the discussion on external references earlier.
New module sequence In the case depicted above, once it receives a new MODULE_IDENT message, the Module Manager has created a TMM_Module object we'll call m. The (*moduleBound)( ) call is made passing m as a parameter (along with the external reference for the Module Manager itself) out to the Application. During the call, the Application makes an object (mExt) that contains information about the module useful to the Application. In the final step, the application calls the Module Manager to indicate that the new module's external reference should become mExt (via TMM_moduleExternalSet( )). This is an indication to the Module Manager that In the future, all callbacks that refer to that same module shouldinclude a reference to the application object as well. You will see examples of this improves things below.
Module Find or Create
Frequently applications need to locate a particular module, not merely wait around for that module to appear. To meet this need, the Module Manager has the ModuleFindCreate( ) function. It can be used in several ways.
You can call ModuleFindCreate( ) with an exact module address and type. If the module that fits that description has been identified on the network and the Module Manager has it on its list, the pointer to that module will be returned. If a module that fits that description has not been found yet on the network, one is created anyway. The idea is that if it is explicitly asked for, then we should assume that the module is in fact on the network. In the example below, since the address and type are specified completely the module will always be found.
TeleoError TMM_moduleFindCreate ( TeleoModuleManager * tmm, cchar * address, cchar * type, bool * bound, TMM_Module ** tmmm )
You can also call ModuleFindCreate( ) with an under-specified address and a type. In this case, if a module that matches the address is known to the Module Manager, that module will be returned, otherwise the moduleFindCreate( ) function will fail.
One slight snag with all this is that if the module is found, the (*moduleBound)( ) callback can not be invoked in the middle of the ModuleFindCreate( ) routine since the application code may not yet be ready to receive it - a classic race condition. To solve this problem, we pass in a bound pointer variable to permit us to know if the module was bound or not. If your calling code needs to do important things to the module once its bound and would normally use a bound callback, you can test the state of the bound variable and make the call yourself when you're ready to do so.
Module Information
The Module Manager's internal list of modules may be queried by using the following routines:
TeleoError TMM_moduleCountGet ( TeleoModuleManager * tmm, int * count )
TeleoError TMM_moduleGet ( TeleoModuleManager * tmm, int index, TMM_Module ** tmmm ) Once a module has been located via any means, it may be queried using the following calls:
TeleoError TMM_moduleAddressGet ( TeleoModuleManager * tmm, TMM_Module * tmmm, cchar ** address )
TeleoError TMM_moduleTypeGet ( TeleoModuleManager * tmm, TMM_Module * tmmm, cchar ** type )
TeleoError TMM_moduleVersionGet ( TeleoModuleManager * tmm, TMM_Module * tmmm, uint8 * version_major, uint8 * version_minor ) Incoming Messages
As the application receives characters from the OS and supplies them to the Channel, the Channel finds out which messages are being received and extracts the information from them accordingly. As each is extracted the corresponding callback is issued with the contents of the message parsed and provided as parameters. In all cases the when the callbacks refer to a specific module, the appropriate reference is supplied. All of the network messages are set out in the Teleo Network Messages section of the Teleo System part of the User Guide.
Module Manager callback messages There are many different kinds of Module Manager messages that report many different situations. The general format of the callbacks passes most of the following as parameters:
tmm - the module manager
tmmExt - the external reference associated with the Module Manager
tmmm - the module object
tmmmExt - the external reference associated with the modulethen other parameters as appropriate
The moduleBound function is called when a new module is attached to or detached from the network.
TeleoError (*moduleBound)(TeleoModuleManager *tmm, void *tmmExt, TMM_Module *tmmm, void *tmmmExt, bool bound);
The dataReceive function is called when data is received from a known module on the network.
TeleoError (*dataReceive)(TeleoModuleManager *tmm, void *tmmExt, TMM_Module *tmmm, void *tmmmExt, uint8 device, uint8 property, TeleoData *data);
The button press function is called when someone or something presses the button on a module.
TeleoError (*buttonPress)(TeleoModuleManager *tmm, void *tmmExt, TMM_Module *tmmm, void *tmmmExt);
The readdressReceive function is called when the module enters re-addressing mode (when the module's button has been pressed for more than 3 seconds).
TeleoError (*readdressReceive)(TeleoModuleManager *tmm, void *tmmExt, TMM_Module *tmmm, void *tmmmExt);
The resetReceive function is called when a module first powers up or is reset.
TeleoError (*resetReceive)(TeleoModuleManager *tmm, void *tmmExt, TMM_Module *tmmm, void *tmmmExt);
The echoReceive function is called when an echo message is received from a module.
TeleoError (*echoReceive)(TeleoModuleManager *tmm, void *tmmExt, TeleoMessage *message);
The addressConflict function is called when the Module Manager detects either two modules at the same address on the network or a conflict between a created module and one found on the network.
TeleoError (*addressConflict)(TeleoModuleManager *tmm, void *tmmExt, cchar *address);
The unknownMessageReceive function is called whenever the Module Manager receives a message that it doesn't understand.
TeleoError (*unknownMessageReceive)(TeleoModuleManager *tmm, void *tmmExt, TeleoMessage *message);
The messageReceive function is called whenever the Module Manager receives any message. This is provided to allow the owners of the Module Manager to do its own message processing.
TeleoError (*messageReceive)(TeleoModuleManager *tmm, void *tmmExt, TeleoMessage *message, TMM_Module *tmmm_);
The capacityWarning function is called when a module receives a capacity warning message from a module indicating that it is processing too many messages and it may soon overflow.
TeleoError (*capacityWarning)(TeleoModuleManager *tmm, void *tmmExt, TMM_Module *tmmm, void *tmmmExt, bool incoming);
Incoming data messages may have been solicited in one of two ways. Either the data was requested directly by sending a Data Request Message or indirectly by requesting an update whenever a change occurs in a property value.
In the first case, the Application calls TMM_dataRequest( ), passing the reference to the module, the device index and the property index. At some later time if the module is still on the network and device and port reference are legal for that module, a Monitor Data message will be received and the application's (*dataReceive)( ) function will be called.
Data Request and return Monitor Data message The second method is to request that a particular property, or all properties on a module or all properties on the whole network send their updated values whenever they change. The scope of this is determined by the monitor calls: TMM_monitor( ) will permit the monitoring of a single property on a device. TMM_monitorAll( m ) sent to a module, m, will cause all the properties on a device to be monitored. TMM_monitorAll( NULL ) will cause this message to be broadcast to all modules. Note that since the message is being broadcast and broadcast messages are not sent reliably, there is no guarantee that all the modules will receive the message, so it is advisable to send it multiple times.
When the appropriate monitor mode is set up, Monitor Data messages will be sent from the modules, which will result in (*dataReceive)( ) callbacks.
Monitor All message to all modules makes them send messages whenever a property changes Outgoing Messages
When the application needs to send messages to a particular module/device/property, it calls the TMM_dataSend( ) function. The lower layers of the software send the request as a Data Message out to the module.
Setting a Property with the TMM_dataSend( ) call. The application calls TMM_dataSend( ), passing in the module, device index, property index and the data. The module manager populated the appropriate message structure and sends it to the Channel, which then packetizes the message and sends it out (via the Application callback (*output)( ) ).
Module Manager Data Send Application programs that interact with Teleo Modules will mostly want to do so at the device and property level. Conceptually you want to deal with a potentiometer connected to Analog In 0 on a MultiIO, not the MultiIO. The Teleo Device Manager provides an easy mechanism to communicate with Teleo Devices. The idea is that you write a specification for the module and device that you're interested in, register it with the Device Manager and get a pointer to a TDM_Device back. If a module is found on the network that matches your description, it is connected (bound) to the device object. Once your device is bound, you can use it to send properties to the actual network device you're pointed to and receive property updates from it.
The advantage of this layer is that the creation of the device can be separated from the location of the device on the network, you can create device abstractions at your program's initialization time and not worry if the module you're talking to is actually there or not.
There is a sample program, TeleoDeviceTest.c, supplied with the SDK that demonstrates the principles of the Teleo Module Manager code and which can be read alongside the following detailed description.
Creating the Device Manager
The Device Manager, requires a functioning Module Manager to handle the Module operations. So the first task is to create a Teleo Module Manager (see the section above detailing how you do that).
Then the Device Manager is created by calling TDM_Create( ). This call returns a reference that you will use in all communication with the Device Manager.
Once the Device Manager has been created, you need to tell it about the Module Manager you want it to use. Do this with the TDM_moduleManagerSet( ) function.
Device Manager Create The Device Manager has two callbacks that it invokes from time to time; (*deviceBound)( ) and (*propertyUpdate)( ). These are called when devices are bound and when new property data arrives for a device. These callbacks are shadows of the ones provided on the TDM_Device objects themselves and are provided for convenience in simple implementations.
Creating Devices
Once the Device Manager is running, your application code is free to create the device proxies that it needs in order to perform its tasks. Do this with the TDM_deviceCreate( ) function. You specify the address and the type of the module you want to communicate with and the index of the device on that module that you want to connect to. See the Module Manager's Addresses and Types section for more information.
If you specify a address of NULL, you are instructing the Device Manager that you're willing to talk to the specified module whatever address it has. This is very handy if you only have one of any kind of device on the network, since you never have to worry about what its address is. If you do have multiple devices of the same type, it is essential that you specify which one you want to communicate with otherwise the selection of the module the Device Manager binds to will not be consistent.
What you get back from this call is a pointer to the new TDM_Device.
So you use the TDM_deviceCreate( ) function to create a TDM_Device. With the pointer passed back, once the device is bound to a module on the network, you will be able interact with the device, sending and receiving property data.
At the time you create the device, the Module Manager's list of known modules is consulted and if a match is found or if you specifed the module precisely, you are considered to be bound immediately. In the case where you're immediately bound you will be informed of this by a bound variable that you have to pass to the create function. You should call the appropriate bound function yourself when you've finished setting everything up.
Device Manager Device Create Devices have their own callbacks that are similar to the ones on the Device Manager. One, (*propertyUpdate)( ), exists to inform the Device owner about property updates, the other, (*bound)( ) informs when the device is bound and unbound.
Binding
Frequently, the device created will not be bound immediately because at initialization time, the network has not yet had enough time to identify all the connected modules. This process takes place over seconds. Whenever a new module is identified on the network, the Module Manager invokes its (*moduleBound)( ) callback. When the Device Manager receives this notification, it scans its list of devices to see if any existing devices can be bound to the new module. For each device that can be bound to the new module, callbacks on both the Device Manager, and the TDM_Device are invoked. This process is outlined below.
Device Manager Module Bound Once the module is bound, property data can be exchanged with the device on the network module.
Property Set
To set properties on the device's bound module, you just need to call TDMD_propertySet( ) passing in a property index and a TeleoData structure initialized with the data you want to send. This call kicks off a process where the various Managers perform their various functions on the data and ultimately the application is requested to send a block of data out on the appropriate OS interface.
Device Manager Property Set Property Update
When an incoming message is decoded by the layers of Managers to be a new property value, the (*propertyUpdate)( ) callback is invoked on both the Device Manager and the Device itself. This call passes the details of the device involved, the index of the property being updated and the data itself.
In addition to informing the owning code that a message has been received, the data itself is stored so that it remains available for the application to query asynchronously.
Device Manager Property Update Property Get and Request
All data sent to the TDM_Device during property updates is recorded by the device itself so the application doesn't have to. At any time after a property update has occured, the application code can request the latest data value by calling TDMD_propertyGet( ).
Device Manager Property Get If it is unlikely that an update has been received from a property (for example if it's primarily for writing to), you may request the current value of the port with the TDMD_propertyRequest( ) call. This call will initiate a fairly long set of interactions - first the module is sent a message requesting the specified property value, then the module sends a monitor message back with the data in it. This data appears to the application as a regular data update.
Device Manager Property Request The Teleo Device Proxies are the highest level abstraction the Teleo Application API provides to the user. They present Teleo Devices to the user in a way that is very easy to program. In addition they name all the properties so there is no need to remember property indicies.
Teleo Device Proxies are the objects that programmers create and use when they're using the Teleo Easy layer. Teleo Easy sets everything up so that there's not really mush else to do. If you're not using Teleo Easy, you need to set the Teleo SDK up yourself. This includes the Teleo Device Manager, the Teleo Module Manager, a Teleo Channel and a Teleo Interface.
Creating Proxies
With or without Teleo Easy, once you have a well configured Teleo Device Manager you can start creating proxies. There is a separate create( ) function for every proxy, but they all share the same signature. You pass in the Teleo Device Manager, the address of the module the device is implemented on, the device index and variables to hold the bound state and the created proxy.
TeleoError TMioDin_Create ( TeleoDeviceManager * tdm, cchar * address, uint8 dinDeviceIndex, bool * bound, TMioDin ** tmdin ) For the address parameter, you can either pass in NULL which indicates that you don't care what the address of the binding module is, or you can pass in a string describing it. See the discussion on module addressing for more information about the address parameter.
The deviceIndex is from 0 to the number of that kind of device on the module - 1. This means, for example that for the TMioDin proxy, it can take a device index from 0 to 3.
The bound parameter informs the caller whether the device bound immediately upon creation. If this is so, the calling program should call whatever function it needs to to intialize it.
The diagram below depicts the process involved in creating a proxy. First the proxy object itself is created. Then the Teleo Device Manager is requested to create a TDM_device with the address as supplied to the proxy create function and with the module type supplied by the proxy. The Device Manager tries to bind to a module and returns whether it did or not in the bound variable.
Device Proxy Create Proxy Binding
The proxy may become bound in two ways: either at create time because the module was able to be located immediately or later when a matching module is located on the network.
A proxy may be re-bound if a module reset message is received.
Regardless of what the cause of the binding was, the proxy does a number of things. First, it has to inform the module that it's interested in receiving property updates from the readable properties (by sending the propertyMonitor request). Second, if the application has set any of the properties before the proxy was bound, these properties have to be sent to the device. Thirdly, for any properties not set in the previous step, the proxy requests those values from the hardware. Some time will elapse before these messages return - when they do the application can be called back with a property update function.
Device Proxy Bind Proxy Property Request
The application can request a value be retrieved from the network at anytime by issuing a propertyRequest call. Note that when this function returns, the value will not yet have been obtained since the message has to go all the way out to the module and come back.
Later when it does come back, the propertyUpdate function will be called.
Device Proxy Property Request Proxy Property Get
Monitoring is initiated on the readable properties by the proxy at bind time. So whenever any of those values change an update message is returned through the system to the application. Since all the values are retained by the TDM_Device, the values can be checked and read anytime by calling the propertyGet function on the proxy.
Device Proxy Property Get Proxy Property Set
Setting a property is a matter of propagating a send request through the layers until it is sent out as a message to the module.
When a property is set, its value is saved in the TDM_Device so that it may be read back anytime and so that it can be re-sent if ever the module is reset or replaced.