/*
|
* BlueALSA - at.c
|
* Copyright (c) 2016-2018 Arkadiusz Bokowy
|
* 2017 Juha Kuikka
|
*
|
* This file is a part of bluez-alsa.
|
*
|
* This project is licensed under the terms of the MIT license.
|
*
|
*/
|
|
#include "at.h"
|
|
#include <ctype.h>
|
#include <stdbool.h>
|
#include <stdio.h>
|
#include <string.h>
|
|
#include "shared/defs.h"
|
#include "shared/log.h"
|
|
|
/**
|
* Build AT message.
|
*
|
* @param buffer Address of the buffer, which shall be big enough to contain
|
* AT message: len(command) + len(value) + 6 bytes.
|
* @param type AT message type.
|
* @param command AT command. If one wants to build unsolicited response code,
|
* this parameter should be set to NULL, otherwise AT command response will
|
* be build.
|
* @param value AT command value or unsolicited response code.
|
* @return Pointer to the destination buffer. */
|
char *at_build(char *buffer, enum bt_at_type type, const char *command,
|
const char *value) {
|
switch (type) {
|
case AT_TYPE_RAW:
|
strcpy(buffer, command);
|
break;
|
case AT_TYPE_CMD:
|
sprintf(buffer, "AT%s\r", command);
|
break;
|
case AT_TYPE_CMD_GET:
|
sprintf(buffer, "AT%s?\r", command);
|
break;
|
case AT_TYPE_CMD_SET:
|
sprintf(buffer, "AT%s=%s\r", command, value);
|
break;
|
case AT_TYPE_CMD_TEST:
|
sprintf(buffer, "AT%s=?\r", command);
|
break;
|
case AT_TYPE_RESP:
|
if (command != NULL)
|
sprintf(buffer, "\r\n%s:%s\r\n", command, value);
|
else
|
sprintf(buffer, "\r\n%s\r\n", value);
|
break;
|
case __AT_TYPE_MAX:
|
break;
|
}
|
return buffer;
|
}
|
|
/**
|
* Parse AT message.
|
*
|
* @param str String to parse.
|
* @param at Address of the AT structure, where the parsed information will
|
* be stored.
|
* @return On success this function returns a pointer to the next message
|
* within the input string. If the input string contains only one message,
|
* returned value will point to the end null byte. On error, this function
|
* returns NULL. */
|
char *at_parse(const char *str, struct bt_at *at) {
|
|
char *command = at->command;
|
bool is_command = false;
|
const char *feed;
|
char *tmp;
|
|
/* locate <CR> character, which indicates end of message */
|
if ((feed = strchr(str, '\r')) == NULL)
|
return NULL;
|
|
/* consume empty message */
|
if (feed == str)
|
return at_parse(feed + 1, at);
|
|
/* check whether we are parsing AT command */
|
if (strncasecmp(str, "AT", 2) == 0) {
|
is_command = true;
|
str += 2;
|
}
|
else {
|
/* response starts with <LF> sequence */
|
if (str[0] != '\n')
|
return NULL;
|
str += 1;
|
}
|
|
strncpy(command, str, sizeof(at->command) - 1);
|
at->value = NULL;
|
|
/* check everything twice (we don't want to be hacked) */
|
if ((size_t)(feed - str) < sizeof(at->command))
|
command[feed - str] = '\0';
|
|
if (is_command) {
|
|
/* determine command type */
|
if ((tmp = strchr(command, '=')) != NULL) {
|
if (tmp[1] == '?')
|
at->type = AT_TYPE_CMD_TEST;
|
else {
|
at->type = AT_TYPE_CMD_SET;
|
at->value = tmp + 1;
|
}
|
}
|
else if ((tmp = strchr(command, '?')) != NULL)
|
at->type = AT_TYPE_CMD_GET;
|
else
|
at->type = AT_TYPE_CMD;
|
|
if (tmp != NULL)
|
*tmp = '\0';
|
|
}
|
else {
|
|
at->type = AT_TYPE_RESP;
|
|
if ((tmp = strchr(command, ':')) == NULL)
|
/* provide support for GSM standard */
|
tmp = strchr(command, '=');
|
|
if (tmp != NULL) {
|
at->value = tmp + 1;
|
*tmp = '\0';
|
}
|
else {
|
/* unsolicited (with empty command) result code */
|
at->value = memmove(command + 1, command, sizeof(at->command) - 1);
|
command[0] = command[sizeof(at->command) - 1] = '\0';
|
}
|
|
/* consume <LF> from the end of the response */
|
if (feed[1] == '\n')
|
feed++;
|
|
}
|
|
/* In the BT specification, all AT commands are in uppercase letters.
|
* However, if someone will not respect this "convention", we will make
|
* life easier by converting received command to all uppercase. */
|
while (*command != '\0') {
|
*command = toupper(*command);
|
command++;
|
}
|
|
debug("AT message: %s: command:%s, value:%s", at_type2str(at->type), at->command, at->value);
|
return (char *)&feed[1];
|
}
|
|
/**
|
* Parse AT +CIND GET response.
|
*
|
* The maximal number of possible mappings is 20. This value is hard-coded,
|
* and is defined by the HFP specification.
|
*
|
* Mapping stored in the map array is 0-based. However, indexes returned by
|
* the +CIEV unsolicited result code are 1-based. Please, be aware of this
|
* incompatibility.
|
*
|
* @param str Response string.
|
* @param map Address where the mapping between the indicator index and the
|
* HFP indicator type will be stored.
|
* @return On success this function returns 0, otherwise -1 is returned. */
|
int at_parse_cind(const char *str, enum hfp_ind map[20]) {
|
|
static const struct {
|
const char *str;
|
enum hfp_ind ind;
|
} mapping[] = {
|
{ "service", HFP_IND_SERVICE },
|
{ "call", HFP_IND_CALL },
|
{ "callsetup", HFP_IND_CALLSETUP },
|
{ "callheld", HFP_IND_CALLHELD },
|
{ "signal", HFP_IND_SIGNAL },
|
{ "roam", HFP_IND_ROAM },
|
{ "battchg", HFP_IND_BATTCHG },
|
};
|
|
char ind[16];
|
size_t i, ii;
|
|
memset(map, HFP_IND_NULL, sizeof(*map) * 20);
|
for (i = 0; i < 20; i++) {
|
if (sscanf(str, " ( \"%15[a-z]\" , ( %*[0-9,-] ) )", ind) != 1)
|
return -1;
|
for (ii = 0; ii < ARRAYSIZE(mapping); ii++)
|
if (strcmp(mapping[ii].str, ind) == 0) {
|
map[i] = mapping[ii].ind;
|
break;
|
}
|
if ((str = strstr(str, "),")) == NULL)
|
break;
|
str += 2;
|
}
|
|
return 0;
|
}
|
|
/**
|
* Convert AT type into a human-readable string.
|
*
|
* @param type AT message type.
|
* @return Human-readable string. */
|
const char *at_type2str(enum bt_at_type type) {
|
static const char *types[__AT_TYPE_MAX] = {
|
[AT_TYPE_RAW] = "RAW",
|
[AT_TYPE_CMD] = "CMD",
|
[AT_TYPE_CMD_GET] = "GET",
|
[AT_TYPE_CMD_SET] = "SET",
|
[AT_TYPE_CMD_TEST] = "TEST",
|
[AT_TYPE_RESP] = "RESP",
|
};
|
return types[type];
|
}
|