|Author:||Sebastien Lelong, The SirBot Project, 2005-2007|
This is the first draft of the SirBot Documentation. Some sections are still "work in progress". More to come soon.
- 1 Introduction
- 2 Installation
- 3 Protocols
- 4 Communications
- 5 Events
- 6 Frequently Asked Questions
|Revision:||$Revision: 466 $|
BTW, what's the SirBot Project ? What it is for ? Good question, I've often wondered what it was. As of today, I'm quite clear about what it is, what it does, and what it should be and do... And the easiest way to understand what it is and what is does is to consider how you'd do things without it. So... How does it look like without SirBot ?
You like dealing with electronic stuff. All those resistors, those capacitors, those leds are just great. Once, you discover microcontrollers, these chips you can (almost) easily program to do amazing things, such as... blinking a led ! Yes, blinking a led is great. A lot a time was needed to blink a led, but you're proud, because you now see all the opportunities now you're able to blink a led. You decide to build you own home-made next-generation Aibo robot...
From blinking a led to your next-generation Aibo, that's a long way. So you start, for instance, to get distance measures, thanks to this GP2D02 IR ranger from Sharp. This will be usefull when your Aibo needs to move and detect objects. But here it becomes more complicated... You'd like to order to take distance whenever you want. You need to communicate with your microcontroller. And remember there's an old protocol which could help: serial communication aka RS232 com (or the like). So you decide to code this communication protocol and now you're able to trigger your favorite serial communication program (cu ?) and send orders to your microcontroller. Yes, you order it to take a distance measure, and it returns the result: a char. Just one. Far from a distance in cm... Sometimes you can't even see the char because it's not printable... You need to convert this char into a human readable distance. Coding the translation within the PIC would require several hours (days ?) whereas it just takes 10 minutes to do it in python. Too bad. More, it would take too much memory (2K for the PIC 16F628). Way too much. And you need memory. And you think it's just a waste of time and memory to have this translation done within the PIC. More, what about reacting when the distance becomes too short ? How to easily detect this event ? Give up...
Because your robot will also need to move, you decide to start learning how to move servos. Again, you need to communicate with your bot to send the positions for all your servos. Doing this through the serial communication becomes annoying. It's unreadable, worse unwritable... And you'd like to send order such as "move servo A so it describes a circle. Twice. Please." instead of "move servo A to position 1, move servo A to position 2, move servo A to position 3, move servo... Twice ? Kidding !".
This example is illustrated as followed: an IR ranger attached to a servo is moved, incrementally. On each step, the ranger takes a distance measure. Here's what the dialect between the PC and the bot looks like:
You need to aggregate orders into simpler one. And again, you don't want to code them in the PIC (memory). You want to code them from your friendly PC. Doing so, you'll also be able to dynamically program your bot: you just code the primitive actions within the PIC, and keep the other actions programmed from your PC. The code will be cleaner, easier to maintain, your future Aibo will be more powerful...
Several days, even months, have passed. You look at your PIC microcontroller and wonder what it is: "What is this chip done for ?". you look at the assembler code and... well... it's just unreadable. Even with all those comments (because remember, days have passed). After one hour, you finally remember you can get distance measures with it. Another hour and you remember how to convert those weird returned chars into a human readable distance, thanks to these graphs and sheets and this "man ascii". You then remember you can move servo but another hour is required to understand why moving your servo to position 13 by sending "SA13" just does not work. (position is coded with 3 digits using the MIC800 chip).
You need a way to easily build your bot, access your bot, communicate with it, understand what it says, and react according to different events it produces. You need a framework, easy to use.
- Building a bot is easy
just look at the available modules, and assemble them. More modules will be soon available so you'll be able to build quite complex bots
- Communicating with a bot is easy
thanks to the protocol declaration syntax, you just define actions that can be understood by the robot
- Controlling and programming a bot is intuitive
- SirBot is python-powered, so you can access your bot with python CLI, such as ipython, and interactively control and program your bot
- Monitoring a bot is simple
- when you talk to your bot, returned response can be converted into events, thanks to the event declaration syntax. Events can be created from other events (cascading events). Events can also be conditionnaly triggered under specific conditions (pre-condition), and activate user-defined actions (post-conditions) according to a particular event state.
Compared to the previous results, here's what it looks like when you want to range a scene with SirBot:
|Revision:||$Revision: 455 $|
There's no setup.py file as in almost every python lib, nor easy way to build jal lib, so following is an install howto.
First untar/gunzip the archive:
tar xzf sirbot-0.1.tar.gz cd sirbot-0.1
You then have to adjust your PYTHONPATH so python can find all the SirBot librairies:
Time to run tests... (yes, there's a Makefile). Check all tests have passed:
make test ... Ran 22 tests in 0.105s OK
SirBot is ready to be used. Have fun !!!
If you plan to build a robot with SirBot, using the available hardware modules, you may want to use the SirBot jal librairies in order to get the assembler (or hex) code to program your PIC. Here is an example of use Jal to compile the code for the TamaBot Daycare.
Jal librairies are located in jal/lib (SirBot specific) and jal/3rdparty_lib (original jal lib)
There's a Jal binary executable you can use. If needed, you can find Jal sources as a 3rd party component in the 3rdparty directory. Please refer to Jal doc to compile you own jal executable.
Time to compile the TamaBot DayCare code:
cd jal ./bin/jalv2 -s "lib;3rdparty_lib" labs/tamabot/tamabot.jal 2> /dev/null ... 0 errors, 14 warnings 228 tokens, 138060 chars; 4043 lines; 17 files generating PIC code pass 1 generating PIC code pass 2 writing result Code area: 854 of 2048 used Data area: 57 of 224 used Software stack available: 80 bytes Hardware stack depth 4
Errors are put in /dev/null, because while compiling the jal file, the Jal compiler seems to forget to release a tag, thus producing a long list of warning message. If it says "0 errors", this should be ok. Check here for more... And, don't forget to put quotes while telling jal where to find libs (-s option).
- You're done. Just have to program the hex file to your PIC, using your favorite programmer.
|Revision:||$Revision: 471 $|
Protocols are the most important part of SirBot, since it's where you going to declare what your robot can do and how.
The basic architecture of a robot built with SirBot is quite simple:
- A Core System, on a PC (or the like, an iPaq, etc...), sends orders.
- The Bot receives orders, interpret them and perform actions
This means (almost) everything first comes from the Core system, not from the Bot. This is because it would dramatically make the bot too much complex: you'd need to define a kind of server within the bot, which would listen to connections and orders, would have the ability to dynamically program actions, would store every piece of information, would handle events, etc...
Because we (at least me) can feel the cold wind of fear while imagining how to do this with a basic microcontroller, the simplest way to achieve these is to keep them on a PC, and do the mimimal things within the bot. Those minimal things are called primitive actions (also previously called procotol unit, that is the smallest unit of a protocol definition. Nomenclature changed when the "almost" big rewrite occured). They represent what the bot can do, in its most primitive form (!).
- A bot has two motors associated to two wheels, so it can moves around. From the bot perspective, the primitive actions are "move motor A /B forward/backward". It doesn't understand what "turn left !!!" means. Because "turn left !!!" is done moving one motor in a way, the other in the other way. That is, "turn left !!!" is the composition of two primitive actions. Why implementing this composed, complex action in the bot ? To keep it simple, this type of action will be kept in the core system, on the PC.
So, only primitive actions are implemented within the bot, all others complex actions are in the PC. Now, how to order the bot "turn left !!!" ? Simply by telling it, from the PC: this complex action will be translated into the corresponding primitive actions. Only these primitive actions will be sent to the bot, as it can only understand this kind of action.
A SirBot Protocol is the definition of complex actions and primitive actions:
- Complex actions are just the composition of other complex actions, or primitive actions
- Primitive actions are what the bot can actually do, thus what will actually be sent to robot.
In practice, primitive actions is where SirBot will do most of its job, by abstracting what's been sent and how it's been sent to the bot. This is where most of the protocol definition stands, as it defines how to send requests to the bot and receive responses from the bot.
The Protocol Definition Syntax (PDS) declares primitive actions in a protocol. PDS means nothing but it's a 3-letter acronym, so it's probably something cool...
Because SirBot is python-based, an action (primitive or complex) is just a class method. The difference comes from the fact class methods for primitive actions are decorated with @connector. Using this decorator, we tell SirBot what to send to the bot, and what is expected as a response.
@connector(char="w") def move_servo(self,servo_id,position): pass # "bot" is a protocol object, that is, what's representing the robot bot.move_servo(ROTATIVE_SERVO,117)
When we call the move_servo method, SirBot and its @connector decorator will translate a python call into something meaningful to the bot. How does it works ? The char argument specifies the action character: when the bot receives the letter "w", it knows it'll have to move servo (action is coded within the bot). The protocol also defines how a list of arguments can be sent to the bot (not shown here in this piece of code). The @connector actually build a string, and send it to the bot. The important thing is we, human, don't care about how the translaction occurs, how the communication is processed, etc... we just write python code !
@connector(char="r",lreturn=1) def ir_range(self): '''Take distance measure with a IR ranger''' pass (request,response) = bot.ir_range()
Same as above, expect we declare, with lreturn, that when calling this method, we're expecting a result. Precisely, the length of the returned chars from the bot must be 1. Exactly. Or it'll raise an error, informing something bad happened... Also note we get a returned tuple: a request and a response object (when calling a primitive action, we always get request/response objects, whether we're expecting a returned value or not). The request object contains every piece of information used to build what's actually been sent to the bot. The response object is what we've received from the bot, that is, the distance. If no returned value is expected, the content the response object will be None.
Using the PDS, we've been able to declare the primitive actions. Now, how complex actions are built ? Easy. Just write some python code... If you want your bot the turn left then:
LEFT_WHEEL = "A" RIGHT_WHEEL = "B" def turn_left(self): self.move_servo(LEFT_WHEEL,10) self.move_servo(RIGHT_WHEEL,120)
SirBot is designed as a master/slave communication process: the core system (PC) is the master, the bot is the slave. So everything first come from the PC (like sending primitive actions).
Sometimes this model is not appropriate. Imagine a robot able to move around, and detect walls. The action "detecting a wall" can't come from the PC, since it's the bot which must inform something terrible is going to happen... To prevent this disacter, SirBot is able to handle background actions (or tasks), which will recurrently be executed. To be honest, the model remains master/slave, but SirBot provides an easy way to "poll" the bot, thus asking often "it is ok ?".
This poll mechanism is based on the @poll decorator. It can be used both with primitive and complex actions. Another application is to use this decorator to recurrently process a task. Actually, the delay value will determine if the action is a way to bypass the master/slave model, or just a recurrent task...
# This complex action will @poll(every=0.5,name='The job that detects the wall') def detect_wall(self): # do some stuff to detect the wall... self.detect_wall() # task become active
Every 0.5 seconds, the bot will be ordered to check is there's a wall around there
# tagging a primitive action @poll(every=2,name='moving forward') @connector("m") def motor_forward(self,step): pass self.motor_forward(1)
The core system will order the bot to move one step forward, every 2 seconds.
We can even declare a polling job for an action which generate events
@poll(every=10,name='watchdog',trigger=True) @emits(PongEvent,"!") # "!" is the pong char @connector("?") # this is the ping char, the bot must respond with "!" def watchdog(self): pass
We'll check the bot is alive. If not, we won't get the pong char ("!") and exception will be raised (logic in PongEvent class), stopping all active jobs. The trigger param makes this action switchable (on/off), if the protocol uses a job manager:
from sirbot.core.protocol.jobs import JobManager com = CommunicationManager(communicator=rs232_com) job = JobManager() my_protocol = WatchDogProtocol(com_manager=com,job_manager=job) my_protocol.watchdog() # starts the job my_protocol.job_manager.show() # show active jobs my_protocol.do_something() # can still do others things my_protocol.watchdog() # stop the job my_protocol.job_manager.stop_all() # or stop all jobs...
|Note:||This @poll feature is still very experimental. Many communication problems can occur while starting background jobs together. It's very important to deal with errors and exceptions, and re-init the bot is a consistent state if a job crashes. Also important is to understand that while different jobs are running together, they access the same resource (the communicator). There's currently a RS232 communicator which handle concurent access with a semaphore object. Still, if one crashes, others may get wrong results from the bot, since the crashed one may leave the communication process desynchronized (eg. the job managed to send a char, but crashed before it receives something from the bot, and release the communicator resource (semaphore). Other jobs may get this result but should not...)|
|Revision:||$Revision: 488 $|
This part explains how the Communication Manager handles communications between the Core System (PC) and the robot. Communication can be done using two modes:
- in a master/slave mode: the Core System is the master, the bot is the slave. This means the bot is unable to start the communication and send messages. The Core System sends Request object and receives a Response object.
- in a peer-to-peer mode: both the Core System and the bot can initiate the communication. When the bot is the initiator, it sends Message to the Core System.
Using one of this mode depends on the FrameManager used.
When primitive actions are invoked, a Request object is created by the @connector decorator, and passed to the communication manager.The Request encapsulates information about the actual action, its name, its arguments if given, the length of the expected response. Since this data is still very pythonic, it's converted into a Frame object, which encapsulates the communication logic, such as what to use to determine the start/end of an argument list, how to delimitate arguments, does a start/end of frame must be used... The frame object is then sent to the bot. At this level, this is what the bot can understand. Its job will then interprete the action, potentially parse the arguments, do the job and send a response.
Each time the com. manager sends a Request, it creates a Response object. This represents what the bot sent to the Core System. If nothing is sent, the Response value will be None. If a return value is expected (set by the lreturn parameter of @connector), the com. manager will fill the Response with this information.
Message objects only occurs when using the peer-to-peer mode. Messages are what the bot sends, without beeing ordered to, as in the Request/Response dialog.
While creating a new bot object, a Communication Manager object is required. This manager uses a Communicator, the object actually responsible for sending and receiving data from the bot. It also requires a FrameManager, responsible for creating and sending frames to the bot, and digesting frames coming from the bot.
The way the manager should handle the communication can be set while instantiating a CommunicationManager object or, better, defining this directly within the protocol object:
from sirbot.core.protocol.base import BaseProtocol # First specifies communication behavior within # the protocol class MyProtocol(BaseProtocol): # as class attributes sac = "(" eac = ")" dac = "," sof = None eof = None # Then create a Communicator and a Com.Manager from sirbot.core.communication.rs232 import RS232 rs232 = RS232(port="/dev/ttyS0",baudrate=115200) from sirbot.core.communication.manager import CommunicationManager com_manager = CommunicationManager(communicator=rs232) # Finally init the whole from the protocol com_manager.init_from_protocol(MyProtocol)
Since all of this is very standard, this is automatically done when subclassing the BaseProtocol class:
- This is the Start Argument Char and specify that following are arguments
- End Argument Char, specify the end of arguments
- Delimiter Argument Char is used to seperate arguments
- Start Of Frame. If set, every frame will start will this char.
- End Of Frame. With both SOF and EOF, frames of different/variable length can be sent to the bot. And the length of the bot's response can also be variable.
The Communicator is the object which actually sends and receives stuff from the bot. Since SirBot handles (should ?) different communication link, this object can be everything, providing it known how to connect to the bot, send and receive things, and flush potential buffer. Currently, there's only a RS232 communicator. More to come as soon as it'll be needed...
The RS232 Communicator handles communications over a serial link. It uses py-serial as a requirement. While instantiate this communicator, several parameters can be passed (all of py-serial's). Most importants are:
- port: /dev/ttyS0, /dev/ttyUSB0, ... What port to use.
- baudrate: 19200, 115200, ... What's the communication speed the bot understand
- allow_echo: each char sent to the bot must be echoed ? Not handle in the Communication Manager since it's quite low level
- max_tries: if allow_echo is True, how many times should the communicator try to get an echo before giving up ?
- thread_safe: will this resource be shared amongst different thread ? If so, setting thread_safe to True will make the communicator using a semaphore to acquire/release the serial link. Every attempts to use the communicator while the resource is not released will make access blocking, and waiting until the resource is available. This is mandatory when using background jobs.
A typical usage:
rs232_com = RS232(port="/dev/ttyS0",baudrate=115200,thread_safe=True)
A nice stub used to develop/debug without actually having a bot...
rs232_com_stub = RS232Stub(port="stub_port",baudrate=0) # When sending 'e', the bot will answer with 'R' rs232_com_stub.talking['e'] = 'R'
The FrameManager is a important part of the communication process. It's responsible for creating, sending and digesting frames. The FrameManager used will also specify the communication mode: Master/Slave or Peer-to-Peer.
By default, when creating a CommunicationManager, a standard and simple FrameManager is used. This one declares a Master/Slave mode (only the Core System can be the initiator), where every frames have a fixed length. This frame manager is easy to use (you don't even have to know it exists) but quite limited.
Sometimes, you can't know how long the response from the bot will be. The standard frame manager won't help here... You have to use a frame manager which can handle variable length frame. This is done using Start-Of-Frame (SOF) and End-Of-Frame chars. Currently, there are two frame managers, both works in Master/Slave mode:
- VariableLengthFrameManager: both frames for the bot and from the bot are variable length frames.
- SemiVariableLengthFrameManager: only frames from the bot are variable length frames
OK. the Master/Slave mode is not for you. Your bot have to send messages, even if not ordered to ("Hey ! Wall over there !!!"). You then need the BGReaderFrameManager. It's a combination of a variable length frame manager (with SOF/EOF) and a background reading thread. This also uses typed frames, to check whether the frame is a Response from a Request (Core System is the initiator) or Message (Bot is the initiator).
Since a FrameManager object is composed with a FrameCreator and a FrameDigestor, there can be a lot of possible combination to provide to correct communication behavior.
|Revision:||$Revision: 455 $|
|Revision:||$Revision: 455 $|
What is SirBot ?
The SirBot Project is a python framework used to build, program, control and monitor amateur robot. They are two main parts:
- The SirBot Module libraries (hardware) provides instruction to build a bot.
- The SirBot Software provides an easy way to program, control and monitor your bot
Why the name "SirBot" ?
Because I want to be polite with my bots
Where does the SirBot mascot come from ?
This is my key ring...
Does SirBot only communicate through RS232 ?
This is currently the only way to communicate, as this is only one I use... But the architecture is able to handle others. Or at least, it should :)
Do I need to use the SirBot modules, or can I use my own ?
Using the SirBot Modules can help you to get started, but you can use your own modules (ie. bot). The "only" things to do is to declare how to communicate with your robot, that is, defining a SirBot protocol.
Why doesn't the SirBot Module use PCB boards ? They would be smaller...
Because I don't have such a possibility. But feel free to send your own PCB boards, I'll add them to the libraries.