/* * AlsaLibMapping -- provide low level interface with ALSA lib (extracted from alsa-json-gateway code) * Copyright (C) 2015,2016,2017, 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. References: https://github.com/fulup-bzh/AlsaJsonGateway (original code) http://alsa-lib.sourcearchive.com/documentation/1.0.20/modules.html http://alsa-lib.sourcearchive.com/documentation/1.0.8/group__Control_gd48d44da8e3bfe150e928267008b8ff5.html http://alsa.opensrc.org/HowTo_access_a_mixer_control https://github.com/gch1p/alsa-volume-monitor/blob/master/main.c https://github.com/DongheonKim/android_hardware_alsa-sound/blob/master/ALSAControl.cpp (ALSA low level API) https://www.kernel.org/doc/html/v4.11/sound/index.html */ #define _GNU_SOURCE // needed for vasprintf #include "Alsa-ApiHat.h" PUBLIC void NumidsListParse(ActionSetGetT action, queryValuesT *queryValues, ctlRequestT *ctlRequest) { int length; for (int idx = 0; idx < queryValues->count; idx++) { json_object *jId, *valuesJ; ctlRequest[idx].used = 0; ctlRequest[idx].valuesJ = NULL; // when only one NUMID is provided it might not be encapsulated in a JSON array if (json_type_array == json_object_get_type(queryValues->numidsJ)) ctlRequest[idx].jToken = json_object_array_get_idx(queryValues->numidsJ, idx); else ctlRequest[idx].jToken = queryValues->numidsJ; enum json_type jtype = json_object_get_type(ctlRequest[idx].jToken); switch (jtype) { case json_type_int: // if NUMID is not an array then it should be an integer numid with no value ctlRequest[idx].numId = json_object_get_int(ctlRequest[idx].jToken); // Special SET simple short numid form [numid, [VAL1...VALX]] if (action == ACTION_SET && queryValues->count == 2) { ctlRequest[idx].valuesJ = json_object_array_get_idx(queryValues->numidsJ, 1); queryValues->count = 1; //In this form count==2 , when only one numid is to set idx++; continue; } else break; case json_type_array: // NUMID is an array 1st slot should be numid, optionally values may come after length = json_object_array_length(ctlRequest[idx].jToken); // numid must be in 1st slot of numid json array ctlRequest[idx].numId = json_object_get_int(json_object_array_get_idx(ctlRequest[idx].jToken, 0)); if (action == ACTION_GET) continue; // In Write mode second value should be the value if (action == ACTION_SET && length == 2) { ctlRequest[idx].valuesJ = json_object_array_get_idx(ctlRequest[idx].jToken, 1); continue; } // no numid value ctlRequest[idx].used = -1; break; case json_type_object: // numid+values formated as {id:xxx, val:[aa,bb...,nn]} if (!json_object_object_get_ex(ctlRequest[idx].jToken, "id", &jId) || !json_object_object_get_ex(ctlRequest[idx].jToken, "val", &valuesJ)) { AFB_NOTICE("Invalid Json=%s missing 'id'|'val'", json_object_get_string(ctlRequest[idx].jToken)); ctlRequest[idx].used = -1; } else { ctlRequest[idx].numId = json_object_get_int(jId); if (action == ACTION_SET) ctlRequest[idx].valuesJ = valuesJ; } break; default: ctlRequest[idx].used = -1; } } } STATIC json_object *DB2StringJsonOject(long dB) { char label [20]; if (dB < 0) { snprintf(label, sizeof (label), "-%li.%02lidB", -dB / 100, -dB % 100); } else { snprintf(label, sizeof (label), "%li.%02lidB", dB / 100, dB % 100); } // json function takes care of string copy return (json_object_new_string(label)); } // Direct port from amixer TLV decode routine. This code is too complex for me. // I hopefully did not break it when porting it. STATIC json_object *decodeTlv(unsigned int *tlv, unsigned int tlv_size, int mode) { char label[20]; unsigned int type = tlv[0]; unsigned int size; unsigned int idx = 0; const char *chmap_type = NULL; json_object * decodeTlvJson = json_object_new_object(); if (tlv_size < (unsigned int) (2 * sizeof (unsigned int))) { printf("TLV size error!\n"); return NULL; } type = tlv[idx++]; size = tlv[idx++]; tlv_size -= (unsigned int) (2 * sizeof (unsigned int)); if (size > tlv_size) { fprintf(stderr, "TLV size error (%i, %i, %i)!\n", type, size, tlv_size); return NULL; } switch (type) { case SND_CTL_TLVT_CONTAINER: { json_object * containerJson = json_object_new_array(); size += (unsigned int) (sizeof (unsigned int) - 1); size /= (unsigned int) (sizeof (unsigned int)); while (idx < size) { json_object *embedJson; if (tlv[idx + 1] > (size - idx) * sizeof (unsigned int)) { fprintf(stderr, "TLV size error in compound!\n"); return NULL; } embedJson = decodeTlv(tlv + idx, tlv[idx + 1] + 8, mode); json_object_array_add(containerJson, embedJson); idx += (unsigned int) (2 + (tlv[idx + 1] + sizeof (unsigned int) - 1) / sizeof (unsigned int)); } json_object_object_add(decodeTlvJson, "container", containerJson); break; } case SND_CTL_TLVT_DB_SCALE: { json_object * dbscaleJson = json_object_new_object(); if (size != 2 * sizeof (unsigned int)) { json_object * arrayJson = json_object_new_array(); while (size > 0) { if (mode >= QUERY_VERBOSE) { snprintf(label, sizeof (label), "0x%08x,", tlv[idx++]); json_object_array_add(arrayJson, json_object_new_string(label)); } else { json_object_array_add(arrayJson, json_object_new_int(tlv[idx++])); } size -= (unsigned int) sizeof (unsigned int); } json_object_object_add(dbscaleJson, "array", arrayJson); } else { if (mode >= QUERY_VERBOSE) { json_object_object_add(dbscaleJson, "min", DB2StringJsonOject((int) tlv[2])); json_object_object_add(dbscaleJson, "step", DB2StringJsonOject(tlv[3] & 0xffff)); json_object_object_add(dbscaleJson, "mute", DB2StringJsonOject((tlv[3] >> 16) & 1)); } else { json_object_object_add(dbscaleJson, "min", json_object_new_int((int) tlv[2])); json_object_object_add(dbscaleJson, "step", json_object_new_int(tlv[3] & 0xffff)); json_object_object_add(dbscaleJson, "mute", json_object_new_int((tlv[3] >> 16) & 1)); } } json_object_object_add(decodeTlvJson, "dbscale", dbscaleJson); break; } #ifdef SND_CTL_TLVT_DB_LINEAR case SND_CTL_TLVT_DB_LINEAR: { json_object * dbLinearJson = json_object_new_object(); if (size != 2 * sizeof (unsigned int)) { json_object * arrayJson = json_object_new_array(); while (size > 0) { if (mode >= QUERY_VERBOSE) { snprintf(label, sizeof (label), "0x%08x,", tlv[idx++]); json_object_array_add(arrayJson, jso
/*
 * 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, something express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Reference:
 *   Json load using json_unpack https://jansson.readthedocs.io/en/2.9/apiref.html#parsing-and-validating-values
 */

