Howto:Getting started with Glass Cockpit Avionics Development
This article is a stub. You can help the wiki by expanding it. |
The FlightGear forum has a subforum related to: FlightGear Scripting (Nasal) |
The FlightGear forum has a subforum related to: 2D drawing via Canvas |
Note This article is targeted at people wanting to develop/extend MFD-based features like a PFD, ND, EFB, CDU for FlightGear without having to touch any C++ code, and without having to rebuild FlightGear from source. The article intends to cover the fundamentals to help create a working prototype. The article is focused on describing techniques for accomplishing a number of important design goals, namely:
For charting/mapping purposes, there's a dedicated framework called MapStructure. People facing performance issues or wanting to debug/troubleshoot Canvas based efforts, will want to check out Canvas Troubleshooting. People interested in extending the underlying C++ code, are encouraged to check out Canvas Development. |
For scripted PFD/NDs or any other other modern avionics (MFDs), there's a dedicated subsystem in FlightGear, called Canvas. This is a fully scriptable, 100% hardware-accelerated off-screen rendering target using FBOs for RTT (render-to-texture) to satisfy arbitrary 2D drawing needs (instruments, avionics, HUDs, widgets, GUIs etc).
Canvas texture are dynamically updated on demand and can be placed in the simulator using virtual placements, such as:
- aircraft (interior/exterior, e.g. cockpit/livery)
- scenery
- GUI dialogs/windows
- HUDs
People interested in modern glass cockpit will probably want to read up on FlightGear scripting using the built-in scripting language (called Nasal ) as well as the Canvas FlightGear 2D rendering framework, which implements a fully hardware-accelerated scenegraph (using OSG/OpenSceneGraph-based) on top of the FlightGear Property Tree, exposed to scripting space using the Nasal/CppBind framework.
Nasal works very much like JavaScript in a browser - it can be used to extend/create simulator features without touching the c++ code or rebuilding the binary using the built-in Nasal Console or the Interactive Nasal Console for rapid prototyping:
2D rendering is accomplished through a hardware-accelerated 2D rendering API implemented on top of our property tree called "Canvas".
The 2D rendering API is a wrapper on top of ShivaVG (OpenVG, vector graphics), OSG (OpenSceneGraph) primitives and a handful of rendering primitives (raster images, groups, maps, paths).
Nasal has scriping space bindings (wrappers) for those APIs. This is the way glass cockpit displays can be fully implemented in scripting space these days. Even without a lot of coding.
Nasal, in conjunction with Canvas, can be used to create modern avionics fairly easily, including PFDs and NDs but also standalone GPS units or any other MFD — for instance, the following images are screen shots showing instruments and GUI dialogs that were solely created in scripting space using Nasal scripting and Canvas for 2D drawing:
This is an adapted version of the Garmin GPSMap 196 that is currently being developed by F-JJTH. Here, the whole instrument is entirely set up in XML space without using any Nasal, including buttons/event handling, but also the embedded canvas region that serves as the 'screen'. The idea is to allow arbitrary MFDs to be specified in an aircraft-agnostic fashion, including displays like a PFD, NavDisplay or EFB. For details, please see Canvas Glass Cockpit Efforts
F-JJTH has updated the SVG file implementing the panel page for the Garmin GPSMap 196
Screen shot showing Gijs' CDU prototype [1]
Garmin GNS530 prototype using Nasal & Canvas
the most complex and most impressive Canvas-based MFD instrument we have in FlightGear is the Avidyne Entegra R9
To get started with MFD development, you'll want to start reading up on Nasal coding - for example using the following articles as entry points:
- Howto:Understand Namespaces and Methods
- Howto:Start using vectors and hashes in Nasal
- Object oriented programming in Nasal
- Object Oriented Programming with Nasal
Once you understand basic Nasal scripting and OOP (object oriented programming), you'll want to look at the Nasal/Canvas tutorials and APIs:
This should provide a pretty good foundation for anybody wanting to contribute to Nasal/Canvas based efforts like the PFD, ND or other MFD efforts.
However, people wanting to get involved in these efforts don't necessarily need to know how to write code though - there are many other ways to help, i.e. by creating 3D models for instruments, or by creating vector graphics using Inkscape. The most recent effort involving a generic framework for glass cockpit avionics is Project Farmin. I'd suggest to get in touch via the Canvas forum.
Maps & Charts
For anything involving dynamic maps and charts (including moving maps), you don't need to create a mapping system from scratch.
Thanks to MapStructure there already is a front-end agnostic scripting space framework in place that can be used for creating pretty much arbitrary maps and charts that are fully interactive, including support for resource management (timers, listeners, caching).
In addition, the MapStructure framework cannot only be used for creating custom avionics, but also for creating GUI dialogs - the back-end is implemented in a generic fashion, so that MapStructure consists primarily of a number of layers that may contain symbols using static or dynamic positions. Symbols can also be both, static and dynamic (i.e. animated).
The following code snippet is taken from Canvas Snippets and illustrates how a simple moving map can be implemented using ~20-30 lines of code:
var TestMap = root.createChild("map");
TestMap.setController("Aircraft position");
TestMap.setRange(25);
TestMap.setTranslation( myCanvas.get("view[0]")/2,
myCanvas.get("view[1]")/2
);
var r = func(name,vis=1,zindex=nil) return caller(0)[0];
foreach(var type; [r('APT'), r('VOR') ] )
TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name, visible: type.vis, priority: type.zindex,);
The MapStructure framework already comes with a sizable library of existing layers that can be easily styled and customized for all kinds of different purposes.
For a list of available MapStructure layers, please refer to: Canvas MapStructure Layers.
In addition, it is is fairly easy to create new layers from scratch. And MapStructure-based maps can also be easily reused in legacy GUI/PUI dialogs, too:
User Interface (GUI)
Introduce custom Canvas GUI widgets and styles
on the second level of abstraction, nearly every widget provides methods to change its state. eg. Button.setChecked/setDown/toggle or ScrollArea.scrollBy.
— TheTom (Mon Jun 30). Re: Widget.nas MVC & DefaultStyle.nas considerations.
(powered by Instant-Cquotes) |
Looking at examples of some of our more sophisticated MFDs -like the Avidyne Entegra R9 or even just the Garmin GPSMap196- that may sooner or later benefit from being able to use native Canvas GUI widgets (buttons, checkbox, labels etc) with a custom style, it seems that we should strive to encapsulate event handling by not hard-coding things addEventListener() for mouse events or keyboard events at the Widget.nas level, because such devices may typically have their own "virtual" input means, such as a virtual keypad, or buttons/rockers to navigate between controls.
— Hooray (Sun Jun 29). Widget.nas MVC & DefaultStyle.nas considerations.
(powered by Instant-Cquotes) |
The Avidyne Entegra R9 contains several examples where this kind of generalization would mean that the instrument could directly use a customized Canvas.Widget instead of their own hand-written widgets: gitorious/extra500/extra500/HEAD/Nasal/AvidyneEntegra9/widget.nas - but obviously their widgets must remain functional even without the implicit assumption that widgets will always be controlled via keyboard/mouse.
— Hooray (Sun Jun 29). Widget.nas MVC & DefaultStyle.nas considerations.
(powered by Instant-Cquotes) |
Regarding the UI parts you mentioned, those should probably be based on the existing ScrollList widget in $FG_ROOT/Nasal/canvas/gui/widgets
|
it is already using MVC, the styling part is handled by $FG_ROOT/Nasal/canvas/gui/styles/DefaultStyle.nas, which basically determines appearance of widgets (size, fonts, images etc).
|
Scaleable Vector Graphics
Caution Depending on your OS, you may be unaware of any parse errors returned by parsesvg - parsesvg()/svg.nas is a relatively simple Nasal module that only supports a tiny subset of SVG by mapping it to the corresponding Canvas/OpenVG primitives - it's a clever piece of code actually, but obviously there's a plethora of SVG features that are not supported by this Nasal module - thus, you should watch out for any errors/warnings, and if necessary revisit your SVG file, i.e. by converting some constructs and/or exporting the SVG to a simplified version. You will however only be able to treat a SVG file as a canvas group if the file could be processed successfully. |
Depending on what you have in mind, we have a few howtos/tutorials on the wiki, including quite a few code snippets/examples in $FG_ROOT
You'd basically create your MFD via Inkscape, as one (or more) SVG file/s and then animate SVG elements via Nasal callbacks.
Canvas based gauges (MFDs) will typically be SVG files (created via Inkscape) and animated via Nasal timers/listeners.
To get started we need a SVG file representing the device (screen,keypad/buttons) - each functional element should be addressable by having an SVG id. Textures can be implemented using png images.
Then, we would assign event handlers for each MFD key.
$FG_ROOT/Nasal/canvas/svg.nas is our existing example on populating a canvas procedurally by parsing an XML file.
Basically, it looks for supported SVG primitives, and turns those into Canvas/OpenVG primitives by setting properties.
The svg parser (svg.nas) is hand-written and can be found in $FG_ROOT/Nasal/canvas/svg.nas, it can be easily extended to support additional primitives/tags, you just need to map them to the corresponding OpenVG equivalents, I think supporting shapes would make sense and shouldn't be too difficult.
Internally, canvas will turn all SVG commands into OpenVG primitives and then maintain a raster image which is rendered each frame, but usually only updated when absolutely necessary (modified/animated).
think about what a "layer" ultimately is: it is an entity to which you can render something and that you can easily show/hide, which kinda matches the definition of a canvas group - which is why ND/MapStructure layers are internally "just" Canvas groups - to see for yourself, use the property browser and inspect any ND/MapStructure tree.
You can directly load SVG files via parsesvg, and then use the findElemetbyID() call to look up an element and animate/transform it - but watch the console, our svg parser is still fairly basic and may be missing some features
you can just get a handle to the element that you're interested in via findElementByName(), and then write it to a group via parsesvg(), implement depth-ordering via the z-index property - if something doesn't work as expected, you can also open svg.nas and extend the parser to support your specific use-case/XML tag.
using z-index layering for your groups - a canvas is a "layer" basically.
But in this case I would suggest editing svg.nas and extend it support raster images via the <image> tag so that this will be automatically set up.
check the console for errors - our SVG parser is hand-written and only supports a subset of SVG instructions, so may need to be extended, or simply use supported inkscape primitives instead.
The SVG standard explicitly suppots referencing other images (including raster images and SVGs) via the "image" tag - even recursively, so I'd be surprised if inkscape didn't support that. However, our svg parser doesn't currently support this IIRC - but it's "just" Nasal code, so would probably just require ~10-15 lines of code to add support for the image/object tags. |
Animation Handling
Important Concepts
For anything involving MFDs (glass cockpit/EFIS-type avionics), you'll usually want to use Inkscape SVG files that are animated using Nasal/Canvas. For that, you should understand a few Nasal basics, especially object-oriented programming using Nasal (vectors, hashes, classes, objects, inheritance):
If you're just getting started, you may also want to check out the advice on creating a simple Nasal framework: Howto:Coding a simple Nasal Framework
Next, you need to understand how to load an Inkscape SVG file onto a Canvas, for prototyping purposes it make sense to use a GUI dialog during development (which can later on be trivially turned into an actual instrument) :
Canvas Snippets#Creating a Canvas GUI Window
To learn more about adding raster images (e.g. as background), see: Canvas Snippets#Adding Raster Images
Howto:Using raster images and nested canvases
To see how Inkscape SVG files can be loaded, please refer to the examples/tutorial at: Howto:Use SVG inside a Canvas
Next, there's also the possibility to reuse FlightGear's existing gauges and render/animate those using Canvas: Howto:Parsing 2D Instruments using the Canvas
The reference for Canvas/Image handling (raster images) can be found at: Canvas Image