Skip to content

Soletta Project is a framework for making IoT devices. With Soletta Project's libraries developers can easily write software for devices that control actuators/sensors and communicate using standard technologies. It enables adding smartness even on the smallest edge devices.

License

kalyankondapally/soletta

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

35 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Soletta Project

Soletta Project is a framework for making IoT devices. With Soletta Project's libraries developers can easily write software for devices that control actuators/sensors and communicate using standard technologies. It enables adding smartness even on the smallest edge devices.

Portable and scalable, it abstracts details of hardware and OS, enabling developers to reuse their code and knowledge on different targets.

General Information

Soletta Project uses sol as C namespace, so macros start with SOL_ and functions, enumerations, structures and others start with sol_.

It uses a main loop to provide single threaded cooperative tasks (co-routines) triggered by UNIX file-descriptors, timers or idlers (runs whenever there is nothing else to do). The traditional main loop is based on Glib's GMainLoop, while some smaller OS have their own implementation, see --with-mainloop documentaion.

The build system is based on autotools (autoconf/automake). If you got the unreleased source code from GIT, you must run:

    ./autogen.sh

If you got the release tarballs then run:

    ./configure --prefix=/usr CFLAGS="-O2"

More options and variables are available with:

    ./configure --help

To compile use (from top directory)

    make all

or to be verbose and get all the commands being executed:

    make all V=1

To install run (sudo only required if installing to privileged directories):

    sudo make install

Note that to run applications that rely on modules to be installed (such as sol-io and conffile) one needs to either make install or configure as maintainer using:

    ./configure --enable-maintainer-mode

Then they will run from inside the build tree.

Debug

Soletta provides sol-log to provide meaningful critical, error, warning, informational or debug messages in a simple way. The following environment variables affect sol-log behavior:

export SOL_LOG_LEVEL="LEVEL"

    defines the maximum level to show messages. This affects all
    domains. The value of LEVEL can be an integer or the string
    alias such as CRITICAL, ERROR, WARNING, INFO, DEBUG, CRI, ERR,
    WRN, INF, DBG.

export SOL_LOG_LEVELS="domain1:level1,domain2:level2,...,domainN:levelN"

    the fine-grained version of $SOL_LOG_LEVEL that specifies
    different levels per domain. The specification is a key:value
    pair, the key being the domain name and the level being an
    integer or string alias.

export SOL_LOG_ABORT="LEVEL"

    if a message_level is less or equal to LEVEL, then the program
    will abort execution with abort(3). Say SOL_LOG_ABORT=ERROR,
    then it will abort on critical or error messages.
    Defaults to critical only.

export SOL_LOG_SHOW_COLORS=[0|1]

    will disable or enable the color output.
    Defaults to enabled if terminal supports it.


export SOL_LOG_SHOW_FILE=[0|1]

    will disable or enable the file name in output.
    Enabled by default.

export SOL_LOG_SHOW_FUNCTION=[0|1]

    will disable or enable the function name in output.
    Enabled by default.

export SOL_LOG_SHOW_LINE=[0|1]

    will disable or enable the line number in output.
    Enabled by default.

Note that at compile time some levels may be disabled by usage of SOL_LOG_LEVEL_MAXIMUM C-pre-processor macro, which may be set for soletta itself (internally) with:

    ./configure --with-maximum-internal-log-level=NUMBER

or by applications if they define that in some way. Then messages above that number will be compiled out and using $SOL_LOG_LEVEL or $SOL_LOG_LEVELS for those numbers won't have effect.

Libraries

common

Main loop, logging and access to platform details such as services and state.

Main loop will allow cooperative routines to run in a single thread, they can be started from a timeout (timer), an interruption handler, file descriptor monitor (POSIX-like systems) or when nothing else is running (idlers).

Logging allows different domains to be logged independently, making it is easy to debug your application or Soletta itself.

Platform allows checking the system state, if it's ready, still booting, degraded (something failed), going to shutdown and so on. It has the concept of services that can be started or stopped as well as monitored for a dynamic lifecycle management.

coap

Implementation of The Constrained Application Protocol (CoAP - RFC 7252). This network protocol is pretty simple and uses a HTTP-like message that is space-efficient over UDP.

flow

Implementation of Flow-Based Programming (FBP), allowing the programmer to express business logic as a directional graph of nodes connected to type-specific ports.

Main Loops

Main loops are responsible to deliver events and timers in a single thread by continuously poll and interleave registered functions. It abstracts every OS particularity and delivers common behavior to soletta applications.