#ifndef _CTL_CONFIG_INCLUDE_
#define _CTL_CONFIG_INCLUDE_

#ifdef __cplusplus
extern "C" {
#endif

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include "ctl-plugin.h"
#include <filescan-utils.h>
#include <wrap-json.h>

#ifdef CONTROL_SUPPORT_LUA
  #include "ctl-lua.h"
#endif

#ifndef CONTROL_MAXPATH_LEN
  #define CONTROL_MAXPATH_LEN 255
#endif

#ifndef CONTROL_CONFIG_PRE
  #define CONTROL_CONFIG_PRE "onload"
#endif

#ifndef CTL_PLUGIN_EXT
  #define CTL_PLUGIN_EXT ".ctlso"
  #define CTL_SCRIPT_EXT ".lua"
#endif

#define LUA_ACTION_PREFIX "lua://"
#define API_ACTION_PREFIX "api://"
#define PLUGIN_ACTION_PREFIX "plugin://"

typedef struct ConfigSectionS {
  const char *key;
  const char *uid;
  const char *info;
  int (*loadCB)(AFB_ApiT apihandle, struct ConfigSectionS *section, json_object *sectionJ);
  void *handle;
  CtlActionT *actions;
} CtlSectionT;

typedef struct {
    const char* api;
    const char* uid;
    const char *info;
    const char *version;
    json_object *configJ;
    json_object *requireJ;
    CtlSectionT *sections;
    void *external;
} CtlConfigT;

// This should not be global as application may want to define their own sections
typedef enum {
  CTL_SECTION_PLUGIN,
  CTL_SECTION_ONLOAD,
  CTL_SECTION_CONTROL,
  CTL_SECTION_EVENT,
  CTL_SECTION_HAL,

  CTL_SECTION_ENDTAG,
} SectionEnumT;

// ctl-action.c
CtlActionT *ActionConfig(AFB_ApiT apiHandle, json_object *actionsJ,  int exportApi);
void ActionExecUID(AFB_ReqT request, CtlConfigT *ctlConfig, const char *uid, json_object *queryJ);
int ActionExecOne( CtlSourceT *source, CtlActionT* action, json_object *queryJ);
int ActionLoadOne(AFB_ApiT apiHandle, CtlActionT *action, json_object *, int exportApi);
int ActionLabelToIndex(CtlActionT* actions, const char* actionLabel);

// ctl-config.c
int CtlConfigMagicNew();
json_object* CtlConfigScan(const char *dirList, const char *prefix) ;
char* ConfigSearch(AFB_ApiT apiHandle, json_object *responseJ);
char* CtlConfigSearch(AFB_ApiT apiHandle, const char *dirList, const char *prefix) ;
int CtlConfigExec(AFB_ApiT apiHandle, CtlConfigT *ctlConfig) ;
CtlConfigT *CtlLoadMetaData(AFB_ApiT apiHandle,const char* filepath) ;
int CtlLoadSections(AFB_ApiT apiHandle, CtlConfigT *ctlHandle, CtlSectionT *sections);

// ctl-event.c
int EventConfig(AFB_ApiT apihandle, CtlSectionT *section, json_object *actionsJ);
#ifdef AFB_BINDING_PREV3
void CtrlDispatchApiEvent (AFB_ApiT apiHandle, const char *evtLabel, struct json_object *eventJ);
#else
void CtrlDispatchV2Event(const char *evtLabel, json_object *eventJ);
#endif

// ctl-control.c
int ControlConfig(AFB_ApiT apiHandle, CtlSectionT *section, json_object *actionsJ);

// ctl-onload.c
int OnloadConfig(AFB_ApiT apiHandle, CtlSectionT *section, json_object *actionsJ);

// ctl-plugin.c
int PluginConfig(AFB_ApiT UNUSED_ARG(apiHandle), CtlSectionT *section, json_object *pluginsJ);
int PluginGetCB (AFB_ApiT apiHandle, CtlActionT *action , json_object *callbackJ);

#ifdef __cplusplus
}
#endif

