Lorien:ComponentIntro

From Lorien Documentation
Jump to: navigation, search

Contents

Basic Concepts

Lorien is dynamic component-oriented operating environment. Each unit of software functionality is an encapsulated component that can be loaded, instantiated, destroyed and unloaded at runtime while the remainder of the system continues to function. Components interact with one another using interfaces, which are collections of function prototypes. Components can provide interfaces, offering functionality to other components, and can require interfaces, expressing a need for functionality offered by another component. The required and provided interfaces of different components can be connected if they are type-compatible.

This basic premise is illustrated below.

ComponentModel.png

Components can declare as many provided and required interfaces as they like, and the compatible required interfaces of multiple different components X, Y and Z can be connected to the same provided interface of a component C.

Lorien is unusual because it includes an advanced runtime software management system and because its system building-model is uniformly applied to a very large proportion of node software, all the way down to hardware-facing drivers and schedulers, which can be added and removed at runtime like anything else. This uniform model of software is an important step towards fundamentally evolvable and autonomous systems.


Writing a new component

Every component in Lorien is described by two files; its component source code, and its configuration fragment. The component source code defines the functionality of the component and is written in a syntactically-augmented variant of C. The configuration fragment defines an abstract `role name' for an instance of this component, and defines how that instance's required interfaces are to be connected to compatible provided interfaces of other components (roles) in the system.

In the components/apps/ directory of Lorien, create a new folder 'myapp', and inside it create two files: myapp.c and MyApp.cfg

Inside myapp.c place the following code:

component provides{
ILife;
}

component requires{
ILog *output;
}

component state{
unsigned int ni;
}

static int ILife:start()
   {
   output -> log("Hi there! ni is %u\n", ni);
   return 0;
   }

static int ILife:stop()
   {
   return 0;
   }

int construct()
   {
   ni = 0;
   return OK;
   }

int destruct()
   {
   return OK;
   }

This component offers the provided interface ILife, and declares a dependency with the required interface ILog. The component also defines some instance state which is initialised in its constructor. The functions of the ILife provided interface are implemented using the IIntf:function() notation that is part of Lorien's minimal component-oriented presentation language.

The ILife interface itself is one that you'll be using a lot to start up and stop the functionality of your components. It's important to understand that the construct and destruct functions are only for the initialisation of state; your component is not connected to its required interfaces in these functions. When ILife:start() is called however your component is fully connected to all of its dependencies, and remains fully connected until ILife:stop() is called and returns.

The ILog interface plays the role of "standard output" in Lorien, in place of printf(). More detail on this is available here.

Note that Lorien's component "language" is not actually a complete language in itself, but rather is a small collection of syntactic additions to C. Component source files are first passed to Lorien's component language parser, which treats these special syntactic constructs, and then passes the rest (i.e. all functional code, structs etc.) directly to a standard C compiler. A complete component model reference is available here.


Writing a configuration fragment

Now that we have our component source code we need a configuration fragment to define an instance of this component and connect its dependencies to other components in the system.

In the file MyApp.cfg that you created in the components/apps/myapp/ folder, place the following:

[Role]
MyApp=myapp.so

[Bindings]
ILog->Output

This names our component instance abstractly as "MyApp", which will be instantiated from the object file "myapp.so". It then specifies that this instance's ILog required interface should be connected to a compatible ILog provided interface on a component instance abstractly identified as "Output". If no such instance currently exists in the system, or else it does not host a compatible provided interface, then no connection will be made, and our component's ILife:start() function will not be called until such time that all specified connections can be made.


Compiling and composing a system

Finally we need to compile all of the components that we want to use and compose them into an image to upload to a sensor node. This just represents the initial set of components that will be running on the node; Lorien can always add and remove individual components later.

Edit the Makefile in lorien/xmakefiles/tmote/ to add the following at the very bottom of the file:

myapp: base
	@echo Compiling the individual components that I want
	$(LCC) $(LIBRARY_DIR)/concurrency/task_scheduler.c -o $(OBJDIR)/tasksched.so
	$(CFBUILD) $(LIBRARY_DIR)/concurrency/Scheduler.cfg -o $(OBJDIR)/Scheduler
	$(LCC) $(LIBRARY_DIR)/data/output/output.c -o $(OBJDIR)/output.so
	$(CFBUILD) $(LIBRARY_DIR)/data/output/Output.cfg -o $(OBJDIR)/Output
	$(LCC) $(PLATFORM_DIR)/peripherals/serial_port_task.c -o $(OBJDIR)/serial.so
	$(CFBUILD) $(PLATFORM_DIR)/peripherals/SerialPort.cfg -o $(OBJDIR)/SerialPort
	$(LCC) $(APPS_DIR)/myapp/myapp.c -o $(OBJDIR)/myapp.so
	$(CFBUILD) $(APPS_DIR)/myapp/MyApp.cfg -o $(OBJDIR)/MyApp
	@echo Invoking the synthesis system to create a pure dynamics image
	$(MAKE) lorien_open CONFIG="$(CORE_CONFIG) $(OBJDIR)/Scheduler $(OBJDIR)/Output $(OBJDIR)/SerialPort $(OBJDIR)/MyApp"
	$(OBJ_CPY) lorien.o -O ihex $(IHEXFILE)

The LCC command used above invokes Lorien's component compiler which converts components to plain C and then invokes the platform's normal GCC compiler. The CONFIG variable lists the architecture we want to include in the image. lorien_open is the main Lorien build mode to create a dynamic image. Note that the output (object) file name of our compilation of myapp.c uses the name myapp.so, matching that used in our configuration fragment.

Next, from a command prompt in the lorien/xmakefiles/tmote/ type make myapp.u

This builds our image and then uploads it to a connected node on /dev/ttyUSB0 - we assume here you're using a TelosB / Tmote Sky and have connected it to a USB port on your computer.

NOTE: Before doing this ensure you have compiled the toolchain; if not go to lorienos/ and type "./install.sh" (you may need to make this file executable first with "chmod +x install.sh")

To actually view output from the node we need to use a program which can read / write to the serial port. You can use whichever program you like for this, but for now we'll use loriencomm.

Go to lorien/xtools/ and type ./loriencomm

Now press the reset button on the TelosB to see your message printed out!


Using timers, sensors and networking

The majority of WSN applications use some combination of timers, sensory inputs and networking. Here we upgrade our above example to use all three.

The process of locating and using another component in Lorien is always the same: locate the C file of the component you want in Lorien's source tree, and check the provided interfaces that it offers. Next look up those interfaces in the interfaces/ directory to find out how to interact with the component, and then declare a required interface of the desired type in your own component. After that, open the configuration fragment of the component you want to use to check its role name, and in your own component's configuration fragment specify a dependency of your required interface on that role name.

Finally, compile the desired component and its configuration fragment into your system using $(LCC) and $(CFBUILD), ensuring that the .so output name for LCC matches that specified in the configuration fragment, and include the compiled configuration fragment in your CONFIG= list.

Our updated component source code in myapp.c using a sensory input, a timer, and the network stack, is as follows:

component provides{
ITimerCallback;
IMXRadioCallback;
ILife;
}

component requires{
ITimer *timer;
IMXRadio *radio;
ISense *sensor;
ILog *output;
}

component state{
unsigned int ni;
}

#define PORT 5099
#define INTERVAL 5000

typedef struct _mp{
   int sensorValue;
   } MyPacket;

static int IMXRadioCallback:recv(unsigned char *data, size_t len, unsigned int fromID, unsigned int port)
   {
   MyPacket *pkt = (MyPacket*) data;
   output -> log("DATA: %i from %u\n", pkt -> sensorValue, fromID);
   return 0;
   }

static int ITimerCallback:timeout(void *tid, void *ptr)
   {
   MyPacket pkt;
   pkt.sensorValue = sensor -> getValue();
   radio -> send(&pkt, sizeof(pkt), PORT);
   return 0;
   }

static int ILife:start()
   {
   output -> log("Hi there! ni is %u\n", ni);
   radio -> registerCallback(this, PORT);
   timer -> setRepeatingTimer(this, INTERVAL, NULL);
   return 0;
   }

static int ILife:stop()
   {
   radio -> unregisterCallback(this, PORT);
   timer -> clearTimers(this);
   return 0;
   }

int construct()
   {
   ni = 0;
   return OK;
   }

int destruct()
   {
   return OK;
   }

A few things to point out here are the callback system, the stop() function, virtual ports, and the sensor input agnosticism of this code.

First, a collection of Lorien components support the registration of callbacks. These work in the same way as the listener design pattern in object-orientation, and we pass a self-reference to the callback registration functions using the keyword this. The component supporting callbacks will check if our component offers a compatible provided interface for the corresponding callback (e.g. ITimerCallback:timeout()) and if so will set up the notification on the appropriate event.

Second, now that we have incoming callbacks, we've added shutdown code to the ILife:stop() function. This is invoked when our component is about to lose full connectivity to its dependencies, and you must clear all callbacks and shut down all functionality in this function. Next, Lorien's default networking components support virtual ports, allowing multiple applications and protocols to share the network. You should generally use ports above 1024 for your own code.

Finally, note that the above code does not specify from which sensor it is reading; instead this is left to the architecture level to configure, making our component source code much more general.

Next we update our component's configuration fragment, MyApp.cfg, as follows:

[Role]
MyApp=myapp.so

[Bindings]
ILog->Output
ITimer->Timer
IMXRadio->MAC
ISense->THSense:Temperature

Here we specify that our general ISense required interface will be connected to a temperature sensor, and our generic IMXRadio interface will be connected to a MAC protocol.

And finally we update our make rule as follows:

myapp: base
	@echo Compiling the individual components that I want
	$(LCC) $(LIBRARY_DIR)/concurrency/task_scheduler.c -o $(OBJDIR)/tasksched.so
	$(CFBUILD) $(LIBRARY_DIR)/concurrency/Scheduler.cfg -o $(OBJDIR)/Scheduler
	$(LCC) $(LIBRARY_DIR)/data/output/output.c -o $(OBJDIR)/output.so
	$(CFBUILD) $(LIBRARY_DIR)/data/output/Output.cfg -o $(OBJDIR)/Output
	$(LCC) $(PLATFORM_DIR)/peripherals/serial_port_task.c -o $(OBJDIR)/serial.so
	$(CFBUILD) $(PLATFORM_DIR)/peripherals/SerialPort.cfg -o $(OBJDIR)/SerialPort
	$(LCC) $(PLATFORM_DIR)/networking/radio_mx_task.c -o $(OBJDIR)/radio.so
	$(CFBUILD) $(PLATFORM_DIR)/networking/Radio.cfg -o $(OBJDIR)/Radio
	$(LCC) $(LIBRARY_DIR)/protocols/mac/lpl.c -o $(OBJDIR)/mac.so
	$(CFBUILD) $(LIBRARY_DIR)/protocols/mac/MAC.cfg -o $(OBJDIR)/MAC
	$(LCC) $(LIBRARY_DIR)/random/park_miller_carta.c -o $(OBJDIR)/random.so
	$(CFBUILD) $(LIBRARY_DIR)/random/Random.cfg -o $(OBJDIR)/Random
	$(LCC) $(CPU_DIR)/time/timer_32ms_task.c -o $(OBJDIR)/timer.so
	$(CFBUILD) $(CPU_DIR)/time/Timer.cfg -o $(OBJDIR)/Timer
	$(LCC) $(PLATFORM_DIR)/peripherals/sensor_sht11.c -o $(OBJDIR)/sht11.so
	$(CFBUILD) $(PLATFORM_DIR)/peripherals/SHT11.cfg -o $(OBJDIR)/SHT11
	$(LCC) $(APPS_DIR)/myapp/myapp.c -o $(OBJDIR)/myapp.so
	$(CFBUILD) $(APPS_DIR)/myapp/MyApp.cfg -o $(OBJDIR)/MyApp
	@echo Invoking the synthesis system to create a pure dynamics image
	$(MAKE) lorien_open CONFIG="$(CORE_CONFIG) $(OBJDIR)/Timer $(OBJDIR)/MAC $(OBJDIR)/Random $(OBJDIR)/Radio $(OBJDIR)/Scheduler $(OBJDIR)/Output $(OBJDIR)/SerialPort $(OBJDIR)/SHT11 $(OBJDIR)/MyApp"
	$(OBJ_CPY) lorien.o -O ihex $(IHEXFILE)

You can now upload this code to two different sensor nodes and put one on battery power, with the other connected to USB, and use loriencomm as before to view sensor output from the remote node.


Using LEDs

Finally, LEDs are a good way to understand the basic states of sensor nodes from which output cannot be directly viewed (for example those running on battery power). Here we update our above example one more time to toggle an LED on data reception. This also helps to reinforce the use of component state and programming towards generalised behaviour that can be selected at the architecture level.

Update your component's required interfaces, state, and recv and construct functions to the following:

component requires{
ITimer *timer;
IMXRadio *radio;
ISense *sensor;
ILed *led;
ILog *output;
}

component state{
unsigned int ni;
int on;
}

static int IMXRadioCallback:recv(nsigned char *data, size_t len, unsigned int fromID, unsigned int port)
   {
   MyPacket *pkt = (MyPacket*) data;
   output -> log("DATA: %i from %u\n", pkt -> sensorValue, fromID);
   if (on)
      led -> on();
      else
      led -> off();
   on = !on;
   return 0;
   }

int construct()
   {
   on = 0;
   ni = 0;
   return OK;
   }

Again note here that the component source code does not know which LED it is controlling, generalising this out to the architecture level.

At the architecture level we change our configuration fragment to be:

[Role]
MyApp=myapp.so

[Bindings]
ILog->Output
ITimer->Timer
IMXRadio->MAC
ISense->THSense:Temperature
ILed->LED:Blue

Selecting the Blue LED. The LED:Blue notation here uses interface variants, which can be used on both provided and required interfaces, in this case to connect to a provided interface of type ILed on the LED component varianted as "Blue". Interface variants are discussed in detail here.

Finally we update our make rule to include the LED driver component:

myapp: base
	@echo Compiling the individual components that I want
	$(LCC) $(LIBRARY_DIR)/concurrency/task_scheduler.c -o $(OBJDIR)/tasksched.so
	$(CFBUILD) $(LIBRARY_DIR)/concurrency/Scheduler.cfg -o $(OBJDIR)/Scheduler
	$(LCC) $(LIBRARY_DIR)/data/output/output.c -o $(OBJDIR)/output.so
	$(CFBUILD) $(LIBRARY_DIR)/data/output/Output.cfg -o $(OBJDIR)/Output
	$(LCC) $(PLATFORM_DIR)/peripherals/serial_port_task.c -o $(OBJDIR)/serial.so
	$(CFBUILD) $(PLATFORM_DIR)/peripherals/SerialPort.cfg -o $(OBJDIR)/SerialPort
	$(LCC) $(PLATFORM_DIR)/networking/radio_mx_task.c -o $(OBJDIR)/radio.so
	$(CFBUILD) $(PLATFORM_DIR)/networking/Radio.cfg -o $(OBJDIR)/Radio
	$(LCC) $(LIBRARY_DIR)/protocols/mac/lpl.c -o $(OBJDIR)/mac.so
	$(CFBUILD) $(LIBRARY_DIR)/protocols/mac/MAC.cfg -o $(OBJDIR)/MAC
	$(LCC) $(LIBRARY_DIR)/random/park_miller_carta.c -o $(OBJDIR)/random.so
	$(CFBUILD) $(LIBRARY_DIR)/random/Random.cfg -o $(OBJDIR)/Random
	$(LCC) $(CPU_DIR)/time/timer_32ms_task.c -o $(OBJDIR)/timer.so
	$(CFBUILD) $(CPU_DIR)/time/Timer.cfg -o $(OBJDIR)/Timer
	$(LCC) $(PLATFORM_DIR)/peripherals/sensor_sht11.c -o $(OBJDIR)/sht11.so
	$(CFBUILD) $(PLATFORM_DIR)/peripherals/SHT11.cfg -o $(OBJDIR)/SHT11
	$(LCC) $(PLATFORM_DIR)/peripherals/leds.c -o $(OBJDIR)/led.so
	$(CFBUILD) $(PLATFORM_DIR)/peripherals/LED.cfg -o $(OBJDIR)/LED
	$(LCC) $(APPS_DIR)/myapp/myapp.c -o $(OBJDIR)/myapp.so
	$(CFBUILD) $(APPS_DIR)/myapp/MyApp.cfg -o $(OBJDIR)/MyApp
	@echo Invoking the synthesis system to create a pure dynamics image
	$(MAKE) lorien_open CONFIG="$(CORE_CONFIG) $(OBJDIR)/Timer $(OBJDIR)/MAC $(OBJDIR)/Random $(OBJDIR)/Radio $(OBJDIR)/Scheduler $(OBJDIR)/Output $(OBJDIR)/SerialPort $(OBJDIR)/SHT11 $(OBJDIR)/LED $(OBJDIR)/MyApp"
	$(OBJ_CPY) lorien.o -O ihex $(IHEXFILE)


And we're done! Again you can upload this system to two (or more!) different sensor nodes and see the LEDs light up as messages are received.


Other components

Lorien has an ever-expanding library of components from file propagation protocols to user button drivers. Each is included simply by compiling its component source file and configuration fragment and specifying the inclusion of its compiled configuration fragment in the CONFIG= list for your make rule. You can learn how to navigate/use the component collection here and browse the current component library [here].

We also recommend you check out how to create your own new interfaces here.

Personal tools
Namespaces
Variants
Actions
Navigation
Toolbox