For instance, while in Linux GPIO interruptions are handled at the kernel level and dispatched to userspace by means of file-descriptors that get notification via poll(2) syscall, in RIOT you get called from the interruption service routine (ISR) and you can mess with other interruptions while you execute your code. Soletta uses mainloop to remove those differences and in RIOT you'll get an internal ISR that queues GPIO events to be later delivered by the mainloop in application's thread.

Same for timers, on some platforms they are preemptive while in others they will be delayed. Soletta will never preempt user and all timers will be queued, thus they may be delayed.

Idlers are tasks to be executed when nothing else is running. These should be implemented by short-run functions as they will not be preempted in any way, thus effectively delaying the dispatch of timers and events. Often idlers are used to implement cooperative co-routines that do a single step of a complex computation and return "true" so they can run again. They can also be used by a worker thread to schedule a function to be run from the main thread.

If some software have real-time requirements it's better to consider writing native code to be as efficient as possible, usually as kernel drivers or separate userspace service. This should be the case when one lacks hardware PWM, in Linux it should be a kernel driver "soft pwm" while in RIOT it would be a kernel thread.

While most of Soletta is not thread-safe, main loop functions sol_timeout_add(), sol_timeout_del(), sol_idle_add(), sol_idle_del(), sol_fd_add() and sol_fd_del() are thread-safe and can be used from worker threads.

GLib (--with-mainloop=glib)

The well known GMainLoop is used by soletta so GLib-based frameworks can be easily integrated, things like social network services, multimedia systems and so on.

When using GLib as platform you should also use their debug infrastructure in addition to our common part.

See https://developer.gnome.org/glib/stable/glib-running.html for in-depth information, the summary of environment variables to use:

    export G_DEBUG="all" # fatal-warnings, gc-friendly...
    export G_SLICE="all" # always-malloc, debug-blocks
    export G_MESSAGES_DEBUG="all" # print all debug

    ./configure CFLAGS="-O0 -ggdb3" # disable optimizations

To debug binaries that are not yet installed (in-build tree), remember that they are actually libtool scripts and you need to call libtool command to get them right:

    libtool --mode=execute valgrind ./my-binary
    libtool --mode=execute gdb ./my-binary
POSIX (--with-mainloop=posix)

If GLib is too big then you can use a simpler implementation based solely on POSIX syscalls (poll/ppoll). As it's fully implemented in soletta there is no extra variables to debug it.

It is a nice match for --with-platform=micro-linux

Platforms

Platforms is about target states and services.

Target states is what defines the current state such as initializing (booting), degraded (running with some kind of problems), maintenance (reset to factory defaults or rescue mode), stopping (shutting down) or just running, that is the common state.

One can trigger a state change by setting a new target such as "emergency", "reboot" or "poweroff", then the platform will be put in such state. Some well-defined names exists, but it should be extensible per-platform.

Services can be started or stopped dynamically and they will notify monitors about their state changes. They are platform-specific and depends on underlying platform setup.

Systemd (--with-platform=systemd)

This uses systemd as base. Whenever a target is set or a service is started it will call systemd. The D-Bus events from systemd will be used to notify monitors.

Thus both targets and services are implemented according to systemd using units in standard locations such as /usr/lib/systemd/system and /etc/systemd/system.

Micro-Linux (--with-platform=micro-linux)

Micro-Linux implementation allows your Soletta binary to be used as PID1, it will do required initialization and will handle services as modules, so you can have a very small system that works.

Targets will be handled by calling Linux's reboot(2) or executing binaries with well known target name such as "/sbin/rescue" or "/sbin/emergency".

Services are handled as modules, they can be compiled into Soletta using --with-linux-micro-MODULE=builtin or as dynamically loadable modules. They have a simple API and is extensible by third party, so if Soletta lacks a service it easy to add that without patch Soletta itself.

Soletta provides a init.d/rc.d compatibility service called rc-d.so, the usage is to run scripts from /etc/init.d or /etc/rc.d with standard parameters "start", "stop", "restart" and "status". To enable a service all one needs to do is symlink the service name to rc-d.so, as an example to enable /etc/init.d/myservice to be used by Micro-Linux:

    ln -s /usr/lib/soletta/modules/linux-micro/rc-d.so \
          /usr/lib/soletta/modules/linux-micro/myservice.so

A set of services are started automatically by Soletta in order to do initialization, these are listed in /usr/lib/soletta/modules/linux-micro/initial-services.

Flow Based Programming

Flow-Based Programming (FBP) allows the programmer to express business logic as a directional graph of nodes connected to type-specific ports.

The connected node ports will exchange information packets (IP) that will be used to run the flow. The packet can be either empty, used just to trigger an action, or carry a value such as booleans, integers, floats and even complex data types such as blobs. Ports can decide if the packets they produce will be queued or replaced if they were not consumed by target ports when a new packet is to be produced.