#endif /* _CTL_CONFIG_INCLUDE_ */
n_object_get_type(numidsJ); switch (jtype) { case json_type_array: queryValues.numidsJ = numidsJ; queryValues.count = json_object_array_length(numidsJ); break; case json_type_int: case json_type_object: queryValues.count = 1; queryValues.numidsJ = numidsJ; break; default: afb_req_fail_f(request, "numid-notarray", "NumId=%s NumId not valid JSON array", json_object_get_string(numidsJ)); goto OnErrorExit; } } if ((err = snd_ctl_open(&ctlDev, queryValues.devid, 0)) < 0) { afb_req_fail_f(request, "sndcrl-notfound", "devid='%s' load fail error=%s\n", queryValues.devid, snd_strerror(err)); goto OnErrorExit; } snd_ctl_elem_list_alloca(&ctlList); if ((err = snd_ctl_elem_list(ctlDev, ctlList)) < 0) { afb_req_fail_f(request, "listInit-failed", "devid='%s' load fail error=%s\n", queryValues.devid, snd_strerror(err)); goto OnErrorExit; } if ((err = snd_ctl_elem_list_alloc_space(ctlList, snd_ctl_elem_list_get_count(ctlList))) < 0) { afb_req_fail_f(request, "listAlloc-failed", "devid='%s' load fail error=%s\n", queryValues.devid, snd_strerror(err)); goto OnErrorExit; } if ((err = snd_ctl_elem_list(ctlDev, ctlList)) < 0) { afb_req_fail_f(request, "listOpen-failed", "devid='%s' load fail error=%s\n", queryValues.devid, snd_strerror(err)); goto OnErrorExit; } // Parse numids string (empty == all) ctlCount = snd_ctl_elem_list_get_used(ctlList); if (queryValues.count == 0) { ctlRequest = alloca(sizeof (ctlRequestT)*(ctlCount)); } else { ctlRequest = alloca(sizeof (ctlRequestT)*(queryValues.count)); NumidsListParse(action, &queryValues, ctlRequest); } // if more than one crl requested prepare an array for response if (queryValues.count != 1 && action == ACTION_GET) sndctls = json_object_new_array(); else sndctls = NULL; // Loop on all ctlDev controls for (int ctlIndex = 0; ctlIndex < ctlCount; ctlIndex++) { unsigned int selected = 0; int jdx; if (queryValues.count == 0 && action == ACTION_GET) { selected = 1; // check is this numid is selected within query jdx = ctlIndex; // map all existing ctl as requested } else { int numid = snd_ctl_elem_list_get_numid(ctlList, ctlIndex); if (numid < 0) { AFB_NOTICE("snd_ctl_elem_list_get_numid index=%d fail", ctlIndex); continue; } // check if current control was requested in query numids list for (jdx = 0; jdx < queryValues.count; jdx++) { if (numid == ctlRequest[jdx].numId) { selected = 1; break; } } } // control is selected open ctlid and get value if (selected) { snd_ctl_elem_id_t *elemId; snd_ctl_elem_id_alloca(&elemId); snd_ctl_elem_list_get_id(ctlList, ctlIndex, elemId); switch (action) { case ACTION_GET: err = alsaGetSingleCtl(ctlDev, elemId, &ctlRequest[jdx], queryValues.mode); break; case ACTION_SET: err = alsaSetSingleCtl(ctlDev, elemId, &ctlRequest[jdx]); break; default: err = 1; } if (err) status++; else { // Do not embed response in an array when only one ctl was requested if (action == ACTION_GET) { if (queryValues.count == 1) sndctls = ctlRequest[jdx].valuesJ; else json_object_array_add(sndctls, ctlRequest[jdx].valuesJ); } } } } // if we had error let's add them into response message info json_object *warningsJ = json_object_new_array(); for (int jdx = 0; jdx < queryValues.count; jdx++) { if (ctlRequest[jdx].used <= 0) { json_object *failctl = json_object_new_object(); if (ctlRequest[jdx].numId == -1) json_object_object_add(failctl, "warning", json_object_new_string("Numid Invalid")); else { if (ctlRequest[jdx].used == 0) json_object_object_add(failctl, "warning", json_object_new_string("Numid Does Not Exist")); if (ctlRequest[jdx].used == -1) json_object_object_add(failctl, "warning", json_object_new_string("Value Refused")); } json_object_object_add(failctl, "ctl", ctlRequest[jdx].jToken); json_object_array_add(warningsJ, failctl); } /* WARNING!!!! Check with Jose why following put free valuesJ if (ctlRequest[jdx].jToken) json_object_put(ctlRequest[jdx].jToken); if (ctlRequest[jdx].valuesJ) json_object_put(ctlRequest[jdx].valuesJ); */ } if (json_object_array_length(warningsJ) > 0) warmsg = json_object_get_string(warningsJ); else json_object_put(warningsJ); // send response+warning if any afb_req_success(request, sndctls, warmsg); snd_ctl_elem_list_clear(ctlList); OnErrorExit: return; } PUBLIC void alsaGetCtls(afb_req request) { alsaSetGetCtls(ACTION_GET, request); } PUBLIC void alsaSetCtls(afb_req request) { alsaSetGetCtls(ACTION_SET, request); }