diff options
-rw-r--r-- | README.md | 422 | ||||
-rw-r--r-- | book.json | 104 | ||||
-rw-r--r-- | docs/README.md | 19 | ||||
-rw-r--r-- | docs/SUMMARY.md | 6 | ||||
-rw-r--r-- | docs/Usage.md | 53 | ||||
-rw-r--r-- | docs/_layouts/ebook/page.html | 36 | ||||
-rw-r--r-- | docs/_layouts/ebook/pdf_footer.html | 13 | ||||
-rw-r--r-- | docs/_layouts/ebook/pdf_header.html | 13 | ||||
-rw-r--r-- | docs/_layouts/ebook/summary.html | 58 | ||||
-rw-r--r-- | docs/_layouts/layout.html | 28 | ||||
-rw-r--r-- | docs/configSample.md | 98 | ||||
-rw-r--r-- | docs/controller.md | 28 | ||||
-rw-r--r-- | docs/controllerConfig.md | 241 | ||||
-rw-r--r-- | docs/resources/cover.svg | 210 | ||||
-rw-r--r-- | docs/resources/ebook.css | 402 | ||||
-rwxr-xr-x | docs/resources/make_cover.sh | 27 | ||||
-rwxr-xr-x | gendocs.sh | 90 |
17 files changed, 1431 insertions, 417 deletions
@@ -1,423 +1,11 @@ -# Controller +# App Controller -* Object: Generic Controller to handle Policy,Small Business Logic, Glue in between components, ... -* Status: Release Candidate -* Author: Fulup Ar Foll fulup@iot.bzh -* Date : May-2018 +Here you will find a guide on how to install, configure and use the controller. -## Features - -* Create a controller application from a JSON config file -* Each control (eg: navigation, multimedia, ...) is a suite of actions. When all actions succeed control is granted, if one fails control access is denied. -* Actions can either be: - * Invocation of an other binding API, either internal or external (eg: a policy service, Alsa UCM, ...) - * C routines from a user provided plugin (eg: policy routine, proprietary code, ...) - * Lua script function. Lua provides access to every AGL appfw functionality and can be extended by plugins written in C. - -## Installation - -* Controller can easily be included as a git submodule in any AGL service or application binder. -* Dependencies: the only dependencies are AGL application framework (https://gerrit.automotivelinux.org/gerrit/p/src/app-framework-binder.git) and app-afb-helpers-submodule (https://gerrit.automotivelinux.org/gerrit/p/apps/app-afb-helpers-submodule.git). -* Controller relies on Lua-5.3, when not needed Lua might be removed at compilation time. - -## Monitoring - -* The default test HTML page expect the monitoring HTML page to be accessible under /monitoring with the --monitoring option. -* The monitoring HTML pages are installed with the app framework binder in a subdirectory called monitoring. -* You can add other HTML pages with the alias options e.g. afb-daemon --port=1234 --monitoring --alias=/path1/to/htmlpages:/path2/to/htmlpages --ldpaths=. --workdir=. --roothttp=../htdocs -* The monitoring is accessible at http://localhost:1234/monitoring. - -## Controller binding configuration - -By default the controller searches for a config filename with the same 'middlename' as the daemon process. As an example if your process name is afb-daemon then middle name is 'daemon'. In addition, if your process name is afb-daemon-audio the middle name is also 'daemon'. Moreover the prefix is chosen when you call the [CtlConfigSearch](<#4)_Do_controller_config_parsing_at_binding_pre-init>) function, see below: - -```bash -CtlConfigSearch(AFB_ApiT apiHandle, const char *dirList, const char *prefix) -``` - -```bash -# Middlename is taken from process middlename. -(prefix-)middlename*.json -``` - -You may overload the config search path with environment variables - -* **CONTROL_CONFIG_PATH**: change default reserch path for configuration. You may provide multiple directories separated by ':'. -* **CONTROL_LUA_PATH**: same as CONTROL_CONFIG_PATH but for Lua script files. - -Example: to load a config named '(prefix-)myconfig-test.json' do - -```bash -afb-daemon --name myconfig --verbose ...' -``` - -The configuration is loaded dynamically during startup time. The controller scans **CONTROL_CONFIG_PATH** for a file corresponding to the pattern -"(prefix-)bindermiddlename*.json". The first file found in the path is loaded, -any other file corresponding to the same path is ignored and only generates a warning. - -Each block in the configuration file is defined with - -* **uid**: mandatory, it is used either for debugging or as input for the action (eg: signal name, control name, ...) -* **info**: optional, it is used for documentation purpose only - -> **Note**: by default the controller config search path is defined at compilation time, but the path might be overloaded with the **CONTROL_CONFIG_PATH** -> environment variable. - -### Config is organised in sections - -* **metadata**: describes the configuration -* **plugins or resources**: defines the set of functions provided by the plugins allowing to load additionnal resources (compiled C or lua) -* **onload**: a collection of actions meant to be executed at startup time -* **control**: sets the controls with a collection of actions, in dynamic api it could also specify the verbs of the api -* **event**: a collection of actions meant to be executed when receiving a given signal -* **personnal sections**: personnal section - -Callbacks to parse sections are documented in [Declare your controller config section in your binding](<#3_Declare_your_controller_config_section_in_your_binding>) section. You can use the callback defined in controller or define your own callback. - -### Metadata - -As today matadata is only used for documentation purpose. - -* **uid**: mandatory -* **version**: mandatory -* **api**: mandatory -* **info**: optional -* **require**: optional -* **author**: optional -* **date**: optional - -### OnLoad section - -Onload section defines startup time configuration. Onload may provide multiple initialisation -profiles, each with a different uid. - -You can define the following keys or arrays of the following keys: - -* **uid**: mandatory. -* **info**: optional -* **action**: mandatory -* **args**: optionnal - -### Control section - -Control section defines a list of controls that are accessible. - -You can define the following keys or arrays of the following keys, moreover -this section could be verb api: - -* **uid**: mandatory -* **info**: optional -* **action**: the list of actions is mandatory - -### Event section - -Event section defines a list of actions to be executed on event reception. Event can do -anything a controller can (change state, send back signal, ...) -eg: if a controller subscribes to vehicle speed, then speed-event may adjust -master-volume to speed. - -You can define the following keys or arrays of the following keys, moreover you can define an event from an another API with the following syntax "API/event". - -* **uid**: mandatory -* **info**: optional -* **action**: the list of actions is mandatory - -### Plugin section - -Plugin section defines plugins used with this controller. A plugin is a C/C++ program meant to -execute some tasks after an event or on demand. This easily extends intrinsec -binding logic for ad-hoc needs. - -You can define the following keys or arrays of the following keys: - -* **uid**: mandatory -* **info**: optionnal -* **spath**: optionnal, semicolon separated paths where to find the plugin. This could be a compiled shared library or LUA scripts. Could be specified using CONTROL_PLUGIN_PATH environment variable also. -* **libs**: mandatory, Plugin file or LUA scripts to load -* **lua**: optionnal, C functions that could be called from a LUA script - -### Personnal sections - -* **uid**: mandatory -* **info**: optionnal -* **action**: mandatory -* **any keys wanted**: optionnal - -You can define your own sections and add your own callbacks into the -CtlSectionT structure, see -[Declare your controller config section in your binding](<#3_Declare_your_controller_config_section_in_your_binding>) section. - -### Actions Categories - -Controller supports three categories of actions. Each action returns a status -where 0=success and 1=failure. - -* **AppFw API** provides a generic model to request other bindings. Requested bindings can be local (eg: ALSA/UCM) or external (eg: vehicle signalling). - * `"action": "api://API_NAME#verb_name"` -* C-API, when defined in the onload section, the plugin may provide C native API with `CTLP-CAPI(apiname, uid, args, query, context)`. Plugin may also create Lua command with `CTLP-LUA2C(LuaFuncName, uid, args, query, context)`. Where `args`+`query` are JSON-C object and context is the returned value from `CTLP_ONLOAD` function. Any missing value is set to NULL. - * `"action": "plugin://plugin_name#function_name"` -* Lua-API, when compiled with Lua option, the controller supports action defined directly in Lua script. During "*onload*" phase, the controller searches in `CONTROL_LUA_PATH` file with pattern "(prefix-)bindermiddlename*.lua". Any file corresponding to this pattern is automatically loaded. Any function defined in those Lua scripts can be called through a controller action. Lua functions receive three parameters (uid, args, query). - * `"action": "lua://plugin_name#function_name"` - -You also can add the **privileges** property that handles AGL permission -needed to be able to call this action. - -> **Note**: Lua added functions are systematically prefixed. AGL standard AppFw -functions are prefixed with AGL: (eg: AFB:notice(), AFB:success(), ...). -> User Lua functions added through the plugin and CTLP_LUA2C are prefixed with -the plugin uid or the one you defined in your config (eg: MyPlug:HelloWorld1). - -### Available Application Framework Commands - -Each Lua AppFw commands should be prefixed by AFB: - -* `AFB:notice ("format", arg1,... argn)` directly printed LUA tables as json string with '%s'. - `AFB:error`, `AFB:warning`, `AFB:info`, `AFB:debug` work on the same model. Printed messages are limited to 512 characters. - -* `AFB:service ('API', 'VERB', {query}, "Lua_Callback_Name", {context})` is an asynchronous call to another binding. When empty, query/context should be set to '{}' - and not to 'nil'. When 'nil', Lua does not send 'NULL' value but removes arguments to calling stack. WARNING:"Callback" - is the name of the callback as a string and not a pointer to the callback. (If someone as a solution to fix this, please - let me known). Callback is call as LUA "function Alsa_Get_Hal_CB (error, result, context)" where: - * error is a Boolean - * result is the full answer from AppFw (do not forget to extract the response) - * context is a copy of the Lua table pass as an argument (warning it's a copy not a pointer to original table) - -* `error,result=AFB:servsync('API', 'VERB', {query})` is saved as previous but for synchronous call. Note that Lua accepts multiple - returns. AFB:servsync returns both the error message and the response as a Lua table. Like for AFB:service, the user should not - forget to extract response from result. - -* `AFB:success(request, response)` is the success request. request is the opaque handle passes when Lua is called from (api="control", verb="docall"). - Response is a Lua table that will be returned to the client. - -* `AFB:fail(request, response)` is the same as for success. Note that LUA generates automatically the error code from Lua function name. - The response is transformed into a json string before being returned to the client. - -* `EventHandle=AFB:evtmake("MyEventName")` creates an event and returns the handle as an opaque handle. Note that due to a limitation - of json_object, this opaque handle cannot be passed as an argument in a callback context. - -* `AFB:subscribe(request, MyEventHandle)` subscribes a given client to a previously created event. - -* `AFB:evtpush (MyEventHandle, MyEventData)` pushes an event to every subscribed client. MyEventData is a Lua table that will be - sent as a json object to the corresponding clients. - -* `timerHandle=AFB:timerset (MyTimer, "Timer_Test_CB", context)` initialises a timer from MyTimer Lua table. This table should contains 3 elements: - MyTimer={[l"abel"]="MyTimerName", ["delay"]=timeoutInMs, ["count"]=nBOfCycles}. Note that if count==0 then timer is cycled - infinitely. Context is a standard Lua table. This function returns an opaque handle to be used to further control the timer. - -* `AFB:timerclear(timerHandle)` kills an existing timer. Returns an error when timer does not exit. - -* `MyTimer=AFB:timerget(timerHandle)` returns uid, delay and count of an active timer. Returns an error when timerHandle does not - point on an active timer. - -* `AFB:GetEventLoop()` retrieves the common systemd's event loop of AFB. - -* `AFB:RootDirGetFD()` gets the root directory file descriptor. This file descriptor can be used with functions 'openat', 'fstatat', ... - -> **Note**: Except for functions call during binding initialisation period. Lua calls are protected and should returned clean messages -> even when they are improperly used. If you find bug please report. - -### Adding Lua command from User Plugin - -User Plugin is optional and may provide either native C-action accessible directly from controller actions as defined in -JSON config file, or alternatively may provide a set of Lua commands usable inside any script (onload, control,event). A simple -plugin that provides both notice C API and Lua commands is provided as example (see ctl-plugin-sample.c). Technically a -plugin is a simple sharelibrary and any code fitting in sharelib might be used as a plugin. Developer should nevertheless -not forget that except when no-concurrency flag was at binding construction time, any binding should to be thread safe. - -A plugin must be declared with `CTLP_REGISTER("MyCtlSamplePlugin")`. This entry point defines a special structure that is checked -at plugin load time by the controller. Then you have an optional init routine declare with `CTLP_ONLOAD(plugin, handle)`. - The init routine may create -a plugin context that is later presented to every plugin API, this for both LUA and native C ones. Then each: - -* C API declare with `CTLP_CAPI(MyCFunction, source, argsJ, queryJ) {your code}`. Where: - * **MyFunction** is your function - * **source** is the structure config - * **argsJ** a json_object containing the argument attaches to this control in JSON config file - * **queryJ** a json_object - -* Lua API declare with `CTLP_LUA2C(MyLuaCFunction, source, argsJ, responseJ) {your code}`. Where - * **MyLuaCFunction** is both the name of your C function and Lua command - * **source** is the structure config - * **argsJ** the arguments passed this time from Lua script and not from Json config file. - * **responseJ** if success the argument is passed into the request. - -> **Warning**: Lua samples use with controller enforce strict mode. As a result every variable should be declared either as -> local or as global. Unfortunately "luac" is not smart enough to handle strict mode at build time and errors only appear -> at run time. Because of this strict mode every global variables (which include functions) should be prefixed by '_'. -> Note that LUA requires an initialisation value for every variables and declaring something like "local myvar" will not -> allocate "myvar". - -### Debugging Facilities - -Controller Lua scripts are checked for syntax from CMAKE template with Luac. When needed to go further, a developer API should be allowed to -execute directly Lua commands within the controller context from Rest/Ws (api=control, verb=lua_doscript). DoScript API takes two -other optional arguments func=xxxx where xxxx is the function to execute within Lua script and args, a JSON object to provide -input parameters. When funcname is not given by default, the controller tries to execute middle filename doscript-xxxx-????.lua. - -When executed from the controller, Lua script may use any AppFw Apis as well as any L2C user defines commands in plugin. - -### Running as Standalone Controller - -The controller is a standard binding. It can be started with the following command: +To generate the doc enter: ```bash -afb-daemon --name=yourname --port=1234 --workdir=. --roothttp=./htdocs --tracereq=common --token= --verbose --binding=pathtoyourbinding.so --monitoring -``` - -Afb-Daemon only loads controller bindings without searching for the other -binding. In this case, the controller binding will search for a configuration file -name '(prefix-)bindermiddlename*.json'. This model can be used to implement for testing -purpose or simply to act as the glue between a UI and other binder/services. - -## Usage - -### 1) Add app-controller-submodule as a submodule to include in your project - -```bash -git submodule add https://gerrit.automotivelinux.org/gerrit/apps/app-controller-submodule -``` - -### 2) Add app-controller-submodule as a static library to your binding - -```cmake - # Library dependencies (include updates automatically) - TARGET_LINK_LIBRARIES(${TARGET_NAME} - ctl-utilities - ... other dependencies .... - ) -``` - -### 3) Declare your controller config section in your binding - -```C -// CtlSectionT syntax: -// key: "section name in config file" -// loadCB: callback to process section -// handle: a void* pass to callback when processing section -static CtlSectionT ctlSections[]= { - {.key="plugins" , .loadCB= PluginConfig, .handle= &halCallbacks}, - {.key="onload" , .loadCB= OnloadConfig}, - {.key="halmap" , .loadCB= MapConfigLoad}, - {.key=NULL} -}; - -``` - -### 4) Do the controller config parsing at binding pre-init - -```C - // check if config file exist - const char *dirList= getenv("CTL_CONFIG_PATH"); - if (!dirList) dirList=CONTROL_CONFIG_PATH; - - const char *configPath = CtlConfigSearch(apiHandle, dirList, "prefix"); - if(!confiPath) return -1; - - ctlConfig = CtlConfigLoad(dirList, ctlSections); - if (!ctlConfig) return -1; +./gendocs.sh pdf ``` -### 5) Execute the controller config during binding init - -```C - int err = CtlConfigExec (ctlConfig); -``` - -## Config Sample - -Here after a simple configuration sample. - -```json -{ - "$schema": "http://iot.bzh/download/public/schema/json/ctl-schema.json", - "metadata": { - "uid": "sample-audio-control", - "api": "audio-control", - "info": "Provide Default Audio Policy for Multimedia, Navigation and Emergency", - "version": "1.0", - "require": ["intel-hda", "jabra-usb", "scarlett-usb"] - }, - "plugins": { - "uid" : "MyPlug", - "spath":"./plugins/pluginname:../conf.d/project/lua.d", - "libs": ["ctl-audio-plugin-sample.ctlso", "softmixer-simple.lua"], - "lua": ["Lua2cHelloWorld1", "Lua2cHelloWorld2"] - }, - "onload": [{ - "uid": "onload-sample-cb", - "info": "Call control sharelib install entrypoint", - "action": "lua://MyPlug#SamplePolicyInit", - "args": { - "arg1": "first_arg", - "nextarg": "second arg value" - } - }, { - "uid": "onload-sample-api", - "info": "Assert AlsaCore Presence", - "action": "api://alsacore#ping", - "args": { - "test": "onload-sample-api" - } - } - ], - "controls":[{ - "uid": "multimedia", - "privileges": "urn:AGL:permission:audio:public:mutimedia", - "action": "lua://MyPlug#Audio_Set_Multimedia" - }, { - "uid": "navigation", - "privileges": "urn:AGL:permission:audio:public:navigation", - "action": "lua://MyPlug#Audio_Set_Navigation" - }, { - "uid": "emergency", - "privileges": "urn:AGL:permission:audio:public:emergency", - "action": "lua://MyPlug#Audio_Set_Emergency" - }, { - "uid": "multimedia-control-cb", - "info": "Call Sharelib Sample Callback", - "action": "plugin://MyPlug#sampleControlNavigation", - "args": { - "arg1": "snoopy", - "arg2": "toto" - } - }, { - "uid": "navigation-control-ucm", - "action": "api://alsacore#ping", - "args": { - "test": "navigation" - } - }, { - "uid": "navigation-control-lua", - "info": "Call Lua Script to set Navigation", - "action": "lua://MyPlug#Audio_Set_Navigation" - } - ], - "events":[{ - "uid": "speed-action-1", - "action": "plugin://MyPlug#Blink-when-over-130", - "args": { - "speed": 130, - "blink-speed": 1000 - } - }, { - "uid": "Adjust-Volume", - "action": "lua://MyPlug#Adjust_Volume_To_Speed" - }, { - "uid": "Display-Rear-Camera", - "action": "plugin://MyPlug#Display-Rear-Camera" - }, { - "uid": "Prevent-Phone-Call", - "action": "api://phone#status", - "args": { - "call-accepted": "false" - } - }, { - "uid": "Authorize-Video", - "action": "api://video#status", - "args": { - "tv-accepted": "true" - } - } - ] -} -``` + to generate it in pdf version, or change *pdf* to *serve* to have it as a web site.
\ No newline at end of file diff --git a/book.json b/book.json new file mode 100644 index 0000000..eb823f4 --- /dev/null +++ b/book.json @@ -0,0 +1,104 @@ +{ + "title": "APP Controller Guide", + "subtitle": "Developer Documentation", + "description": "This is the utilisation guide for the APP Controller", + "keywords": "AGL, Development, Iotbzh, Controller", + "author": "IoT.Bzh Team", + "website": "http://iot.bzh", + "published": "August 2018", + "version": "1.0", + + "gitbook": "3.2.2", + "root": "docs", + "pdf": { + "fontFamily": "Verdana", + "fontSize": 12, + "paperSize": "a4", + "pageBreaksBefore": "//h:div[@class=\"page-break\"]" + }, + "styles": { + "website": "resources/ebook.css", + "ebook": "resources/ebook.css", + "pdf": "resources/ebook.css" + }, + + "hidepageheaders": [2, 3], + "hidepagefooters": [2, 3], + + "plugins": [ + "regexplace" + ], + "pluginsConfig": { + "regexplace": { + "removeFirstPartsInSectionNumber": true, + "substitutes": [{ + "pattern": "<!-- pagebreak -->", + "flags": "g", + "substitute": "<div class=\"page-break\"></div>" + }, + { + "pattern": "<!-- clear -->", + "flags": "g", + "substitute": "<div class=\"clear\"></div>" + }, + { + "pattern": "<!-- nopagebreak -->", + "flags": "g", + "substitute": "<div class=\"nopb\">" + }, + { + "pattern": "<!-- endnopagebreak -->", + "flags": "g", + "substitute": "</div>" + }, + { + "pattern": "<!-- note -->", + "flags": "g", + "substitute": "<div class=\"note\">" + }, + { + "pattern": "<!-- endnote -->", + "flags": "g", + "substitute": "</div>" + }, + { + "pattern": "<!-- warning -->", + "flags": "g", + "substitute": "<div class=\"warning\">" + }, + { + "pattern": "<!-- endwarning -->", + "flags": "g", + "substitute": "</div>" + }, + { + "pattern": "!\\[(.*?)\\]\\((.*?)(?:\\s+\"(.*)\")?\\){0,}{caption=1([^\\}]*)}", + "flags": "gmi", + "substitute": "<figure id=\"fig_PAGE_LEVEL_._INDEX_\"><img $3 alt=\"$1\" title=\"$1\" href=\"$2\"><figcaption></figcaption></figure>", + "decode": true + }, + { + "pattern": "<img ([^>]*) {0,}\/{0,}> {0,}{caption=1([^\\}]*)}", + "flags": "g", + "substitute": "<figure id=\"fig_PAGE_LEVEL_._INDEX_\"><img $2 $1><figcaption></figcaption></figure>", + "decode": true + }, + { + "pattern": "<img (.*)alt=\"([^\"]*)\"(.*) {0,1}\/{0,1}><figcaption></figcaption>", + "flags": "g", + "substitute": "<img$1alt=\"$2\"$3><figcaption><span>Picture _PAGE_LEVEL_._INDEX_</span>: $2</figcaption>", + "store": { + "substitute": "<a href=\"_PAGE_PATH_#fig_PAGE_LEVEL_._INDEX_\">Pic. _PAGE_LEVEL_._INDEX_</a> <span>$2</span>", + "variable_name": "pictures" + } + }, + { + "pattern": "<img ([^>]*)> {0,}{:: {0,}style=([^}]*)}", + "flags": "g", + "substitute": "<img $1 style=$2>", + "decode": true + } + ] + } + } +} diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..69822c8 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,19 @@ +# Introduction + +<br> +<br> +<br> +<br> +<br> + +| *Meta* | *Data* | +| -- | -- | +| **Title** | {{ config.title }} | +| **Author** | {{ config.author }} | +| **Description** | {{ config.description }} | +| **Keywords** | {{ config.keywords }} | +| **Language** | English | +| **Published** | Published {{ config.published }} as an electronic book | +| **Updated** | {{ gitbook.time }} | +| **Collection** | Open-source | +| **Website** | [{{ config.website }}]({{ config.website }}) | diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md new file mode 100644 index 0000000..edf91f8 --- /dev/null +++ b/docs/SUMMARY.md @@ -0,0 +1,6 @@ +# Summary + +* [The Controller](controller.md) +* [Controller Configuration](controllerConfig.md) +* [Usage](Usage.md) +* [Config Sample](configSample.md)
\ No newline at end of file diff --git a/docs/Usage.md b/docs/Usage.md new file mode 100644 index 0000000..65f1ce9 --- /dev/null +++ b/docs/Usage.md @@ -0,0 +1,53 @@ +# Usage + +## 1) Add app-controller-submodule as a submodule to include in your project + +```bash +git submodule add https://gerrit.automotivelinux.org/gerrit/apps/app-controller-submodule +``` + +## 2) Add app-controller-submodule as a static library to your binding + +```cmake + # Library dependencies (include updates automatically) + TARGET_LINK_LIBRARIES(${TARGET_NAME} + ctl-utilities + ... other dependencies .... + ) +``` + +## 3) Declare your controller config section in your binding + +```C +// CtlSectionT syntax: +// key: "section name in config file" +// loadCB: callback to process section +// handle: a void* pass to callback when processing section +static CtlSectionT ctlSections[]= { + {.key="plugins" , .loadCB= PluginConfig, .handle= &halCallbacks}, + {.key="onload" , .loadCB= OnloadConfig}, + {.key="halmap" , .loadCB= MapConfigLoad}, + {.key=NULL} +}; + +``` + +## 4) Do the controller config parsing at binding pre-init + +```C + // check if config file exist + const char *dirList= getenv("CTL_CONFIG_PATH"); + if (!dirList) dirList=CONTROL_CONFIG_PATH; + + const char *configPath = CtlConfigSearch(apiHandle, dirList, "prefix"); + if(!confiPath) return -1; + + ctlConfig = CtlConfigLoad(dirList, ctlSections); + if (!ctlConfig) return -1; +``` + +## 5) Execute the controller config during binding init + +```C + int err = CtlConfigExec (ctlConfig); +```
\ No newline at end of file diff --git a/docs/_layouts/ebook/page.html b/docs/_layouts/ebook/page.html new file mode 100644 index 0000000..bf325e9 --- /dev/null +++ b/docs/_layouts/ebook/page.html @@ -0,0 +1,36 @@ +{% extends "layout.html" %} + +{% block title %}{{ page.title }}{% endblock %} +{% block description %}{{ page.description }}{% endblock %} + +{% block style %} + {### Include theme css before plugins css ###} + {% if not fileExists(config.styles.print) %} + {% if options.format %} + <link rel="stylesheet" href="{{ (options.format + ".css")|resolveAsset }}"> + {% else %} + <link rel="stylesheet" href="{{ "ebook.css"|resolveAsset }}"> + {% endif %} + {% endif %} + + {{ super() }} + + {### Custom stylesheets for the book ###} + + {% for type, style in config.styles %} + {% if fileExists(style) and (type == "ebook" or type == "print" or type == options.format) %} + <link rel="stylesheet" href="{{ style|resolveFile }}"> + {% endif %} + {% endfor %} +{% endblock %} + +{% block body %} +<div class="page"> + {% block page %} + <h1 class="book-chapter book-chapter-{{ page.depth }}">{{ page.title }}</h1> + <div class="section"> + {{ page.content|safe }} + </div> + {% endblock %} +</div> +{% endblock %} diff --git a/docs/_layouts/ebook/pdf_footer.html b/docs/_layouts/ebook/pdf_footer.html new file mode 100644 index 0000000..679e562 --- /dev/null +++ b/docs/_layouts/ebook/pdf_footer.html @@ -0,0 +1,13 @@ +{% extends "./page.html" %} +{% block body %} +<div id="pdf-footer" class="pdf-footer"> + <span class="footer-left">Version {{ config.version }}</span> + <span class="footer-right">{{ page.num }}</span> + <span class="footer-center">{{ config.published }}</span> +</div> + +<!-- Allow to hide footer for some pages using hidepagefooters config in book.json --> +<script> + if (({% for num in config.hidepagefooters %}{{ page.num }} == {{ num }} || {% endfor %}false)) document.getElementById('pdf-footer').style.display = 'none' +</script> +{% endblock %} diff --git a/docs/_layouts/ebook/pdf_header.html b/docs/_layouts/ebook/pdf_header.html new file mode 100644 index 0000000..ef49641 --- /dev/null +++ b/docs/_layouts/ebook/pdf_header.html @@ -0,0 +1,13 @@ +{% extends "./page.html" %} +{% block body %} +<div id="pdf-header" class="pdf-header"> + <span class="header-left">IoT.Bzh</span> + <span class="header-right">{{ config.title }}</span> +</div> + +<!-- Allow to hide header for some pages using hidepageheaders config in book.json --> +<script> + if (({% for num in config.hidepageheaders %}{{ page.num }} == {{ num }} || {% endfor %}false)) document.getElementById('pdf-header').style.display = 'none' +</script> + +{% endblock %}
\ No newline at end of file diff --git a/docs/_layouts/ebook/summary.html b/docs/_layouts/ebook/summary.html new file mode 100644 index 0000000..be328a4 --- /dev/null +++ b/docs/_layouts/ebook/summary.html @@ -0,0 +1,58 @@ +{% extends "./page.html" %} + +{% block title %}{{ "SUMMARY"|t }}{% endblock %} + +{% macro articles(_articles) %} + {% for article in _articles %} + <li> + <span class="inner"> + {% if article.path or article.url %} + {% if article.path %} + <a href="{{ article.path|contentURL }}{{ article.anchor }}">{{ article.title }}</a> + {% else %} + <a target="_blank" href="{{ article.url }}">{{ article.title }}</a> + {% endif %} + {% else %} + <span>{{ article.title }}</span> + {% endif %} + {% if 1 %} + <span class="page">{{ article.level }}</span> + {% endif %} + </span> + {% if article.articles.length > 0 %} + <ol> + {{ articles(article.articles) }} + </ol> + {% endif %} + </li> + {% endfor %} +{% endmacro %} + +{% block page %} +<div class="section toc"> + <h1>{{ "SUMMARY"|t }}</h1> + <ol> + {% for part in summary.parts %} + {% if part.title %} + <li class="part-title"> + <h2>{{ part.title }}</h2> + </li> + {% endif %} + {{ articles(part.articles) }} + + {% if not loop.last %} + <li class="divider"></li> + {% endif %} + {% endfor %} + + {% if glossary.path %} + <li> + <span class="inner"> + <a href="{{ ('/' + glossary.path)|contentURL }}">{{ "GLOSSARY"|t }}</a> + </span> + </li> + {% endif %} + </ol> +</div> +{% endblock %} + diff --git a/docs/_layouts/layout.html b/docs/_layouts/layout.html new file mode 100644 index 0000000..3d5aca6 --- /dev/null +++ b/docs/_layouts/layout.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html lang="{{ config.language }}" {% if page.dir == "rtl" %}dir="rtl"{% endif %}> + <head> + <meta charset="UTF-8"> + <meta content="text/html; charset=utf-8" http-equiv="Content-Type"> + <title>{% block title %}{{ config.title|d("GitBook", true) }}{% endblock %}</title> + <meta http-equiv="X-UA-Compatible" content="IE=edge" /> + <meta name="description" content="{% block description %}{% endblock %}"> + <meta name="generator" content="GitBook {{ gitbook.version }}"> + {% if config.author %}<meta name="author" content="{{ config.author }}">{% endif %} + {% if config.isbn %}<meta name="identifier" content="{{ config.isbn }}" scheme="ISBN">{% endif %} + {% block style %} + {% for resource in plugins.resources.css %} + {% if resource.url %} + <link rel="stylesheet" href="{{ resource.url }}"> + {% else %} + <link rel="stylesheet" href="{{ resource.path|resolveAsset }}"> + {% endif %} + {% endfor %} + {% endblock %} + + {% block head %}{% endblock %} + </head> + <body> + {% block body %}{% endblock %} + {% block javascript %}{% endblock %} + </body> +</html> diff --git a/docs/configSample.md b/docs/configSample.md new file mode 100644 index 0000000..0c35044 --- /dev/null +++ b/docs/configSample.md @@ -0,0 +1,98 @@ +# Config Sample + +Here after a simple configuration sample. + +```json +{ + "$schema": "http://iot.bzh/download/public/schema/json/ctl-schema.json", + "metadata": { + "uid": "sample-audio-control", + "api": "audio-control", + "info": "Provide Default Audio Policy for Multimedia, Navigation and Emergency", + "version": "1.0", + "require": ["intel-hda", "jabra-usb", "scarlett-usb"] + }, + "plugins": { + "uid" : "MyPlug", + "spath":"./plugins/pluginname:../conf.d/project/lua.d", + "libs": ["ctl-audio-plugin-sample.ctlso", "softmixer-simple.lua"], + "lua": ["Lua2cHelloWorld1", "Lua2cHelloWorld2"] + }, + "onload": [{ + "uid": "onload-sample-cb", + "info": "Call control sharelib install entrypoint", + "action": "lua://MyPlug#SamplePolicyInit", + "args": { + "arg1": "first_arg", + "nextarg": "second arg value" + } + }, { + "uid": "onload-sample-api", + "info": "Assert AlsaCore Presence", + "action": "api://alsacore#ping", + "args": { + "test": "onload-sample-api" + } + } + ], + "controls":[{ + "uid": "multimedia", + "privileges": "urn:AGL:permission:audio:public:mutimedia", + "action": "lua://MyPlug#Audio_Set_Multimedia" + }, { + "uid": "navigation", + "privileges": "urn:AGL:permission:audio:public:navigation", + "action": "lua://MyPlug#Audio_Set_Navigation" + }, { + "uid": "emergency", + "privileges": "urn:AGL:permission:audio:public:emergency", + "action": "lua://MyPlug#Audio_Set_Emergency" + }, { + "uid": "multimedia-control-cb", + "info": "Call Sharelib Sample Callback", + "action": "plugin://MyPlug#sampleControlNavigation", + "args": { + "arg1": "snoopy", + "arg2": "toto" + } + }, { + "uid": "navigation-control-ucm", + "action": "api://alsacore#ping", + "args": { + "test": "navigation" + } + }, { + "uid": "navigation-control-lua", + "info": "Call Lua Script to set Navigation", + "action": "lua://MyPlug#Audio_Set_Navigation" + } + ], + "events":[{ + "uid": "speed-action-1", + "action": "plugin://MyPlug#Blink-when-over-130", + "args": { + "speed": 130, + "blink-speed": 1000 + } + }, { + "uid": "Adjust-Volume", + "action": "lua://MyPlug#Adjust_Volume_To_Speed" + }, { + "uid": "Display-Rear-Camera", + "action": "plugin://MyPlug#Display-Rear-Camera" + }, { + "uid": "Prevent-Phone-Call", + "action": "api://phone#status", + "args": { + "call-accepted": "false" + } + }, { + "uid": "Authorize-Video", + "action": "api://video#status", + "args": { + "tv-accepted": "true" + } + } + ] +} +```
\ No newline at end of file diff --git a/docs/controller.md b/docs/controller.md new file mode 100644 index 0000000..4012455 --- /dev/null +++ b/docs/controller.md @@ -0,0 +1,28 @@ +# Controller + +* Object: Generic Controller to handle Policy,Small Business Logic, Glue in between components, ... +* Status: Release Candidate +* Author: Fulup Ar Foll fulup@iot.bzh +* Date : May-2018 + +## Features + +* Create a controller application from a JSON config file +* Each control (eg: navigation, multimedia, ...) is a suite of actions. When all actions succeed control is granted, if one fails control access is denied. +* Actions can either be: + * Invocation of an other binding API, either internal or external (eg: a policy service, Alsa UCM, ...) + * C routines from a user provided plugin (eg: policy routine, proprietary code, ...) + * Lua script function. Lua provides access to every AGL appfw functionality and can be extended by plugins written in C. + +## Installation + +* Controller can easily be included as a git submodule in any AGL service or application binder. +* Dependencies: the only dependencies are AGL application framework (https://gerrit.automotivelinux.org/gerrit/p/src/app-framework-binder.git) and app-afb-helpers-submodule (https://gerrit.automotivelinux.org/gerrit/p/apps/app-afb-helpers-submodule.git). +* Controller relies on Lua-5.3, when not needed Lua might be removed at compilation time. + +## Monitoring + +* The default test HTML page expect the monitoring HTML page to be accessible under /monitoring with the --monitoring option. +* The monitoring HTML pages are installed with the app framework binder in a subdirectory called monitoring. +* You can add other HTML pages with the alias options e.g. afb-daemon --port=1234 --monitoring --alias=/path1/to/htmlpages:/path2/to/htmlpages --ldpaths=. --workdir=. --roothttp=../htdocs +* The monitoring is accessible at http://localhost:1234/monitoring.
\ No newline at end of file diff --git a/docs/controllerConfig.md b/docs/controllerConfig.md new file mode 100644 index 0000000..6a21fd2 --- /dev/null +++ b/docs/controllerConfig.md @@ -0,0 +1,241 @@ +# Controller binding configuration + +By default the controller searches for a config filename with the same 'middlename' as the daemon process. As an example if your process name is afb-daemon then middle name is 'daemon'. In addition, if your process name is afb-daemon-audio the middle name is also 'daemon'. Moreover the prefix is chosen when you call the [CtlConfigSearch](<#4)_Do_controller_config_parsing_at_binding_pre-init>) function, see below: + +```bash +CtlConfigSearch(AFB_ApiT apiHandle, const char *dirList, const char *prefix) +``` + +```bash +# Middlename is taken from process middlename. +(prefix-)middlename*.json +``` + +You may overload the config search path with environment variables + +* **CONTROL_CONFIG_PATH**: change default reserch path for configuration. You may provide multiple directories separated by ':'. +* **CONTROL_LUA_PATH**: same as CONTROL_CONFIG_PATH but for Lua script files. + +Example: to load a config named '(prefix-)myconfig-test.json' do + +```bash +afb-daemon --name myconfig --verbose ...' +``` + +The configuration is loaded dynamically during startup time. The controller scans **CONTROL_CONFIG_PATH** for a file corresponding to the pattern +"(prefix-)bindermiddlename*.json". The first file found in the path is loaded, +any other file corresponding to the same path is ignored and only generates a warning. + +Each block in the configuration file is defined with + +* **uid**: mandatory, it is used either for debugging or as input for the action (eg: signal name, control name, ...) +* **info**: optional, it is used for documentation purpose only + +> **Note**: by default the controller config search path is defined at compilation time, but the path might be overloaded with the **CONTROL_CONFIG_PATH** +> environment variable. + +## Config is organised in sections + +* **metadata**: describes the configuration +* **plugins or resources**: defines the set of functions provided by the plugins allowing to load additionnal resources (compiled C or lua) +* **onload**: a collection of actions meant to be executed at startup time +* **control**: sets the controls with a collection of actions, in dynamic api it could also specify the verbs of the api +* **event**: a collection of actions meant to be executed when receiving a given signal +* **personnal sections**: personnal section + +Callbacks to parse sections are documented in [Declare your controller config section in your binding](<#3_Declare_your_controller_config_section_in_your_binding>) section. You can use the callback defined in controller or define your own callback. + +## Metadata + +As today matadata is only used for documentation purpose. + +* **uid**: mandatory +* **version**: mandatory +* **api**: mandatory +* **info**: optional +* **require**: optional +* **author**: optional +* **date**: optional + +## OnLoad section + +Onload section defines startup time configuration. Onload may provide multiple initialisation +profiles, each with a different uid. + +You can define the following keys or arrays of the following keys: + +* **uid**: mandatory. +* **info**: optional +* **action**: mandatory +* **args**: optionnal + +## Control section + +Control section defines a list of controls that are accessible. + +You can define the following keys or arrays of the following keys, moreover +this section could be verb api: + +* **uid**: mandatory +* **info**: optional +* **action**: the list of actions is mandatory + +## Event section + +Event section defines a list of actions to be executed on event reception. Event can do +anything a controller can (change state, send back signal, ...) +eg: if a controller subscribes to vehicle speed, then speed-event may adjust +master-volume to speed. + +You can define the following keys or arrays of the following keys, moreover you can define an event from an another API with the following syntax "API/event". + +* **uid**: mandatory +* **info**: optional +* **action**: the list of actions is mandatory + +## Plugin section + +Plugin section defines plugins used with this controller. A plugin is a C/C++ program meant to +execute some tasks after an event or on demand. This easily extends intrinsec +binding logic for ad-hoc needs. + +You can define the following keys or arrays of the following keys: + +* **uid**: mandatory +* **info**: optionnal +* **spath**: optionnal, semicolon separated paths where to find the plugin. This could be a compiled shared library or LUA scripts. Could be specified using CONTROL_PLUGIN_PATH environment variable also. +* **libs**: mandatory, Plugin file or LUA scripts to load +* **lua**: optionnal, C functions that could be called from a LUA script + +## Personnal sections + +* **uid**: mandatory +* **info**: optionnal +* **action**: mandatory +* **any keys wanted**: optionnal + +You can define your own sections and add your own callbacks into the +CtlSectionT structure, see +[Declare your controller config section in your binding](<#3_Declare_your_controller_config_section_in_your_binding>) section. + +## Actions Categories + +Controller supports three categories of actions. Each action returns a status +where 0=success and 1=failure. + +* **AppFw API** provides a generic model to request other bindings. Requested bindings can be local (eg: ALSA/UCM) or external (eg: vehicle signalling). + * `"action": "api://API_NAME#verb_name"` +* C-API, when defined in the onload section, the plugin may provide C native API with `CTLP-CAPI(apiname, uid, args, query, context)`. Plugin may also create Lua command with `CTLP-LUA2C(LuaFuncName, uid, args, query, context)`. Where `args`+`query` are JSON-C object and context is the returned value from `CTLP_ONLOAD` function. Any missing value is set to NULL. + * `"action": "plugin://plugin_name#function_name"` +* Lua-API, when compiled with Lua option, the controller supports action defined directly in Lua script. During "*onload*" phase, the controller searches in `CONTROL_LUA_PATH` file with pattern "(prefix-)bindermiddlename*.lua". Any file corresponding to this pattern is automatically loaded. Any function defined in those Lua scripts can be called through a controller action. Lua functions receive three parameters (uid, args, query). + * `"action": "lua://plugin_name#function_name"` + +You also can add the **privileges** property that handles AGL permission +needed to be able to call this action. + +> **Note**: Lua added functions are systematically prefixed. AGL standard AppFw +functions are prefixed with AGL: (eg: AFB:notice(), AFB:success(), ...). +> User Lua functions added through the plugin and CTLP_LUA2C are prefixed with +the plugin uid or the one you defined in your config (eg: MyPlug:HelloWorld1). + +## Available Application Framework Commands + +Each Lua AppFw commands should be prefixed by AFB: + +* `AFB:notice ("format", arg1,... argn)` directly printed LUA tables as json string with '%s'. + `AFB:error`, `AFB:warning`, `AFB:info`, `AFB:debug` work on the same model. Printed messages are limited to 512 characters. + +* `AFB:service ('API', 'VERB', {query}, "Lua_Callback_Name", {context})` is an asynchronous call to another binding. When empty, query/context should be set to '{}' + and not to 'nil'. When 'nil', Lua does not send 'NULL' value but removes arguments to calling stack. WARNING:"Callback" + is the name of the callback as a string and not a pointer to the callback. (If someone as a solution to fix this, please + let me known). Callback is call as LUA "function Alsa_Get_Hal_CB (error, result, context)" where: + * error is a Boolean + * result is the full answer from AppFw (do not forget to extract the response) + * context is a copy of the Lua table pass as an argument (warning it's a copy not a pointer to original table) + +* `error,result=AFB:servsync('API', 'VERB', {query})` is saved as previous but for synchronous call. Note that Lua accepts multiple + returns. AFB:servsync returns both the error message and the response as a Lua table. Like for AFB:service, the user should not + forget to extract response from result. + +* `AFB:success(request, response)` is the success request. request is the opaque handle passes when Lua is called from (api="control", verb="docall"). + Response is a Lua table that will be returned to the client. + +* `AFB:fail(request, response)` is the same as for success. Note that LUA generates automatically the error code from Lua function name. + The response is transformed into a json string before being returned to the client. + +* `EventHandle=AFB:evtmake("MyEventName")` creates an event and returns the handle as an opaque handle. Note that due to a limitation + of json_object, this opaque handle cannot be passed as an argument in a callback context. + +* `AFB:subscribe(request, MyEventHandle)` subscribes a given client to a previously created event. + +* `AFB:evtpush (MyEventHandle, MyEventData)` pushes an event to every subscribed client. MyEventData is a Lua table that will be + sent as a json object to the corresponding clients. + +* `timerHandle=AFB:timerset (MyTimer, "Timer_Test_CB", context)` initialises a timer from MyTimer Lua table. This table should contains 3 elements: + MyTimer={[l"abel"]="MyTimerName", ["delay"]=timeoutInMs, ["count"]=nBOfCycles}. Note that if count==0 then timer is cycled + infinitely. Context is a standard Lua table. This function returns an opaque handle to be used to further control the timer. + +* `AFB:timerclear(timerHandle)` kills an existing timer. Returns an error when timer does not exit. + +* `MyTimer=AFB:timerget(timerHandle)` returns uid, delay and count of an active timer. Returns an error when timerHandle does not + point on an active timer. + +* `AFB:GetEventLoop()` retrieves the common systemd's event loop of AFB. + +* `AFB:RootDirGetFD()` gets the root directory file descriptor. This file descriptor can be used with functions 'openat', 'fstatat', ... + +> **Note**: Except for functions call during binding initialisation period. Lua calls are protected and should returned clean messages +> even when they are improperly used. If you find bug please report. + +## Adding Lua command from User Plugin + +User Plugin is optional and may provide either native C-action accessible directly from controller actions as defined in +JSON config file, or alternatively may provide a set of Lua commands usable inside any script (onload, control,event). A simple +plugin that provides both notice C API and Lua commands is provided as example (see ctl-plugin-sample.c). Technically a +plugin is a simple sharelibrary and any code fitting in sharelib might be used as a plugin. Developer should nevertheless +not forget that except when no-concurrency flag was at binding construction time, any binding should to be thread safe. + +A plugin must be declared with `CTLP_REGISTER("MyCtlSamplePlugin")`. This entry point defines a special structure that is checked +at plugin load time by the controller. Then you have an optional init routine declare with `CTLP_ONLOAD(plugin, handle)`. + The init routine may create +a plugin context that is later presented to every plugin API, this for both LUA and native C ones. Then each: + +* C API declare with `CTLP_CAPI(MyCFunction, source, argsJ, queryJ) {your code}`. Where: + * **MyFunction** is your function + * **source** is the structure config + * **argsJ** a json_object containing the argument attaches to this control in JSON config file + * **queryJ** a json_object + +* Lua API declare with `CTLP_LUA2C(MyLuaCFunction, source, argsJ, responseJ) {your code}`. Where + * **MyLuaCFunction** is both the name of your C function and Lua command + * **source** is the structure config + * **argsJ** the arguments passed this time from Lua script and not from Json config file. + * **responseJ** if success the argument is passed into the request. + +> **Warning**: Lua samples use with controller enforce strict mode. As a result every variable should be declared either as +> local or as global. Unfortunately "luac" is not smart enough to handle strict mode at build time and errors only appear +> at run time. Because of this strict mode every global variables (which include functions) should be prefixed by '_'. +> Note that LUA requires an initialisation value for every variables and declaring something like "local myvar" will not +> allocate "myvar". + +## Debugging Facilities + +Controller Lua scripts are checked for syntax from CMAKE template with Luac. When needed to go further, a developer API should be allowed to +execute directly Lua commands within the controller context from Rest/Ws (api=control, verb=lua_doscript). DoScript API takes two +other optional arguments func=xxxx where xxxx is the function to execute within Lua script and args, a JSON object to provide +input parameters. When funcname is not given by default, the controller tries to execute middle filename doscript-xxxx-????.lua. + +When executed from the controller, Lua script may use any AppFw Apis as well as any L2C user defines commands in plugin. + +## Running as Standalone Controller + +The controller is a standard binding. It can be started with the following command: + +```bash +afb-daemon --name=yourname --port=1234 --workdir=. --roothttp=./htdocs --tracereq=common --token= --verbose --binding=pathtoyourbinding.so --monitoring +``` + +Afb-Daemon only loads controller bindings without searching for the other +binding. In this case, the controller binding will search for a configuration file +name '(prefix-)bindermiddlename*.json'. This model can be used to implement for testing +purpose or simply to act as the glue between a UI and other binder/services.
\ No newline at end of file diff --git a/docs/resources/cover.svg b/docs/resources/cover.svg new file mode 100644 index 0000000..6726de7 --- /dev/null +++ b/docs/resources/cover.svg @@ -0,0 +1,210 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="1800" + height="2360" + viewBox="0 0 1800 2360" + id="svg2" + version="1.1" + inkscape:version="0.91 r13725" + sodipodi:docname="cover.svg"> + <defs + id="defs4175"> + <filter + style="color-interpolation-filters:sRGB" + inkscape:label="Drop Shadow" + id="filter4000"> + <feFlood + result="flood" + flood-color="rgb(0,0,0)" + flood-opacity="0.475" + id="feFlood4002" /> + <feComposite + result="composite1" + operator="in" + in="flood" + in2="SourceGraphic" + id="feComposite4004" /> + <feGaussianBlur + result="blur" + stdDeviation="5" + id="feGaussianBlur4006" /> + <feOffset + result="offset" + dy="8" + dx="8" + id="feOffset4008" /> + <feComposite + result="composite2" + operator="over" + in="SourceGraphic" + in2="offset" + id="feComposite4010" /> + </filter> + <filter + style="color-interpolation-filters:sRGB" + id="filter4000-6" + inkscape:label="Drop Shadow"> + <feFlood + id="feFlood4002-4" + flood-opacity="0.475" + flood-color="rgb(0,0,0)" + result="flood" /> + <feComposite + id="feComposite4004-6" + in2="SourceGraphic" + in="flood" + operator="in" + result="composite1" /> + <feGaussianBlur + id="feGaussianBlur4006-2" + stdDeviation="5" + result="blur" /> + <feOffset + id="feOffset4008-8" + dx="8" + dy="8" + result="offset" /> + <feComposite + id="feComposite4010-9" + in2="offset" + in="SourceGraphic" + operator="over" + result="composite2" /> + </filter> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.24748737" + inkscape:cx="928.3577" + inkscape:cy="404.58117" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="false" + inkscape:snap-text-baseline="false" + units="px" + inkscape:window-width="1920" + inkscape:window-height="1171" + inkscape:window-x="1920" + inkscape:window-y="0" + inkscape:window-maximized="1" + showguides="false" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(0,1307.6379)"> + <g + id="iotbzh-logo" + transform="matrix(2.3917866,0,0,2.3917866,216.6324,-1946.4393)" + inkscape:export-filename="/home/sdx/Pictures/Logo/logo_iot_bzh_100dpi.png" + inkscape:export-xdpi="100.22011" + inkscape:export-ydpi="100.22011" + style="display:inline;filter:url(#filter4000-6)"> + <text + sodipodi:linespacing="125%" + id="text3557-5-3-7-0-7-3" + y="519.50671" + x="27.886671" + style="font-style:normal;font-weight:normal;font-size:97.09867096px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;display:inline;fill:#000000;fill-opacity:1;stroke:none" + xml:space="preserve"><tspan + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:97.09867096px;line-height:125%;font-family:FreeEuro;-inkscape-font-specification:'FreeEuro Bold';text-align:start;writing-mode:lr-tb;text-anchor:start" + y="519.50671" + x="27.886671" + id="tspan3559-5-4-1-5-0-6" + sodipodi:role="line">IOT</tspan></text> + <path + inkscape:connector-curvature="0" + d="m 286.73007,473.23356 c 28.21686,16.29102 28.75566,58.73779 0.99693,78.53831 -7.67688,5.47598 -8.77935,4.91028 -1.99529,-1.0238 17.47377,-15.28453 17.98492,-42.17775 1.08522,-57.09786 l -3.91266,-3.45435 0.72312,-3.71053 c 0.39771,-2.04076 0.5997,-5.73115 0.44885,-8.20083 -0.33876,-5.54623 0.15803,-6.49185 2.65383,-5.05094 z m -64.76568,11.40332 c 7.06047,-7.74198 18.64659,-14.16089 29.04027,-16.08874 l 6.87489,-1.27521 0.87404,2.89709 c 0.4807,1.59343 0.67439,5.2245 0.43037,8.06906 l -0.44364,5.17195 -6.13887,1.6918 c -10.91241,3.00731 -20.4022,10.85909 -25.4533,21.05979 l -2.41633,4.87984 -2.74281,-0.41238 c -5.14252,-0.77316 -12.72985,-3.97645 -12.79123,-5.40033 -0.092,-2.13451 8.34659,-15.74625 12.76661,-20.59287 z m 33.20546,36.39493 c -28.21687,16.29101 -65.24624,-4.46574 -68.51461,-38.40577 -0.9039,-9.38637 0.13723,-10.0583 1.88428,-1.21608 4.49989,22.77499 27.53453,36.66428 48.90556,29.48876 l 4.94788,-1.66128 2.85184,2.48149 c 1.56852,1.36481 4.66349,3.38493 6.87772,4.48914 4.97257,2.47973 5.54308,3.38282 3.04733,4.82374 z m 22.50729,-61.79039 c 3.17451,9.98553 2.94038,23.22889 -0.58688,33.19399 l -2.33309,6.59143 -2.94597,-0.69161 c -1.6203,-0.38041 -4.86173,-2.02821 -7.2032,-3.6618 l -4.25721,-2.97018 1.60429,-6.16234 c 2.85178,-10.95404 0.79685,-23.09833 -5.51167,-32.57307 l -3.01788,-4.53253 1.72854,-2.16916 c 3.24083,-4.06698 9.80863,-9.03614 11.07242,-8.37738 1.89457,0.98756 9.46336,15.1015 11.45065,21.35265 z m -48.80223,10.31437 c 0,-32.58201 36.49058,-54.27201 67.51771,-40.1325 8.58077,3.9104 8.6421,5.148 0.11108,2.23988 -21.97368,-7.49048 -45.51946,5.51348 -49.99082,27.6091 l -1.03521,5.11561 -3.57498,1.22902 c -1.96621,0.67596 -5.26316,2.34622 -7.32655,3.71171 -4.63379,3.06649 -5.70115,3.10904 -5.70115,0.22718 z m 42.25842,50.3871 c -10.23499,-2.24356 -21.58699,-9.06801 -28.45341,-17.10525 l -4.5418,-5.31622 2.07194,-2.20549 c 1.13957,-1.21302 4.18733,-3.19628 6.77282,-4.40726 l 4.70085,-2.20176 4.53458,4.47053 c 8.06061,7.94674 19.60535,12.23927 30.96496,11.51329 l 5.43422,-0.34731 1.01427,2.58154 c 1.90169,4.84014 2.92124,13.01261 1.71883,13.77769 -1.80254,1.14695 -17.80995,0.64475 -24.21726,-0.75976 z" + style="display:inline;fill:#5a2ca0" + id="path3415-4-2-2-5-0-3-7-4-4-1-5" /> + <text + sodipodi:linespacing="125%" + id="text3557-5-3-7-46-7-3-7" + y="519.50671" + x="317.95816" + style="font-style:normal;font-weight:normal;font-size:97.09867096px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;display:inline;fill:#000000;fill-opacity:1;stroke:none" + xml:space="preserve"><tspan + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:97.09867096px;line-height:125%;font-family:FreeEuro;-inkscape-font-specification:'FreeEuro Bold';text-align:start;writing-mode:lr-tb;text-anchor:start" + y="519.50671" + x="317.95816" + id="tspan3559-5-4-1-90-0-2-9" + sodipodi:role="line">BZH</tspan></text> + </g> + <flowRoot + xml:space="preserve" + id="root-title" + transform="matrix(2.3469382,0,0,2.3469382,464.13874,-1200)"><flowRegion + id="flowRegion4303"><rect + id="rect4305" + width="679.99994" + height="141.42853" + x="-154.28572" + y="400"/> + </flowRegion> + <flowPara + id="title" + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:87.5px;line-height:125%;font-family:Verdana;-inkscape-font-specification:'Verdana, Bold';text-align:center;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1">{title}</flowPara></flowRoot> <flowRoot + xml:space="preserve" + id="root-subtitle" + transform="matrix(1.8523279,0,0,1.8523279,553.97647,-380)"><flowRegion + id="flowRegion4303-6"><rect + id="rect4305-0" + width="852.32806" + height="154.51677" + x="-239.3591" + y="290"/> + </flowRegion> + <flowPara + id="subtitle" + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:62.5px;line-height:125%;font-family:Verdana;-inkscape-font-specification:'Verdana, Bold';text-align:center;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1">{subtitle}</flowPara> + </flowRoot> + <flowRoot + id="root-version" + xml:space="preserve" + transform="translate(-2.0185547,164)"><flowRegion + id="flowRegion4169"><rect + y="501.68909" + x="343.32947" + height="99.591171" + width="1117.3768" + id="rect4171" /></flowRegion><flowPara + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:50px;line-height:125%;font-family:Verdana;-inkscape-font-specification:'Verdana, Bold';text-align:center;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1" + id="version">{version}</flowPara></flowRoot> <flowRoot + transform="translate(-2.0185547,238)" + xml:space="preserve" + id="root-date"><flowRegion + id="flowRegion4170"><rect + id="rect4172" + width="1117.3768" + height="99.591171" + x="343.32947" + y="501.68909" /></flowRegion><flowPara + id="date" + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:50px;line-height:125%;font-family:Verdana;-inkscape-font-specification:'Verdana, Bold';text-align:center;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1">{date}</flowPara></flowRoot> </g> +</svg> diff --git a/docs/resources/ebook.css b/docs/resources/ebook.css new file mode 100644 index 0000000..e8df4d8 --- /dev/null +++ b/docs/resources/ebook.css @@ -0,0 +1,402 @@ +/* IoT.Bzh theaming */ + +h1 { + color: #330066; + border-bottom: 2px solid #330066; +} + +h2 { + color: #330066; +} + +h3 { + color: #330066; +} + +h4 { + color: #330066; +} + + +/* GENERAL ELEMENTS */ + +/* clear both */ + +.clear { + clear: both; +} + +.section> :last-child { + margin-bottom: 0 !important; +} + +.section> :first-child { + margin-top: 0 !important; +} + + +/* SPECIAL ELEMENTS */ + + +/* page break always after element on pdf/print definition */ + +div.pagebreak { + page-break-after: always; +} + + +/* no page break inside element on pdf/print definition */ + +div.nopb { + page-break-inside: avoid !important; + margin: 4px 0 4px 0; +} + + +/* note blocks */ + +div.note { + background: #FCF8E3 none repeat scroll 0% 0%; + color: #8A6D3B; + padding: 15px; + margin-bottom: 10px; + border-bottom: 5px solid #DDD; + border-color: #FAEBCC; + page-break-inside: avoid; +} + +div.note p { + padding-bottom: 0; + margin-bottom: 0; +} + +/* warning blocks */ + +div.warning { + background: #FD9595 none repeat scroll 0% 0%; + color: #8A6D3B; + padding: 15px; + margin-bottom: 10px; + border-bottom: 5px solid #DDD; + border-color: #FF6B6B; + page-break-inside: avoid; +} + +div.warning p { + padding-bottom: 0; + margin-bottom: 0; +} + +/* images, figures and captions */ + +p img { + /* center all images */ + display: block; + margin: 0 auto; + padding: 10px 0; +} + +figure { + margin: 1.0em 0px; + padding: 10px 0; + text-align: center; + page-break-inside: avoid; + display: block; +} + +figure img { + display: block; + margin: 0 auto; +} + +figcaption { + clear: left; + margin: 1.0em 0 0 0; + text-align: center; + font-style: italic; + line-height: 1.5em; + font-size: 80%; + color: #666; + display: block; +} + +.page .section p img { + margin-top: 10px; +} + + +/* ul, ol list margin fix */ + +.page .section ol, +.page .section ul { + margin-bottom: 10px; +} + + +/* blockquotes */ + +.page .section blockquote { + margin: 0 0 0 5%; + font-style: italic; +} + + +/* PAGE SPECIFIC */ + + +/* set summary page to right side of the paper */ + +.page .toc h1 { + page-break-before: right; +} + +.page .section.toc { + page-break-inside: always; +} + +/* table headers */ + +div#README\.md table { + margin-top: 30px; + font-size: 95%; +} + +div#README\.md table thead { + display: none; +} + + + +/* CITATION AND IMAGES */ + + +/* math image styles */ + +.page .section p img.svg, +.page .section p img.png { + margin-top: 0px; + margin-bottom: -2px; +} + +.page .section p img.math { + vertical-align: middle; + height: auto; + width: auto; + margin-top: -4px; + max-height: 15px; +} + +.page .section p img.math.line1 { + margin-top: -7px; + max-height: 19px; +} + +.page .section p img.math.line2 { + margin-top: -1px; + max-height: 30px; +} + + +/* credits page */ + +.page .section ul.pictures { + margin-left: -30px; +} + +.page .section ul.pictures li { + list-style: outside none none; +} + +.page .section ul.pictures li a { + float: left; +} + +.page .section ul.pictures li span { + display: block; + margin-left: 100px; +} + + + +/* sub and super script */ + +.page .section sub { + font-size: 80%; + margin-left: 1px; +} + + +/* citations and references */ + +.page .section sup { + margin-left: -1px; + margin-right: 2px; + font-size: 80%; +} + +.page .section sup:before { + content: " "; +} + +.page .section ul.citations, +.page .section ul.references { + margin-left: -30px; +} + + +.page .section ul.citations li:nth-child(1) { + margin-top: 20px; + padding-top: 20px; + border-top: 1px solid #BBB; +} + +.page .section ul.citations li, +.page .section ul.references li { + list-style: outside none none; +} + +.page .section ul.citations li { + font-size: 80%; +} + +.page .section ul.citations li>span:nth-child(1), +.page .section ul.references li>span:nth-child(1) { + display: block; + float: left; + text-align: left; + width: 70px +} + +.page .section ul.citations li>span:nth-child(1) { + width: 50px +} + +.page .section ul.references li div { + margin-left: 70px; +} + +.page .section ul.citations li div { + margin-left: 50px; +} + +.page .section a[href="#"], +.page .section a[href="#"]:link, +.page .section a[href="#"]:visited, +.page .section a[href="#"]:hover, +.page .section a[href="#"]:focus { + text-decoration: none; + color: inherit; + cursor: text; + font-style: italic; +} + + +/* self referential footnotes */ + +.page .section div[type="selfref"] a[href="#"], +.page .section div[type="selfref"] a[href="#"]:link, +.page .section div[type="selfref"] a[href="#"]:visited, +.page .section div[type="selfref"] a[href="#"]:hover, +.page .section div[type="selfref"] a[href="#"]:focus { + font-style: normal; +} + +.page .section div[type="selfref"] span:nth-child(1) { + display: none; +} + + +/* page break always after element on pdf/print definition */ + +div.page-break { + page-break-inside: always; +} + +div.page-break:before { + content: ' '; +} + + +/* no page break inside element on pdf/print definition */ + +div.nopb { + page-break-inside: avoid; +} + +/* justify text */ +p { + text-align: justify; +} + +/* page header and footer */ + +.pdf-footer, +.pdf-header { + margin-top: 20px; + color: #aaa; +} + +.pdf-header .header-left { + float: left; + margin-left: 2em; + margin-right: auto; +} + +.pdf-header .header-right { + display: table; + margin-left: auto; + margin-right: 2em; +} + +.pdf-footer .sub { + padding-top: 8px; + font-size: 70%; +} + +.pdf-header .sub { + padding-top: 2px; + font-size: 70%; +} + +.pdf-footer { + padding-top: 10px; + border-top: 1px solid #eee; +} + +.pdf-footer .footer-left { + float: left; + margin-left: 2em; + margin-right: auto; +} + +.pdf-footer .footer-center { + display: table; + margin-left: auto; + margin-right: auto; +} + +.pdf-footer .footer-right { + float: right; + margin-left: auto; + margin-right: 2em; +} + +.pdf-header { + padding-bottom: 10px; + border-bottom: 1px solid #eee; +} + +.pdf-header .header-pages-count { + float: right; + text-align: right; +} + +.pdf-header .header-pages-count a, +.pdf-header .header-pages-count a:visited, +.pdf-header .header-pages-count a:active, +.pdf-header .header-pages-count a:focus, +.pdf-header .header-pages-count a:link { + text-decoration: none; + color: #aaa; + cursor: text; +} diff --git a/docs/resources/make_cover.sh b/docs/resources/make_cover.sh new file mode 100755 index 0000000..1026ecb --- /dev/null +++ b/docs/resources/make_cover.sh @@ -0,0 +1,27 @@ +#!/bin/bash +DOCS_DIR=$(cd $(dirname $0)/.. && pwd) +BOOKFILE=$DOCS_DIR/../book.json + +TITLE=$(grep '"title":' $BOOKFILE | cut -d'"' -f 4) +SUBTITLE=$(grep '"subtitle":' $BOOKFILE | cut -d'"' -f 4) +VERSION="Version $(grep '"version":' $BOOKFILE | cut -d'"' -f 4)" +DATE=$(grep '"published":' $BOOKFILE | cut -d'"' -f 4) + +[ -z "$TITLE" ] && { echo "Error TITLE not set!" ; exit 1; } +[ -z "$VERSION" ] && { echo "Error VERSION not set!" ; exit 1; } +[ -z "$DATE" ] && { echo "Error DATE not set!" ; exit 1; } + + +cat $(dirname $0)/cover.svg | sed -e "s/{title}/$TITLE/g" \ + -e "s/font-size:87.5px/font-size:54px/g" \ + -e "s/{subtitle}/$SUBTITLE/g" \ + -e "s/font-size:62.5px/font-size:40px/g" \ + -e "s/{version}/$VERSION/g" \ + -e "s/{date}/$DATE/g" \ + > /tmp/cover.svg + +# use imagemagick convert tool (cover size must be 1800x2360) +convert -resize "1600x2160!" -border 100 -bordercolor white -background white \ + -flatten -quality 100 /tmp/cover.svg $DOCS_DIR/cover.jpg + +convert -resize "200x262!" $DOCS_DIR/cover.jpg $DOCS_DIR/cover_small.jpg diff --git a/gendocs.sh b/gendocs.sh new file mode 100755 index 0000000..fdeb26d --- /dev/null +++ b/gendocs.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# shellcheck disable=SC2086 + +OUTFILENAME="APP_Controller_doc" + +SCRIPT=$(basename "${BASH_SOURCE[@]}") + +VERSION=$(grep '"version":' "$(dirname "${BASH_SOURCE[@]}")/book.json" | cut -d'"' -f 4) +[ "$VERSION" != "" ] && OUTFILENAME="${OUTFILENAME}_v${VERSION}" + + +function usage() { + cat <<EOF >&2 +Usage: $SCRIPT [options] [pdf|serve|doxygen] + +Options: + --debug + enable debug when generating pdf or html documentation + -d|--dry + dry run + -h|--help + get this help + +Example: + $SCRIPT pdf + +EOF + exit 1 +} + +function info() { + echo "$@" >&2 +} + +#default values +DEBUG_FLAG="" +DRY="" +DO_ACTION="" +OUT_DIR=./build +ARGS="" + +[ $# = 0 ] && usage +while [ $# -gt 0 ]; do + case "$1" in + --debug) DEBUG_FLAG="--log=debug --debug";; + -d|--dry) DRY="echo";; + -h|--help) usage;; + pdf | serve | doxygen) DO_ACTION=$1;; + --) shift; ARGS="$ARGS $*"; break;; + *) ARGS="$ARGS $1";; + esac + shift +done + +cd "$(dirname "$0")" || exit 1 +ROOTDIR=$(pwd -P) + +# Create out dir if needed +[ -d $OUT_DIR ] || mkdir -p $OUT_DIR + +if [ "$DO_ACTION" = "pdf" ] || [ "$DO_ACTION" = "serve" ]; then + + GITBOOK=$(which gitbook) + [ "$?" = "1" ] && { echo "You must install gitbook first, using: sudo npm install -g gitbook-cli"; exit 1; } + + which ebook-convert > /dev/null 2>&1 + [ "$?" = "1" ] && { echo "You must install calibre first, using: 'sudo apt install calibre' or refer to https://calibre-ebook.com/download"; exit 1; } + + [ ! -d "$ROOTDIR/node_modules" ] && $GITBOOK install + + if [ "$DO_ACTION" = "pdf" ]; then + + # Update cover when book.json has been changed + [[ $ROOTDIR/book.json -nt $ROOTDIR/docs/cover.jpg ]] && { echo "Update cover files"; $ROOTDIR/docs/resources/make_cover.sh || exit 1; } + + OUTFILE=$OUT_DIR/$OUTFILENAME.pdf + if $DRY $GITBOOK pdf $ROOTDIR $OUTFILE $DEBUG_FLAG $ARGS; then + echo "PDF has been successfully generated in $OUTFILE" + fi + else + $DRY $GITBOOK serve $DEBUG_FLAG $ARGS + fi + +elif [ "$DO_ACTION" = "doxygen" ]; then + $DRY cd $OUT_DIR && cmake .. && make doxygen $ROOTDIR/Doxyfile + +else + echo "Unknown action !" + usage +fi |