For those used to traditional Object Oriented Programming (OOP) it's like using the observer pattern on source nodes, calling the associated data getter which is then forwarded to target nodes by calling their setter. However this is done by the core in an efficient and safe way as no memory leaks, type mismatch or invalid memory access can happen.

Node instances should be isolated and independent thus they can be considered a blackbox that is only defined by their input and output ports. They do not share state so it's easy to replace a node with another without affecting the flow. For instance if a flow used a gpi/reader as a source one could easily replace that by a bluetooth presence sensor only by replacing that node given that they all have the same ports.

Nodes can be created from builtin or third party types distributed as shared objects (.so) stored at /usr/lib/soletta/modules/flow/. Builtin nodes include boolean logic, converters, math, GPIO, Analog I/O, PWM, timers and even Soletta's platform and services. External modules usually covers board or operating system specific support such as udev or evdev for Linux, calamari support for Intel's MinnowBoard extension shield. Node descriptons can be found in JSON files stored at /usr/share/soletta/flow/descriptions/. One can query the node types database using the Python tool sol-flow-node-type-find.py, as an example say we want to query the nodes with at least the same ports as gpio/writer (a single boolean OUT port):

    sol-flow-node-type-find.py --format=simple \
            --similar-ports=gpio/writer

Flow can be created directly in C using low-level primitives from sol-flow.h or using a higher-level API in sol-flow-builder.h. An alternative is to write the flow in a domain-specific language "FBP" that is easier to express. As an example imagine one wants to blink an LED linked to GPIO pin 123 every 200ms:

    MyTimer(timer:interval=200) OUT -> IN MyToggler(boolean/toggle)
    MyToggler OUT -> IN MyLED(gpio/writer:pin=123)

This can be ran directly with sol-fbp-runner or generate C code using sol-fbp-generator. The syntax is pretty simple:

    Instance1(Type1) OUT_PORT_NAME -> IN_PORT_NAME Instance2(Type2)
    Instance1 OTHER_OUT_PORT -> IN_PORT_NAME Instance3(Type3)

The Type1 and Type2 should only be used once when first defining the Instance1 or Instance2. They can be just the type name or give constructor options by appending ":Option1=Value1,Option2=Value2". For integer options one can specify only the value or the complete information set by using "SomeIntOption=min:0|max:100|step:2|val:50"

One can link multiple segments at once:

    a out_port -> in_port b out_port -> in_port c

It is much simpler than a program using the high-level C API, even if we're omitting error checking, as we can see below. For the low-level API see the output of sol-fbp-generator.

        #include "sol-mainloop.h"
        #include "sol-flow-builder.h"
        #include "sol-flow-node-types.h"

        int main(void) {
            struct sol_flow_node_type *node_type;
            struct sol_flow_builder *builder;
            struct sol_flow_node *flow;
            struct sol_flow_node_type_timer_options timer_opts =
                SOL_FLOW_NODE_TYPE_TIMER_OPTIONS_DEFAULTS(.interval.val=200);
            struct sol_flow_node_type_gpio_writer_options gpio_opts =
                SOL_FLOW_NODE_TYPE_GPIO_WRITER_OPTIONS_DEFAULTS(.pin.val=123);

            sol_init();

            builder = sol_flow_builder_new();
            sol_flow_builder_add_node(builder, "MyTimer",
                                     SOL_FLOW_NODE_TYPE_TIMER,
                                     &timer_opts.base);
            sol_flow_builder_add_node(builder, "MyToggler",
                                     SOL_FLOW_NODE_TYPE_BOOLEAN_TOGGLE,
                                     NULL);
            sol_flow_builder_add_node(builder, "MyLED",
                                     SOL_FLOW_NODE_TYPE_GPIO_WRITER,
                                     &gpio_opts.base);

            sol_flow_builder_connect(builder,
                                    "MyTimer", "OUT", "MyToggler", "IN");
            sol_flow_builder_connect(builder,
                                    "MyToggler", "OUT", "MyLED", "IN");


            node_type = sol_flow_builder_get_node_type(builder);
            flow = sol_flow_node_new(NULL, "simple", node_type, NULL);

            sol_run();

            sol_flow_node_del(flow);
            sol_flow_builder_del(builder);
            sol_shutdown();
            return 0;
        }

Build Status

Coverity Scan Build Status

About

Soletta Project is a framework for making IoT devices. With Soletta Project's libraries developers can easily write software for devices that control actuators/sensors and communicate using standard technologies. It enables adding smartness even on the smallest edge devices.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • C 89.5%
  • Python 9.8%
  • Other 0.7%