diff options
24 files changed, 1357 insertions, 90 deletions
@@ -1,61 +1,349 @@ -# Controller Utilities +Softmixer controller for 4A (AGL Advance Audio Architecture). +------------------------------------------------------------ -* Object: Generic Controller Utilities to handle Policy, Small Business Logic, Glue in between components, ... -* Status: Release Candidate -* Author: Fulup Ar Foll fulup@iot.bzh -* Date : October-2017 + * Object: Simulate a hardware mixer through and Alsa-Loop driver and a user space mixer + * Status: In Progress + * Author: Fulup Ar Foll fulup@iot.bzh + * Date : April-2018 -## Usage +## Functionalities: + - Create an application dedicate controller from a JSON config file + - Each controls (eg: navigation, multimedia, ...) is a suite of actions. When all actions succeed control is granted, if one fail control acces is denied. + - Actions can either be: + + Invocation to an other binding API, either internal or external (eg: a policy service, Alsa UCM, ...) + + C routines from a user provider plugin (eg: policy routine, proprietary code, ...) + + Lua script function. Lua provides access to every AGL appfw functionalities and can be extended from C user provided plugins. -0) Dependencies +## Installation + - Controler is a native part of AGL Advance Audio Framework but may be used independently with any other service or application binder. + - Dependencies: the only dependencies are audio-common for JSON-WRAP and Filescan-utils capabilities. + - Controler relies on Lua-5.3, when not needed Lua might be removed at compilation time. -* AGL Application Framework +## Monitoring + - Default test HTML page expect monitoring HTML page to be accessible from /monitoring for this to work you should + * place monitoring HTML pages in a well known location eg: $HOME/opt/monitoring + * start your binder with the alias option e.g. afb-daemon --port=1234 --alias=/monitoring:/home/fulup/opt/afb-monitoring --ldpaths=. --workdir=. --roothttp=../htdocs -1) Clone & build from source (temporally from github) +## Config -```bash -git clone --recursive https://github.com/iotbzh/4a-softmixer -cd build -cmake .. -make -``` +Configuration is loaded dynamically during startup time. The controller scans CONTROL_CONFIG_PATH for a file corresponding to pattern +"onload-bindername-xxxxx.json". When controller runs within AAAA binder it searches for "onload-audio-xxxx.json". First file found in the path the loaded +any other files corresponding to the same pather are ignored and only generate a warning. -2) Activate ALSA loop driver +Each bloc in the configuration file are defined with + * label: must be provided is used either for debugging or as input for the action (eg: signal name, control name, ...) + * info: optional used for documentation purpose only -```modprobe snd-aloop enable=1,1 index=4,5 id=loopback,softmix -``` +Note by default controller config search path is defined at compilation time, but path might be overloaded with CONTROL_CONFIG_PATH +environment variable. Setenv 'CONTROL_ONLOAD_PROFILE'=xxxx to overload 'onload-default-profile' initialisation sequence. -3) Declare your controller config section in your binding +### Config is organised in 4 sections: -```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} -}; + * metadata + * onload defines the set of action to be executed at startup time + * control defines the set of controls with corresponding actions + * event define the set of actions to be executed when receiving a given signal -``` +### Metadata + +As today matadata is only used for documentation purpose. + * label + version mandatory + * info optional + +### OnLoad section + +Defines startup time configuration. Onload may provide multiple initialisation profiles, each with a different label. + * label is mandatory. Label is used to select onload profile at initialisation through DispatchOneOnLoad("onload-label") API; + * info is optional + * plugin provides optional unique plugin name. Plugin should follow "onload-bindername-xxxxx.ctlso" patern + and are search into CONTROL_PLUGIN_PATH. When defined controller will execute user provided function context=CTLP_ONLOAD(label,version,info). + The context returned by this routine is provided back to any C routines call later by the controller. Note that Lua2C function + are prefix in Lua script with plugin label (eg: MyPlug: in following config sample) + * lua2c list of Lua commands shipped with provided plugin. + * require list of binding that should be initialised before the controller starts. Note that some listed requirer binding might be absent, + nevertheless any present binding from this list will be started before controller binding, missing ones generate a warning. + * action the list of action to execute during loadtime. Any failure in action will prevent controller binding from starting. + +### Control section + +Defines a list of controls that are accessible through (api="control", verb="request", control="control-label"). + + * label mandatory + * info optional + * permissions Cynara needed privileges to request this control (same as AppFw-V2) + * action the list of actions + +### Event section + +Defines a list of actions to be executed on event reception. Even can do anything a controller can (change state, +send back signal, ...) eg: if a controller subscribes to vehicule speed, then speed-event may ajust master-volume to speed. + + * label mandatory + * info optional + * action the list of actions + +### Actions Categories + +Controler support tree categories of actions. Each action return a status status where 0=success and 1=failure. + * AppFw API, Provides a generic model to request other bindings. Requested binding can be local (eg: ALSA/UCM) or + external (eg: vehicle signalling). + * api provides requested binding API name + * verb provides verb to requested binding + * args optionally provides a jsonc object for targeted binding API. Note that 'args' are statically defined + in JSON configuration file. Controler client may also provided its own arguments from the query list. Targeted + binding receives both arguments defined in the config file and the argument provided by controller client. + * C-API, when defined in the onload section, the plugin may provided C native API with CTLP-CAPI(apiname, label, args, query, context). + Plugin may also create Lua command with CTLP-Lua2C(LuaFuncName, label, args, query, context). Where args+query are JSONC object + and context the value return from CTLP_ONLOAD function. Any missing value is set to NULL. + * Lua-API, when compiled with Lua option, the controller support action defined directly in Lua script. During "onload" phase the + controller search in CONTROL_Lua_PATH file with pattern "onload-bindername-xxxx.lua". Any file corresponding to this pattern + is automatically loaded. Any function defined in those Lua script can be called through a controller action. Lua functions receive + three parameters (label, args, query). + +Note: Lua added functions systematically prefix. AGL standard AppFw functions are prefixed with AGL: (eg: AGL:notice(), AGL_success(), ...). +User Lua functions added though the plugin and CTLP_Lua2C are prefix with plugin label (eg: MyPlug:HelloWorld1). + +### Avaliable Application Framework Commands + +Each Lua AppFw commands should be prefixed by AFB: + + * AFB:notice ("format", arg1,... argn) LUA table are print directly as json string with '%s'. + AFB:error, AFB:warning, AFB:info, AFB:debug work on the same model. Printed message are limited to 512 characters. + + * AFB:service ('API', 'VERB', {query}, "Lua_Callback_Name", {context}) asynchronous call to an other binding. When empty query/context should be set to '{}' + and not to 'nil'. When 'nil' Lua does not send 'NULL' value but remove 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 response) + * context is a copy of the Lua table pas as argument (warning it's a copy not a pointer to original table) + + * error,result=AFB:servsync('API', 'VERB', {query}) Save as previous but for synchronous call. Note that Lua accept multiple + return. AFB:servsync return both the error message and the response as a Lua table. Like for AFB:service user should not + forget to extract response from result. + + * AFB:success(request, response) request is the opaque handle pass when Lua is called from (api="control", verb="docall"). + Response is a Lua table that will be return to client. -3) Do controller config parsing at binding pre-init + * AFB:fail(request, response) same as for success. Note that LUA generate automatically the error code from Lua function name. + The response is tranformed to a json string before being return to client. -```C - // check if config file exist - const char *dirList= getenv("CTL_CONFIG_PATH"); - if (!dirList) dirList=CONTROL_CONFIG_PATH; + * EventHandle=AFB:evtmake("MyEventName") Create an event and return the handle as an opaque handle. Note that due to a limitation + of json_object this opaque handle cannot be passed as argument in a callback context. - ctlConfig = CtlConfigLoad(dirList, ctlSections); - if (!ctlConfig) goto OnErrorExit; + * AFB:subscribe(request, MyEventHandle) Subscribe a given client to previously created event. + + * AFB:evtpush (MyEventHandle, MyEventData) Push an event to every subscribed client. MyEventData is a Lua table that will be + send as a json object to corresponding clients. + + * timerHandle=AFB:timerset (MyTimer, "Timer_Test_CB", context) Initialise a timer from MyTimer Lua table. This table should contend 3 elements: + MyTimer={[l"abel"]="MyTimerName", ["delay"]=timeoutInMs, ["count"]=nBOfCycles}. Note that is count==0 then timer is cycle + infinitively. Context is a standard Lua table. This function return an opaque handle to be use to further control the timer. + + * AFB:timerclear(timerHandle) Kill an existing timer. Return an error when timer does not exit. + + * MyTimer=AFB:timerget(timerHandle) Return Label, Delay and Count of an active timer. Return an error when timerHandle does not + point on an active timer. + +Note: Except for function call during binding initialisation period. Lua call are protected and should return clean message + even when 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 at set of Lua commands usable inside any script (onload, control,event). A simple +plugin that provide both natice 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 declare with CTLP_REGISTER("MyCtlSamplePlugin"). This entry point defines a special structure that is check +at plugin load time by the controller. Then you have an optional init routine declare with CTLP_ONLOAD(label, version, info). +This init routine receives controller onload profile as selected by DispatchOnLoad("profile"). The init routine may create +a plugin context that is later one presented to every plugin API this for both LUA and native C ones. Then each: + + * C API declare with CTLP_CAPI (MyCFunction, label, argsJ, queryJ, context) {your code}. Where: + * MyFunction is your function + * Label is a string containing the name of your function + * ArgsJ a json_object containing the argument attach the this control in JSON config file. + * context your C context as return from CTLP_ONLOAD + + * Lua API declarewith TLP_LUA2C (MyLuaCFunction, label, argsJ, context) {your code}. Where + * MyLuaCFunction is both the name of your C function and Lua command + * Label your function name as a string + * Args the arguments passed this time from Lua script and not from Json config file. + * Query is not provided as LuaC function are called from a script and not directly from controller action list. + +Warning: Lua samples use with controller enforce strict mode. As a result every variables should be declare 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 prefix by '_'. +Note that LUA require an initialisation value for every variables and declaring something like "local myvar" wont +allocate "myvar" + +### Debugging Facilities + +Controler Lua script are check for syntax from CMAKE template with Luac. When needed to go further an developer API allow to +execute directly Lua command within 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 parameter. When funcname is not given by default the controller try to execute middle filename doscript-xxxx-????.lua. + +When executed from controller Lua script may use any AppFw Apis as well as any L2C user defined commands in plugin. + +### Running as Standalone Controller + +Controller is a standard binding and can then be started independently of AAAA. When started with from build repository with ``` +afb-daemon --port=1234 --workdir=. --roothttp=../htdocs --tracereq=common --token= --verbose --binding=./Controller-afb/afb-control-afb.so +``` + +Afb-Daemon only load controller bindings without search for the other binding. In this case the name of the process is not change +to afb-audio and controller binding will search for a configuration file name 'onload-daemon-xxx.json'. This model can be used +to implement for testing purpose or simply to act as the glue in between a UI and other binder/services. + +## Config Sample -4) Exec controller config during binding init +Here after a simple configuration sample. + +``` +{ + "$schema": "ToBeDone", + "metadata": { + "label": "sample-audio-control", + "info": "Provide Default Audio Policy for Multimedia, Navigation and Emergency", + "version": "1.0" + }, + "onload": [{ + "label": "onload-default", + "info": "onload initialisation config", + "plugin": { + "label" : "MyPlug", + "sharelib": "ctl-audio-plugin-sample.ctlso", + "lua2c": ["Lua2cHelloWorld1", "Lua2cHelloWorld2"] + }, + "require": ["intel-hda", "jabra-usb", "scarlett-usb"], + "actions": [ + { + "label": "onload-sample-cb", + "info": "Call control sharelib install entrypoint", + "callback": "SamplePolicyInit", + "args": { + "arg1": "first_arg", + "nextarg": "second arg value" + } + }, { + "label": "onload-sample-api", + "info": "Assert AlsaCore Presence", + "api": "alsacore", + "verb": "ping", + "args": "test" + }, { + "label": "onload-hal-lua", + "info": "Load avaliable HALs", + "lua": "Audio_Init_Hal" + } + ] + }], + "controls": + [ + { + "label": "multimedia", + "permissions": "urn:AGL:permission:audio:public:mutimedia", + "actions": { + "label": "multimedia-control-lua", + "info": "Call Lua Script function Test_Lua_Engin", + "lua": "Audio_Set_Multimedia" + } + }, { + "label": "navigation", + "permissions": "urn:AGL:permission:audio:public:navigation", + "actions": { + "label": "navigation-control-lua", + "info": "Call Lua Script to set Navigation", + "lua": "Audio_Set_Navigation" + } + }, { + "label": "emergency", + "permissions": "urn:AGL:permission:audio:public:emergency", + "actions": { + "label": "emergency-control-ucm", + "lua": "Audio_Set_Emergency" + } + }, { + "label": "multi-step-sample", + "info" : "all actions must succeed for control to be accepted", + "actions": [{ + "label": "multimedia-control-cb", + "info": "Call Sharelib Sample Callback", + "callback": "sampleControlNavigation", + "args": { + "arg1": "snoopy", + "arg2": "toto" + } + }, { + "label": "navigation-control-ucm", + "api": "alsacore", + "verb": "ping", + "args": { + "test": "navigation" + } + }, { + "label": "navigation-control-lua", + "info": "Call Lua Script to set Navigation", + "lua": "Audio_Set_Navigation" + }] + } + ], + "events": + [ + { + "label": "Vehicle-Speed", + "info": "Action when Vehicule speed change", + "actions": [ + { + "label": "speed-action-1", + "callback": "Blink-when-over-130", + "args": { + "speed": 130 + "blink-speed": 1000 + } + }, { + "label": "Adjust-Volume", + "lua": "Adjust_Volume_To_Speed", + } + ] + }, + { + "label": "Reverse-Engage", + "info": "When Reverse Gear is Engage", + "actions": [ + { + "label": "Display-Rear-Camera", + "callback": "Display-Rear-Camera", + }, { + "label": "Prevent-Phone-Call", + "api" : "phone", + "verb" : "status", + "args": { + "call-accepted": false + } + } + ] + }, + { + "label": "Neutral-Engage", + "info": "When Reverse Neutral is Engage", + "actions": [ + { + "label": "Authorize-Video", + "api" : "video", + "verb" : "status", + "args": { + "tv-accepted": true + } + } + ] + } + ] +} -```C - int err = CtlConfigExec (ctlConfig); ``` -For sample usage look at https://gerrit.automotivelinux.org/gerrit/gitweb?p=apps/app-controller-submodule.git diff --git a/conf.d/cmake/config.cmake b/conf.d/cmake/config.cmake index b1bfa96..a8bce3d 100644 --- a/conf.d/cmake/config.cmake +++ b/conf.d/cmake/config.cmake @@ -18,7 +18,7 @@ # Project Info # ------------------ -set(PROJECT_NAME 4a-softmixer-afbd) +set(PROJECT_NAME 4a-softmixer) set(PROJECT_PRETTY_NAME "Audio SoftMixer") set(PROJECT_DESCRIPTION "Soft Mixer for 4A (AGL Advanced Audio Architecture)") set(PROJECT_URL "https://github.com/iotbzh/4a-softmixer") @@ -73,6 +73,7 @@ set (PKG_REQUIRED_LIST afb-daemon>=4.0 libmicrohttpd>=0.9.55 uuid + alsa>=1.1.2 ) # Prefix path where will be installed the files @@ -130,7 +131,7 @@ list(APPEND link_libraries afb-helpers) set(CONTROL_SUPPORT_LUA 1) add_definitions(-DCONTROL_PLUGIN_PATH="${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}/lib/plugins:${CMAKE_BINARY_DIR}/package/lib/plugins") add_definitions(-DCONTROL_CONFIG_PATH="${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}/etc:${CMAKE_BINARY_DIR}/package/etc") -add_definitions(-DCONTROL_LUA_PATH="${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}/data:${CMAKE_BINARY_DIR}/package/data") +add_definitions(-DCONTROL_LUA_PATH="${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}/data:${CMAKE_SOURCE_DIR}/conf.d/project/lua.d") add_definitions(-DCTL_PLUGIN_MAGIC=987456123) add_definitions(-DUSE_API_DYN=1 -DAFB_BINDING_VERSION=dyn) @@ -197,7 +198,7 @@ set(AFB_REMPORT "1234" CACHE PATH "Default binder listening port") # Print a helper message when every thing is finished # ---------------------------------------------------- -set(CLOSING_MESSAGE "Typical binding launch: afb-daemon --name afbd-${PROJECT_NAME} --port=${AFB_REMPORT} --workdir=${CMAKE_BINARY_DIR}/. --ldpath=package/lib --roothttp=package --token=\"${AFB_TOKEN}\" --tracereq=common --verbose") +set(CLOSING_MESSAGE "Typical binding launch: afb-daemon --name ${PROJECT_NAME}-afbd --port=${AFB_REMPORT} --workdir=${CMAKE_BINARY_DIR} --binding=package/lib/softmixer-binding.so --ldpath=${CMAKE_INSTALL_PREFIX}/4a-alsa-core/lib --roothttp=package/htdocs --token=\"${AFB_TOKEN}\" --tracereq=common --verbose") set(PACKAGE_MESSAGE "Install widget file using in the target : afm-util install ${PROJECT_NAME}.wgt") # Optional schema validator about now only XML, LUA and JSON diff --git a/conf.d/project/etc/4a-softmixer-config.json b/conf.d/project/etc/4a-softmixer-config.json index 9d89f07..db13705 100644 --- a/conf.d/project/etc/4a-softmixer-config.json +++ b/conf.d/project/etc/4a-softmixer-config.json @@ -3,18 +3,27 @@ "metadata": { "uid": "Soft Mixer", "version": "1.0", - "api": "soft-mixer", + "api": "softmixer", "info": "Soft Mixer emulating hardware mixer", - "require": ["alsa-core"] + "require": ["alsacore"] }, "plugins": [ { "uid": "alsa-router", "ldpath": "package/lib/plugins", + "lua2c": ["AlsaDmix", "AlsaRouter"], "info": "Map alsa-loop subdevices to 4A HAL streams" } ], + "onload": [ + { + "uid": "init-soft-mixer", + "info": "Initialise Audio Router", + "lua": "_init_softmixer_" + } + ], + "sndcards": [ { "uid": "Focusrite_Scarlett_18i8", @@ -72,11 +81,17 @@ "controls": [ { "uid": "stream", - "function": "plugin://alsa-router/stream_ctl" + "callback": { + "plugin": "alsa-router", + "function": "stream_ctl" + } }, { "uid": "zone", - "function": "plugin://alsa-router/zone_ctl" + "callback": { + "plugin": "alsa-router", + "function": "zone_ctl" + } } ] } diff --git a/conf.d/project/etc/CMakeLists.txt b/conf.d/project/etc/CMakeLists.txt index 378effc..2e7daa7 100644 --- a/conf.d/project/etc/CMakeLists.txt +++ b/conf.d/project/etc/CMakeLists.txt @@ -19,7 +19,7 @@ ################################################## # Control Policy Config file ################################################## -PROJECT_TARGET_ADD(soft-mixer-config) +PROJECT_TARGET_ADD(softmixer-config) file(GLOB CONF_FILES "*.json") diff --git a/conf.d/project/htdocs/AFB-websock.js b/conf.d/project/htdocs/AFB-websock.js new file mode 100644 index 0000000..99ab3b8 --- /dev/null +++ b/conf.d/project/htdocs/AFB-websock.js @@ -0,0 +1,177 @@ +var urlws; +var urlhttp; + +AFB = function(base, initialtoken){ + +urlws = "ws://"+window.location.host+"/"+base; +urlhttp = "http://"+window.location.host+"/"+base; + +/*********************************************/ +/**** ****/ +/**** AFB_context ****/ +/**** ****/ +/*********************************************/ +var AFB_context; +{ + var UUID = undefined; + var TOKEN = initialtoken; + + var context = function(token, uuid) { + this.token = token; + this.uuid = uuid; + } + + context.prototype = { + get token() {return TOKEN;}, + set token(tok) {if(tok) TOKEN=tok;}, + get uuid() {return UUID;}, + set uuid(id) {if(id) UUID=id;} + }; + + AFB_context = new context(); +} +/*********************************************/ +/**** ****/ +/**** AFB_websocket ****/ +/**** ****/ +/*********************************************/ +var AFB_websocket; +{ + var CALL = 2; + var RETOK = 3; + var RETERR = 4; + var EVENT = 5; + + var PROTO1 = "x-afb-ws-json1"; + + AFB_websocket = function(onopen, onabort) { + var u = urlws; + if (AFB_context.token) { + u = u + '?x-afb-token=' + AFB_context.token; + if (AFB_context.uuid) + u = u + '&x-afb-uuid=' + AFB_context.uuid; + } + this.ws = new WebSocket(u, [ PROTO1 ]); + this.pendings = {}; + this.awaitens = {}; + this.counter = 0; + this.ws.onopen = onopen.bind(this); + this.ws.onerror = onerror.bind(this); + this.ws.onclose = onclose.bind(this); + this.ws.onmessage = onmessage.bind(this); + this.onopen = onopen; + this.onabort = onabort; + this.onclose = onabort; + } + + function onerror(event) { + var f = this.onabort; + if (f) { + delete this.onopen; + delete this.onabort; + f && f(this); + } + this.onerror && this.onerror(this); + } + + function onopen(event) { + var f = this.onopen; + delete this.onopen; + delete this.onabort; + f && f(this); + } + + function onclose(event) { + for (var id in this.pendings) { + var ferr = this.pendings[id].onerror; + ferr && ferr(null, this); + } + this.pendings = {}; + this.onclose && this.onclose(); + } + + function fire(awaitens, name, data) { + var a = awaitens[name]; + if (a) + a.forEach(function(handler){handler(data);}); + var i = name.indexOf("/"); + if (i >= 0) { + a = awaitens[name.substring(0,i)]; + if (a) + a.forEach(function(handler){handler(data);}); + } + a = awaitens["*"]; + if (a) + a.forEach(function(handler){handler(data);}); + } + + function reply(pendings, id, ans, offset) { + if (id in pendings) { + var p = pendings[id]; + delete pendings[id]; + var f = p[offset]; + f(ans); + } + } + + function onmessage(event) { + var obj = JSON.parse(event.data); + var code = obj[0]; + var id = obj[1]; + var ans = obj[2]; + AFB_context.token = obj[3]; + switch (code) { + case RETOK: + reply(this.pendings, id, ans, 0); + break; + case RETERR: + reply(this.pendings, id, ans, 1); + break; + case EVENT: + default: + fire(this.awaitens, id, ans); + break; + } + } + + function close() { + this.ws.close(); + this.onabort(); + } + + function call(method, request) { + return new Promise((function(resolve, reject){ + var id, arr; + do { + id = String(this.counter = 4095 & (this.counter + 1)); + } while (id in this.pendings); + this.pendings[id] = [ resolve, reject ]; + arr = [CALL, id, method, request ]; + if (AFB_context.token) arr.push(AFB_context.token); + this.ws.send(JSON.stringify(arr)); + }).bind(this)); + } + + function onevent(name, handler) { + var id = name; + var list = this.awaitens[id] || (this.awaitens[id] = []); + list.push(handler); + } + + AFB_websocket.prototype = { + close: close, + call: call, + onevent: onevent + }; +} +/*********************************************/ +/**** ****/ +/**** ****/ +/**** ****/ +/*********************************************/ +return { + context: AFB_context, + ws: AFB_websocket +}; +}; + diff --git a/conf.d/project/htdocs/AudioBinding.css b/conf.d/project/htdocs/AudioBinding.css new file mode 100644 index 0000000..1052aa7 --- /dev/null +++ b/conf.d/project/htdocs/AudioBinding.css @@ -0,0 +1,7 @@ +pre {outline: 1px solid #ccc; padding: 5px; margin: 5px; } +.string { color: green; } +.number { color: darkorange; } +.boolean { color: blue; } +.null { color: magenta; } +.key { color: red; } + diff --git a/conf.d/project/htdocs/AudioBinding.js b/conf.d/project/htdocs/AudioBinding.js new file mode 100644 index 0000000..0f5caf9 --- /dev/null +++ b/conf.d/project/htdocs/AudioBinding.js @@ -0,0 +1,197 @@ + var afb = new AFB("api", "mysecret"); + var ws; + var sndcard="HALNotSelected"; + var evtidx=0; + var numid=0; + + function syntaxHighlight(json) { + if (typeof json !== 'string') { + json = JSON.stringify(json, undefined, 2); + } + json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); + return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) { + var cls = 'number'; + if (/^"/.test(match)) { + if (/:$/.test(match)) { + cls = 'key'; + } else { + cls = 'string'; + } + } else if (/true|false/.test(match)) { + cls = 'boolean'; + } else if (/null/.test(match)) { + cls = 'null'; + } + return '<span class="' + cls + '">' + match + '</span>'; + }); + } + + function getParameterByName(name, url) { + if (!url) { + url = window.location.href; + } + name = name.replace(/[\[\]]/g, "\\$&"); + var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), + results = regex.exec(url); + if (!results) return null; + if (!results[2]) return ''; + return decodeURIComponent(results[2].replace(/\+/g, " ")); + } + + // default soundcard is "PCH" + var devid=getParameterByName("devid"); + if (!devid) devid="hw:1"; + + var haldev=getParameterByName("haldev"); + if (!haldev) haldev="scarlett-usb"; + + var sndname=getParameterByName("sndname"); + if (!sndname) sndname="PCH"; + + var mode=getParameterByName("mode"); + if (!mode) mode="0"; + + + + + function replyok(obj) { + console.log("replyok:" + JSON.stringify(obj)); + document.getElementById("output").innerHTML = "OK: "+ syntaxHighlight(obj); + } + + function replyerr(obj) { + console.log("replyerr:" + JSON.stringify(obj)); + document.getElementById("output").innerHTML = "ERROR: "+ syntaxHighlight(obj); + } + + function gotevent(obj) { + console.log("gotevent:" + JSON.stringify(obj)); + document.getElementById("outevt").innerHTML = (evtidx++) +": "+JSON.stringify(obj); + } + + function send(message) { + var api = document.getElementById("api").value; + var verb = document.getElementById("verb").value; + document.getElementById("question").innerHTML = "subscribe: "+api+"/"+verb + " (" + JSON.stringify(message) +")"; + ws.call(api+"/"+verb, {data:message}).then(replyok, replyerr); + } + + + // On button click from HTML page + function callbinder(api, verb, query) { + console.log ("subscribe api="+api+" verb="+verb+" query=" +query); + var question = urlws +"/" +api +"/" +verb + "?query=" + JSON.stringify(query); + document.getElementById("question").innerHTML = syntaxHighlight(question); + ws.call(api+"/"+verb, query).then(replyok, replyerr); + } + + + // Retreive Select value and Text from the binder + // Note: selection of value/text for a given context is huggly!!! + function querySelectList (elemid, api, verb, query) { + + console.log("querySelectList elemid=%s api=%s verb=%s query=%s", elemid, api, verb, query); + + var selectobj = document.getElementById(elemid); + if (!selectobj) { + return; + } + + // onlick update selected HAL api + selectobj.onclick=function(){ + sndcard= this.value; + console.log ("Default Selection=" + sndcard); + }; + + function gotit (result) { + + // display response as for normal onclick action + replyok(result); + var response=result.response; + + // fulfill select with avaliable active HAL + for (idx=0; idx<response.length; idx++) { + var opt = document.createElement('option'); + + // Alsa LowLevel selection mode + if (response[idx].name) opt.text = response[idx].name; + if (response[idx].devid) opt.value = response[idx].devid; + + // HAL selection mode + if (response[idx].shortname) opt.text = response[idx].shortname; + if (response[idx].api) opt.value = response[idx].api; + + selectobj.appendChild(opt); + } + + sndcard= selectobj.value; + } + + var question = urlws +"/"+api +"/" +verb + "?query=" + JSON.stringify(query); + document.getElementById("question").innerHTML = syntaxHighlight(question); + + // request lowlevel ALSA to get API list + ws.call(api+"/"+verb, query).then(gotit, replyerr); + } + + function refresh_list (self, api, verb, query) { + console.log("refresh_list id=%s api=%s verb=%s query=%s", self.id, api, verb, query); + + if (self.value > 0) return; + + // onlick update selected HAL api + self.onclick=function(){ + numid = parseInt(self.value); + console.log ("Default numid=%d", numid); + }; + + function gotit (result) { + + // display response as for normal onclick action + replyok(result); + var response=result.response; + + + + // fulfill select with avaliable active HAL + for (idx=0; idx<response.length; idx++) { + var opt = document.createElement('option'); + + // Alsa LowLevel selection mode + opt.text = response[idx].name + ' id=' + response[idx].id; + opt.value = response[idx].id; + + self.appendChild(opt); + } + self.selectedIndex=2; + numid = parseInt (self.value); + } + + var question = urlws +"/"+api +"/" +verb + "?query=" + JSON.stringify(query); + document.getElementById("question").innerHTML = syntaxHighlight(question); + + // request lowlevel ALSA to get API list + ws.call(api+"/"+verb, query).then(gotit, replyerr); + } + + + function init(elemid, api, verb, query) { + + function onopen() { + // check for active HALs + querySelectList (elemid, api, verb, query); + + document.getElementById("main").style.visibility = "visible"; + document.getElementById("connected").innerHTML = "Binder WS Active"; + document.getElementById("connected").style.background = "lightgreen"; + ws.onevent("*", gotevent); + } + + function onabort() { + document.getElementById("main").style.visibility = "hidden"; + document.getElementById("connected").innerHTML = "Connected Closed"; + document.getElementById("connected").style.background = "red"; + + } + ws = new afb.ws(onopen, onabort); + } diff --git a/conf.d/project/htdocs/CMakeLists.txt b/conf.d/project/htdocs/CMakeLists.txt new file mode 100644 index 0000000..ce916fa --- /dev/null +++ b/conf.d/project/htdocs/CMakeLists.txt @@ -0,0 +1,34 @@ +########################################################################### +# Copyright 2015, 2016, 2017 IoT.bzh +# +# author: Fulup Ar Foll <fulup@iot.bzh> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +########################################################################### + + + +################################################## +# HTML Testing Files +################################################## +PROJECT_TARGET_ADD(softmixer-html5) + + file(GLOB SOURCE_FILES "*.html" "*.js" "*.jpg" "*.css") + + add_input_files("${SOURCE_FILES}") + + SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES + LABELS "HTDOCS" + OUTPUT_NAME ${TARGET_NAME} + ) + diff --git a/conf.d/project/htdocs/README.md b/conf.d/project/htdocs/README.md new file mode 100644 index 0000000..7aafea0 --- /dev/null +++ b/conf.d/project/htdocs/README.md @@ -0,0 +1,7 @@ +------------------------------------------------------------------------ + Basic HTML & WS test +------------------------------------------------------------------------ + + # Load bindings directly from development tree for debug + AFB_BINDER_NAME='sample' afb-daemon --verbose --verbose --token="" --ldpaths=build --port=1234 --roothttp=htdocs + diff --git a/conf.d/project/htdocs/index.html b/conf.d/project/htdocs/index.html new file mode 100644 index 0000000..9a7d66f --- /dev/null +++ b/conf.d/project/htdocs/index.html @@ -0,0 +1,43 @@ +<html> +<head> + <title>Simple COntroller Test</title> + <link rel="stylesheet" href="AudioBinding.css"> + <script type="text/javascript" src="AFB-websock.js"></script> + <script type="text/javascript" src="AudioBinding.js"></script> +</head> + +<body onload="init('hal_registry','alsacore', 'hallist')"> + + <h1>Simple Control Test</h1> + <button id="connected" onclick="init()">Binder WS Fail</button> + <button id="mnitoring" onclick="window.open('/monitoring/monitor.html','_monitor_ctl')">Debug/Monitoring</a></button> + <br><br> + + <h2>V2 API CALL</h2> + <ol> + <li><button onclick="callbinder('pol4a','request', {'uid':'navigation-role'});">Navigation Open</button></li> + <li><button onclick="callbinder('pol4a','request', {'uid':'emergency-role'});">Emergency Open</button></li> + <br> + <li><button onclick="callbinder('pol4a', 'request', {'uid':'release-current'});">Release Current Role</button></li> + </ol> + + <h2>V3 API CALL</h2> + <ol> + <li><button onclick="callbinder('pol4a','navigation-role');">Navigation Open</button></li> + <li><button onclick="callbinder('pol4a','emergency-role');">Emergency Open</button></li> + <br> + <li><button onclick="callbinder('pol4a','release-current');">Release Current Role</button></li> + </ol> + + <h2>Signal/Timeout</h2> + <ol> + <li><button onclick="callbinder('pol4a','signal-timeout', {'timeout':3, 'data':'state', 'event':'quit'});">Get Signal in 3s</button></li> + </ol> + + <div id="main" style="visibility:hidden"> + <ol> + <li>Question <pre id="question"></pre> + <li>Response <pre id="output"></pre> + <li>Events: <pre id="outevt"></pre> + </ol> + </div> diff --git a/conf.d/project/lua.d/CMakeLists.txt b/conf.d/project/lua.d/CMakeLists.txt new file mode 100644 index 0000000..5171349 --- /dev/null +++ b/conf.d/project/lua.d/CMakeLists.txt @@ -0,0 +1,32 @@ +########################################################################### +# Copyright 2017 IoT.bzh +# +# author: Fulup Ar Foll <fulup@iot.bzh> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +########################################################################### + + +################################################## +# Soft Mixer Lua Scripts +################################################## +PROJECT_TARGET_ADD(softmixer-lua) + + file(GLOB LUA_FILES "*.lua") + + add_input_files("${LUA_FILES}") + + SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES + LABELS "DATA" + OUTPUT_NAME ${TARGET_NAME} + ) diff --git a/conf.d/project/lua.d/softmixer-00.lua b/conf.d/project/lua.d/softmixer-00.lua new file mode 100644 index 0000000..29d2c70 --- /dev/null +++ b/conf.d/project/lua.d/softmixer-00.lua @@ -0,0 +1,86 @@ +--[[ + Copyright (C) 2016 "IoT.bzh" + Author Fulup Ar Foll <fulup@iot.bzh> + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Note: this file should be called before any other to assert declare function + is loaded before anything else. + + References: + http://lua-users.org/wiki/DetectingUndefinedVariables + +--]] + + +--=================================================== +--= Niklas Frykholm +-- basically if user tries to create global variable +-- the system will not let them!! +-- call GLOBAL_lock(_G) +-- +--=================================================== +function GLOBAL_lock(t) + local mt = getmetatable(t) or {} + mt.__newindex = lock_new_index + setmetatable(t, mt) +end + +--=================================================== +-- call GLOBAL_unlock(_G) +-- to change things back to normal. +--=================================================== +function GLOBAL_unlock(t) + local mt = getmetatable(t) or {} + mt.__newindex = unlock_new_index + setmetatable(t, mt) +end + +function lock_new_index(t, k, v) + if (string.sub(k,1,1) ~= "_") then + GLOBAL_unlock(_G) + error("GLOBALS are locked -- " .. k .. + " must be declared local or prefix with '_' for globals.", 2) + else + rawset(t, k, v) + end +end + +function unlock_new_index(t, k, v) + rawset(t, k, v) +end + +-- return serialised version of printable table +function Dump_Table(o) + if type(o) == 'table' then + local s = '{ ' + for k,v in pairs(o) do + if type(k) ~= 'number' then k = '"'..k..'"' end + s = s .. '['..k..'] = ' .. Dump_Table(v) .. ',' + end + return s .. '} ' + else + return tostring(o) + end +end + + +-- simulate C prinf function +printf = function(s,...) + io.write(s:format(...)) + io.write("\n") + return +end + +-- lock global variable +GLOBAL_lock(_G) diff --git a/conf.d/project/lua.d/softmixer-01.lua b/conf.d/project/lua.d/softmixer-01.lua new file mode 100644 index 0000000..d4ae580 --- /dev/null +++ b/conf.d/project/lua.d/softmixer-01.lua @@ -0,0 +1,46 @@ +--[[ + Copyright (C) 2016 "IoT.bzh" + Author Fulup Ar Foll <fulup@iot.bzh> + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + NOTE: strict mode: every global variables should be prefixed by '_' +--]] + +-- Static variables should be prefixed with _ +_EventHandle={} + +-- Call when AlsaCore return HAL active list +function _AlsaPingCB_ (source, result, context) + + AFB:notice (source, "--InLua-- PingCB: result='%s'", Dump_Table(result)) + +end + +-- Display receive arguments and echo them to caller +function _init_softmixer_ (source, args) + + -- create event to push change audio roles to potential listeners + _EventHandle=AFB:evtmake(source, "control") + + -- get list of supported HAL devices + AFB:service(source, "alsacore","ping", {}, "_AlsaPingCB_", {}) + + -- test Lua2C plugin + L2C:alsadmix(source, {}) + + AFB:notice (source, "--InLua-- _init_softmixer_ done") + + return 0 -- happy end +end diff --git a/mixer-binding/CMakeLists.txt b/mixer-binding/CMakeLists.txt index bc4ebe6..680c2c4 100644 --- a/mixer-binding/CMakeLists.txt +++ b/mixer-binding/CMakeLists.txt @@ -18,7 +18,7 @@ # Add target to project dependency list -PROJECT_TARGET_ADD(mixer-binding) +PROJECT_TARGET_ADD(softmixer-binding) # Define project Targets ADD_LIBRARY(${TARGET_NAME} MODULE mixer-binding.c) @@ -47,4 +47,4 @@ PROJECT_TARGET_ADD(mixer-binding) ) # make sure config is copied before starting - add_dependencies(${TARGET_NAME} soft-mixer-config)
\ No newline at end of file + add_dependencies(${TARGET_NAME} softmixer-config softmixer-lua softmixer-html5)
\ No newline at end of file diff --git a/mixer-binding/mixer-binding.c b/mixer-binding/mixer-binding.c index 8fb4b50..15d13cd 100644 --- a/mixer-binding/mixer-binding.c +++ b/mixer-binding/mixer-binding.c @@ -29,6 +29,7 @@ PUBLIC afb_dynapi *AFB_default; // Config Section definition (note: controls section index should match handle retrieval in HalConfigExec) static CtlSectionT ctrlSections[]= { {.key="plugins" , .loadCB= PluginConfig}, + {.key="onload" , .loadCB= OnloadConfig}, {.key="controls", .loadCB= ControlConfig}, {.key=NULL} diff --git a/nbproject/configurations.xml b/nbproject/configurations.xml index 8e36718..8a74046 100644 --- a/nbproject/configurations.xml +++ b/nbproject/configurations.xml @@ -13,6 +13,7 @@ <in>ctl-control.c</in> <in>ctl-event.c</in> <in>ctl-lua.c</in> + <in>ctl-onload.c</in> <in>ctl-plugin.c</in> <in>ctl-timer.c</in> </df> @@ -20,6 +21,12 @@ <df name="mixer-binding"> <in>mixer-binding.c</in> </df> + <df name="plugins"> + <df name="alsa"> + <in>alsa-lua.c</in> + <in>alsa-softmixer.c</in> + </df> + </df> </df> <logicalFolder name="ExternalFiles" displayName="Important Files" @@ -118,6 +125,13 @@ <cTool flags="0"> </cTool> </item> + <item path="app-controller-submodule/ctl-lib/ctl-onload.c" + ex="false" + tool="0" + flavor2="3"> + <cTool flags="0"> + </cTool> + </item> <item path="app-controller-submodule/ctl-lib/ctl-plugin.c" ex="false" tool="0" @@ -159,6 +173,10 @@ <cTool flags="0"> </cTool> </item> + <item path="plugins/alsa/alsa-lua.c" ex="false" tool="0" flavor2="0"> + </item> + <item path="plugins/alsa/alsa-softmixer.c" ex="false" tool="0" flavor2="0"> + </item> </conf> </confs> </configurationDescriptor> diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 644e359..28a0609 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1,13 +1,13 @@ ########################################################################### # Copyright 2015, 2016, 2017 IoT.bzh # -# author: Romain Forlot <romain.forlot@iot.bzh> +# author: Fulup Ar Foll <fulup@iot.bzh> # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, @@ -17,24 +17,6 @@ ########################################################################### -PROJECT_TARGET_ADD(alsa-router) - - # Define targets - ADD_LIBRARY(${TARGET_NAME} MODULE ${TARGET_NAME}.c) - - # Alsa Plugin properties - SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES - LABELS "PLUGIN" - PREFIX "" - SUFFIX ".ctlso" - OUTPUT_NAME ${TARGET_NAME} - ) - - # Library dependencies (include updates automatically) - TARGET_LINK_LIBRARIES(${TARGET_NAME} - ${link_libraries} - ) - - target_include_directories(${TARGET_NAME} - PRIVATE "../app-controller-submodule/ctl-lib" - PRIVATE "../mixer-binding") +# Include any directory not starting with _ +# ----------------------------------------------------- +PROJECT_SUBDIRS_ADD(${PROJECT_SRC_DIR_PATTERN}) diff --git a/plugins/alsa/CMakeLists.txt b/plugins/alsa/CMakeLists.txt new file mode 100644 index 0000000..51f4639 --- /dev/null +++ b/plugins/alsa/CMakeLists.txt @@ -0,0 +1,42 @@ +########################################################################### +# Copyright 2015, 2016, 2017 IoT.bzh +# +# author: Romain Forlot <romain.forlot@iot.bzh> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +########################################################################### + + +PROJECT_TARGET_ADD(alsa-router) + + file(GLOB SOURCE_FILES "alsa-*.c") + + # Define targets + ADD_LIBRARY(${TARGET_NAME} MODULE ${SOURCE_FILES}) + + # Alsa Plugin properties + SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES + LABELS "PLUGIN" + PREFIX "" + SUFFIX ".ctlso" + OUTPUT_NAME ${TARGET_NAME} + ) + + # Library dependencies (include updates automatically) + TARGET_LINK_LIBRARIES(${TARGET_NAME} + ${link_libraries} + ) + + target_include_directories(${TARGET_NAME} + PRIVATE "${CMAKE_SOURCE_DIR}/app-controller-submodule/ctl-lib" + PRIVATE "${CMAKE_SOURCE_DIR}/mixer-binding") diff --git a/plugins/alsa/alsa-dmix.c b/plugins/alsa/alsa-dmix.c new file mode 100644 index 0000000..b162d04 --- /dev/null +++ b/plugins/alsa/alsa-dmix.c @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2018 "IoT.bzh" + * Author Fulup Ar Foll <fulup@iot.bzh> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#define _GNU_SOURCE // needed for vasprintf + +#include "alsa-softmixer.h" + +static int uniqueIpcIndex = 1024; + + +PUBLIC int AlsaCreateDmix(CtlSourceT *source) { + + AFB_ApiNotice(source->api, "AlsaCreateDmix: start "); + + int cardid= AlsaCardInfoByPath ("/dev/snd/by-id/usb-Focusrite_Scarlett_18i8_USB_10004EE6-00"); + AFB_ApiNotice(source->api, "AlsaCreateDmix: cardid=%d ", cardid); + + snd_pcm_t *dmixPcm; + snd_config_t *dmixConfig, *slaveConfig, *elemConfig; + snd_pcm_stream_t streamPcm = SND_PCM_STREAM_PLAYBACK; + int error = 0, streamMode = SND_PCM_NONBLOCK; + + + error += snd_config_top(&dmixConfig); + error += snd_config_imake_integer(&elemConfig, "ipc_key", uniqueIpcIndex++); + error += snd_config_add(dmixConfig, elemConfig); + if (error) goto OnErrorExit; + + error += snd_config_make_compound(&slaveConfig, "slave", 0); + error += snd_config_imake_string(&elemConfig, "pcm", "hw:6"); + error += snd_config_add(slaveConfig, elemConfig); + if (error) goto OnErrorExit; + + // add leaf into config + error += snd_config_add(dmixConfig, slaveConfig); + if (error) goto OnErrorExit; + + snd_config_update(); + AlsaDumpConfig (source, snd_config, 1); + AlsaDumpConfig (source, dmixConfig, 1); + + + int status = _snd_pcm_dmix_open(&dmixPcm, "MyDMix", snd_config, dmixConfig, streamPcm , streamMode); + + AFB_ApiNotice(source->api, "AlsaCreateDmix: done"); + + return status; + +OnErrorExit: + AFB_ApiNotice(source->api, "AlsaCreateDmix: OnErrorExit"); + return 1; +}
\ No newline at end of file diff --git a/plugins/alsa/alsa-lua.c b/plugins/alsa/alsa-lua.c new file mode 100644 index 0000000..8f1329f --- /dev/null +++ b/plugins/alsa/alsa-lua.c @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017 "IoT.bzh" + * Author Fulup Ar Foll <fulup@iot.bzh> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#define _GNU_SOURCE // needed for vasprintf + +#include "alsa-softmixer.h" + + +// Force Lua2cWrapper inclusion within already existing plugin +Lua2cWrapperT Lua2cWrap; + +CTLP_LUA2C (AlsaDmix, source, argsJ, responseJ) { + json_object* subscribeArgsJ = NULL; + + int err = 0; + wrap_json_pack(&subscribeArgsJ, "{ss}", "value", "location"); + AFB_ApiNotice(source->api, "--lua2c-- AlsaDmix"); + + return err; +} + +CTLP_LUA2C (AlsaRouter, source, argsJ, responseJ) { + json_object* subscribeArgsJ = NULL; + + int err = 0; + wrap_json_pack(&subscribeArgsJ, "{ss}", "value", "location"); + AFB_ApiNotice(source->api, "lua2c router with %s", json_object_to_json_string_ext(subscribeArgsJ, JSON_C_TO_STRING_PRETTY)); + + return err; +} diff --git a/plugins/alsa/alsa-softmixer.c b/plugins/alsa/alsa-softmixer.c new file mode 100644 index 0000000..bf195fb --- /dev/null +++ b/plugins/alsa/alsa-softmixer.c @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2017 "IoT.bzh" + * Author Fulup Ar Foll <fulup@iot.bzh> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#define _GNU_SOURCE // needed for vasprintf + +#include "alsa-softmixer.h" + +CTLP_CAPI_REGISTER("alsa-mixer"); + +// Call at initialisation time +CTLP_ONLOAD(plugin, callbacks) { + AFB_ApiDebug (plugin->api, "SoftMixer plugin: uid='%s' 'info='%s'", plugin->uid, plugin->info); + + // fake action call during init for debug + CtlSourceT source; + source.api = plugin->api; + AlsaCreateDmix (&source); + + return 0; +} + +CTLP_CAPI (zone_ctl, source, argsJ, eventJ) { + json_object* subscribeArgsJ = NULL; + + int err = 0; + wrap_json_pack(&subscribeArgsJ, "{ss}", "value", "location"); + AFB_ApiDebug(source->api, "Calling zone_ctl with %s", json_object_to_json_string_ext(subscribeArgsJ, JSON_C_TO_STRING_PRETTY)); + + return err; +} + +CTLP_CAPI (stream_ctl, source, argsJ, eventJ) { + json_object* subscribeArgsJ = NULL; + + int err = 0; + wrap_json_pack(&subscribeArgsJ, "{ss}", "value", "location"); + AFB_ApiDebug(source->api, "Calling stream_ctl with %s", json_object_to_json_string_ext(subscribeArgsJ, JSON_C_TO_STRING_PRETTY)); + + return err; +} diff --git a/plugins/alsa-router.c b/plugins/alsa/alsa-softmixer.h index e0e7eb2..d413929 100644 --- a/plugins/alsa-router.c +++ b/plugins/alsa/alsa-softmixer.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 2016 "IoT.bzh" - * Author Romain Forlot <romain.forlot@iot.bzh> + * Copyright (C) 2018 "IoT.bzh" + * Author Fulup Ar Foll <fulup@iot.bzh> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,9 @@ #define _GNU_SOURCE // needed for vasprintf +#ifndef _ALSA_SOFTMIXER_ +#define _ALSA_SOFTMIXER_ + #include <afb/afb-binding.h> #include <systemd/sd-event.h> #include <json-c/json_object.h> @@ -27,20 +30,20 @@ #include "ctl-plugin.h" #include "wrap-json.h" -CTLP_CAPI_REGISTER("alsa-mixer"); +#include <alsa/asoundlib.h> + +// Provide proto for LibASound low level API +int _snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name, + snd_config_t *root, snd_config_t *conf, + snd_pcm_stream_t stream, int mode); + +// alsa-tools-high.c +PUBLIC void AlsaDumpConfig (CtlSourceT *source, snd_config_t *config, int indent); -// Call at initialisation time -/*CTLP_ONLOAD(plugin, callbacks) { - AFB_NOTICE ("GPS plugin: uid='%s' 'info='%s'", plugin->uid, plugin->info); - return api; -}*/ +// alsa-tools-low.c +PUBLIC int AlsaCardInfoByPath (const char *control); -CTLP_CAPI (zone_action, source, argsJ, eventJ) { - json_object* subscribeArgsJ = NULL, *responseJ = NULL; - int err = 0; - wrap_json_pack(&subscribeArgsJ, "{ss}", "value", "location"); - AFB_ApiDebug(source->api, "Calling zone_action with %s", json_object_to_json_string_ext(subscribeArgsJ, JSON_C_TO_STRING_PRETTY)); +PUBLIC int AlsaCreateDmix (CtlSourceT *source); - return err; -} +#endif
\ No newline at end of file diff --git a/plugins/alsa/alsa-tools-high.c b/plugins/alsa/alsa-tools-high.c new file mode 100644 index 0000000..a86ba2e --- /dev/null +++ b/plugins/alsa/alsa-tools-high.c @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2018 "IoT.bzh" + * Author Fulup Ar Foll <fulup@iot.bzh> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#define _GNU_SOURCE // needed for vasprintf + + +#include "alsa-softmixer.h" + + +PUBLIC void AlsaDumpConfig(CtlSourceT *source, snd_config_t *config, int indent) { + snd_config_iterator_t it, next; + + // hugly hack to get minimalist indentation + char *pretty = alloca(indent + 1); + for (int idx = 0; idx < indent; idx++) pretty[idx] = '-'; + pretty[indent] = '\0'; + + snd_config_for_each(it, next, config) { + snd_config_t *node = snd_config_iterator_entry(it); + const char *key; + + // ignore comment en empty lines + if (snd_config_get_id(node, &key) < 0) continue; + + switch (snd_config_get_type(node)) { + long valueI; + const char *valueS; + + case SND_CONFIG_TYPE_INTEGER: + snd_config_get_integer(node, &valueI); + AFB_ApiNotice(source->api, "DumpAlsaConfig: %s %s: %d (int)", pretty, key, (int) valueI); + break; + + case SND_CONFIG_TYPE_STRING: + snd_config_get_string(node, &valueS); + AFB_ApiNotice(source->api, "DumpAlsaConfig: %s %s: %s (str)", pretty, key, valueS); + break; + + case SND_CONFIG_TYPE_COMPOUND: + AFB_ApiNotice(source->api, "DumpAlsaConfig: %s %s { ", pretty, key); + AlsaDumpConfig(source, node, indent + 2); + AFB_ApiNotice(source->api, "DumpAlsaConfig: %s } ", pretty); + break; + + default: + snd_config_get_string(node, &valueS); + AFB_ApiNotice(source->api, "DumpAlsaConfig: %s: key=%s unknown=%s", pretty, key, valueS); + break; + } + } +} diff --git a/plugins/alsa/alsa-tools-low.c b/plugins/alsa/alsa-tools-low.c new file mode 100644 index 0000000..57b5e5c --- /dev/null +++ b/plugins/alsa/alsa-tools-low.c @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2018 "IoT.bzh" + * Author Fulup Ar Foll <fulup@iot.bzh> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#define _GNU_SOURCE // needed for vasprintf + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <fcntl.h> +#include <sys/ioctl.h> + +#include "alsa-softmixer.h" + +// extracted IOCTLs from <alsa/asoundlib.h> +#define _IOR_HACKED(type,nr,size) _IOC(_IOC_READ,(type),(nr),size) +#define SNDRV_CTL_IOCTL_CARD_INFO(size) _IOR_HACKED('U', 0x01, size) + +// Clone of AlsaLib snd_card_load2 static function +int AlsaCardInfoByPath (const char *devpath) { + int open_dev; + snd_ctl_card_info_t *cardinfo = alloca(snd_ctl_card_info_sizeof()); + + open_dev = open(devpath, O_RDONLY); + if (open_dev >= 0) { + int rc = ioctl(open_dev, SNDRV_CTL_IOCTL_CARD_INFO(snd_ctl_card_info_sizeof()), cardinfo); + if (rc < 0) { + int err = -errno; + close(open_dev); + return err; + } + close(open_dev); + int cardId = snd_ctl_card_info_get_card(cardinfo); + return cardId; + } else { + return -errno; + } +} + |