Overview
The standard design for deploying applications in the Sedona Framework is to utilize the component model to separate the code from the application. In this architecture you have a clean boundary between the code packaged up as kits and the application packaged up as a tree of components. This model allows you to build applications simply by assembling components together, configuring their properties, and linking slots together to define control flow. This style of programming is especially amenable to graphical programming.
So when talking about a Sedona Framework application, we are really talking about a tree of components assembled together. An application is purely declarative, all the code is encapsulated in the kits. The application itself is stored as a file using one of two file formats:
- SAX: is a simple XML representation of the application that is easily generated and consumed by software tools
- SAB: is a compact binary representation of the application suitable for storage and execution on a Sedona Framework-enabled device
You can convert between the two file formats using sedonac.
Boot Strap
Applications are boot strapped using the following phases:
- Loaded: all components are loaded from SAB file into
memory and have their
Component.loadedcallback invoked. - Start: all components have their
Component.startcallback invoked. - Running: the application enters in main loop for execution (described next).
Execution
The Sedona Framework's execution model is based a single threaded main loop with a fixed scan rate:
- Recursively execute components (children first). For each component:
- Propagate incoming links
- Call the
Component.executevirtual method
- Give any remaining time in the scan cycle to services via
Service.work - If no services have remaining work, then relinquish CPU (via yield or sleep) until next cycle
Yield vs Sleep
Depending on the underlying execution environment, the App can either return control to the OS (exit the VM) or sleep until it's time to run again.
Preemptive multithreaded OS
These include Windows, Linux or QNX. When a thread calls the OS sleep primitive, other threads are given a chance to run. The VM may be swapped in and out several times during an execute cycle.
Main Loop
In this environment, the Sedona VM executes as the main loop and all other work is done @ ISR level. Once the App completes an execute cycle, it can delay by entering a busy-wait loop.
Cooperative tasking OS
In this environment, a task must return control to the scheduler to allow other tasks a chance to run. The sleep and busy-wait approaches won't work here since the VM will never exit, so the VM must support a clean exit and re-entry. This is accomplished by the yield mechanism described below.
Yield
The Sedona Framework supports yielding to provide a graceful exit (and subsequent reentry) of the VM, allowing the CPU to perform other operations.
Systems that require yield functionality must override the following functions on Platform
yieldRequired() - return true if the VM should exit after each App execute cycle
yield(long yieldTime) - indicates the VM will be exiting and requests to be resumed in yieldTime nanoseconds.
If a Platform returns true to yieldRequired(), then the VM will exit with
the error code ERR_YIELD after each pass execution loop. Before
exiting, yield(yieldTime) will be called indicating how long
the VM wishes to delay before re-entry.
Native code can resume the VM via vmResume(SedonaVM*)
If vmResume is not called before yieldTime expires, App overruns will occur.
Hibernation
When entering hibernation, the App exits and returns control to the bootstrap code. It is similar to yield but it is expected that the hibernation time will be much longer than a typical yield time. Hibernation is driven by application logic and most likely will not occur each App execute cycle, whereas yield must occur each cycle.
To enter the hibernation state, an application calls App.hibernate().
This will set a flag on App and when the current execution cycle is complete,
it will cause the App to exit with error code ERR_HIBERNATE.
This will gracefully unwind the call stack, returning control to the boot code.
The device's boot code should then put the device to sleep. It is a device dependent
issue to decide how it will wake up from hibernation. When the device does wake, it
should restart the VM with a call to vmResume(SedonaVM*). If your device
doesn't support hibernation, then you will need to simulate it using code such
as the following:
result = vmRun(&vm);
while (result == ERR_HIBERNATE)
{
printf("-- Simulated hibernate --\n");
result = vmResume(&vm);
}
If you are developing Sedona Framework components, applications, or drivers you should
keep hibernation in mind. Any software that might run on a battery powered
device needs to support hibernation cleanly. This means that function blocks
should assume the scan rate might have hibernation pauses. If you have
services that need to do something special when hibernating or waking, then
you will need to override the functions
Service.onHibernate() and Service.onUnhibernate().
Service.onHibernate() will be called prior to VM exit. Service.onUnhibernate() will be called after the VM is resumed and prior to the App execution loop starting back up.
Steady State
Most apps will be fully operational by the end of the first cycle. When hardware I/O is involved, however, an app may need to allow additional time for the hardware to warm up, or for complex logic results to propagate fully to all components. For this purpose, the Sedona Framework provides a "steady state" timing feature that should be used to protect the hardware from reading or writing transient values while the app is starting up.
The steady state feature consists of two pieces:
- timeToSteadyState:
This integer specifies the time delay between the start of app execution and the time at
which the app is considered to be in "steady state".
It is a config property of the App class, so it can be set in the app definition (.sax file) or
at runtime from a remote access tool (e.g. the App property sheet in Sedona Framework Workbench).
The default value is 0, meaning that the delay ends when the app enters the "Running" phase described above. The correct value for a given App will vary depending on the application logic and the specific hardware involved. - isSteadyState():
This is a method that returns
trueif the app has entered "steady state" mode, i.e. if the time delay defined bytimeToSteadyStatehas elapsed, andfalseotherwise. Once steady state mode is reached, this method will continue to returntrueuntil the app is restarted. Code that affects hardware on the native level should use this method to avoid reading or writing hardware values until steady state is reached.
Note: This feature is not used internally within the App.
It only affects behavior of components that use the isSteadyState() method.
It is the responsibility of each kit developer to call this method as needed to protect the
hardware.
App.timeToSteadyState applies only to the first time the VM is started. Once steady time has elapsed, hibernate/yield will not affect it. If App.hibernationResetsSteadyState is set to true, then the steady state flag will be reset each time device exits hibernation.
Links
Links are the mechanism used to define control flow in an app. Links are said to be from a given component's slot to another component's slot.
Currently only prop-to-prop links are supported. During link propagation, the from-property value is copied into the to-property. This mechanism is often used to link sensor inputs through control logic to actuator outputs.
Services
Services are special components that subclass from sys::Service. Services have three primary characteristics that set them apart from other types of components:
- Type Lookup: services are easy to lookup by
type via the
App.lookupServicemethod. - Background work: most components get a single callback to do work during a scan cycle. Services on the other hand get to handle background work during the time available at the end of a given scan cycle.
- Hibernation control: Each time App finishes an execute
cycle, it calls
Service.canHibernateon all services. If a service is not in a state where it can hibernate, it should return false. For example, if the service is waiting for a network reply, it would return false. Platform services for devices that never need to hibernate should always return false.
Services are often used to provide functionality to other components.
For example the UserService is used to lookup and
authenticate user accounts. Many services such as protocol drivers
also perform background work to service network messages.
SAX Files
The SAX format is structured as follows:
<sedonaApp>
<schema>
<kit name='sys'/>
...
</schema>
<app>
<comp name="play" id="1" type="sys::Folder">
<comp name="rampA" id="7" type="control::Ramp">
<prop name="min" val="20.00000"/>
<prop name="max" val="80.00000"/>
</comp>
...
</comp>
...
</app>
<links>
<link from="/play/rampA.out" to="/play/something.else"/>
...
</links>
</sedonaApp>
<sedonaApp> root element that contains:
- <schema>: contains <kit> elements
- <app>: contains <comp> elements
- <links>: contains <link> elements
<kit> defines the kits used by the application:
- name: required kit name
- checksum: optional kit checksum; if omitted the latest version of the kit is assumed
<comp> defines each component in the application:
- <comp>: nested elements map to nested components
- <prop>: property configuration for the component
- name: required name of component (limited in length)
- type: required qname of the component's type
- id: optional two byte identifier; if omitted an id is auto-generated
<prop> defines the property value of a component within a <comp> element. Supported attributes:
- name: required name of property
- val: required value. Buf properties should be a base64 encoded value (unless asStr in which case just use string value).
<link> element defines a link between two slots using the format of "/path/comp.slot":
- from: the from component and slot name
- to: the to component and slot name
SAB Files
While XML is a nice representation for tools to work with app files, XML is too big and difficult to work with in an embedded device. So we use the SAB format when we need a compact binary representation. Sedona Framework devices typically store their application as an SAB "file" in FLASH (although often it might just be location or sector of FLASH versus a real file system).
Although SAB is a very compact format for applications it must be used with a matching schema. For example, in order for a Sedona Framework device to load a given SAB file, its scode must have the exact same kits and checksums installed.
APIs
You can work with both SAX and SAB files in Java using the
sedona.offline APIs:
- OfflineApp.encodeAppXml
- OfflineApp.decodeAppXml
- OfflineApp.encodeAppBinary
- OfflineApp.decodeAppBinary