zephyr/net/bluetooth/at.c

335 lines
6.9 KiB
C
Raw Normal View History

/**
* @file at.c
* Generic AT command handling library implementation
*/
/*
* Copyright (c) 2015-2016 Intel Corporation
*
* 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.
*/
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <stdarg.h>
#include <net/buf.h>
#include "at.h"
int at_check_byte(struct net_buf *buf, char check_byte)
{
const char *str = buf->data;
if (*str != check_byte) {
return -EINVAL;
}
net_buf_pull(buf, 1);
return 0;
}
const char *skip_whitespace(const char *buf)
{
while (*buf == ' ') {
buf++;
}
return buf;
}
int at_get_number(const char *buf, uint32_t *val)
{
for (buf = skip_whitespace(buf); isdigit(*buf); buf++) {
*val = *val * 10 + *buf - '0';
}
return 0;
}
int str_has_prefix(const char *str, const char *prefix)
{
if (strncmp(str, prefix, strlen(prefix)) != 0) {
return -EINVAL;
}
return 0;
}
static int at_parse_result(const char *str, struct net_buf *buf,
enum at_result *result)
{
/* Map the result and check for end lf */
if ((!strncmp(str, "OK", 2)) && (at_check_byte(buf, '\n') == 0)) {
*result = AT_RESULT_OK;
return 0;
}
if ((!strncmp(str, "ERROR", 5)) && (at_check_byte(buf, '\n')) == 0) {
*result = AT_RESULT_ERROR;
return 0;
}
return -ENOMSG;
}
static int get_cmd_value(struct at_client *at, struct net_buf *buf,
char stop_byte, enum at_cmd_state cmd_state)
{
int cmd_len = 0;
int len = buf->len;
const char *str = buf->data;
while (cmd_len < len && at->buf_pos != at->buf_max_len) {
if (*str != stop_byte) {
at->buf[at->buf_pos] = *str;
cmd_len++;
str++;
at->buf_pos++;
} else {
cmd_len++;
at->buf[at->buf_pos] = '\0';
at->cmd_state = cmd_state;
break;
}
}
net_buf_pull(buf, cmd_len);
if (at->buf_pos == at->buf_max_len) {
return -ENOBUFS;
}
return 0;
}
static int get_response_string(struct at_client *at, struct net_buf *buf,
char stop_byte, enum at_state state)
{
int cmd_len = 0;
int len = buf->len;
const char *str = buf->data;
while (cmd_len < len && at->buf_pos != at->buf_max_len) {
if (*str != stop_byte) {
at->buf[at->buf_pos] = *str;
cmd_len++;
str++;
at->buf_pos++;
} else {
cmd_len++;
at->buf[at->buf_pos] = '\0';
at->state = state;
break;
}
}
net_buf_pull(buf, cmd_len);
if (at->buf_pos == at->buf_max_len) {
return -ENOBUFS;
}
return 0;
}
static void reset_buffer(struct at_client *at)
{
memset(at->buf, 0, at->buf_max_len);
at->buf_pos = 0;
}
static int at_state_start(struct at_client *at, struct net_buf *buf)
{
int err;
err = at_check_byte(buf, '\r');
if (err < 0) {
return err;
}
at->state = AT_STATE_START_CR;
return 0;
}
static int at_state_start_cr(struct at_client *at, struct net_buf *buf)
{
int err;
err = at_check_byte(buf, '\n');
if (err < 0) {
return err;
}
at->state = AT_STATE_START_LF;
return 0;
}
static int at_state_start_lf(struct at_client *at, struct net_buf *buf)
{
reset_buffer(at);
if (at_check_byte(buf, '+') == 0) {
at->state = AT_STATE_GET_CMD_STRING;
return 0;
} else if (isalpha(*buf->data)) {
at->state = AT_STATE_GET_RESULT_STRING;
return 0;
}
return -ENODATA;
}
static int at_state_get_cmd_string(struct at_client *at, struct net_buf *buf)
{
return get_response_string(at, buf, ':', AT_STATE_PROCESS_CMD);
}
static int at_state_process_cmd(struct at_client *at, struct net_buf *buf)
{
if (at->resp) {
at->resp(at, buf);
return 0;
}
at->state = AT_STATE_UNSOLICITED_CMD;
return 0;
}
static int at_state_get_result_string(struct at_client *at, struct net_buf *buf)
{
return get_response_string(at, buf, '\r', AT_STATE_PROCESS_RESULT);
}
static int at_state_process_result(struct at_client *at, struct net_buf *buf)
{
enum at_result result;
if (at_parse_result(at->buf, buf, &result) == 0) {
if (at->finish) {
at->finish(at, buf, result);
}
}
return 0;
}
static int at_state_unsolicited_cmd(struct at_client *at, struct net_buf *buf)
{
/* TODO return error temporarily*/
return -ENODATA;
}
/* The order of handler function should match the enum at_state */
static handle_parse_input_t parser_cb[] = {
at_state_start, /* AT_STATE_START */
at_state_start_cr, /* AT_STATE_START_CR */
at_state_start_lf, /* AT_STATE_START_LF */
at_state_get_cmd_string, /* AT_STATE_GET_CMD_STRING */
at_state_process_cmd, /* AT_STATE_PROCESS_CMD */
at_state_get_result_string, /* AT_STATE_GET_RESULT_STRING */
at_state_process_result, /* AT_STATE_PROCESS_RESULT */
at_state_unsolicited_cmd /* AT_STATE_UNSOLICITED_CMD */
};
int at_parse_input(struct at_client *at, struct net_buf *buf)
{
int ret;
while (buf->len) {
if (at->state < AT_STATE_START || at->state >= AT_STATE_END) {
return -EINVAL;
}
ret = parser_cb[at->state](at, buf);
if (ret < 0) {
return ret;
}
}
return 0;
}
static int cmd_start(struct at_client *at, struct net_buf *buf,
const char *prefix, parse_val_t func)
{
if (str_has_prefix(at->buf, prefix) < 0) {
at->state = AT_STATE_UNSOLICITED_CMD;
return -ENODATA;
}
reset_buffer(at);
at->cmd_state = CMD_GET_VALUE;
return 0;
}
static int cmd_get_value(struct at_client *at, struct net_buf *buf,
const char *prefix, parse_val_t func)
{
return get_cmd_value(at, buf, '\r', CMD_PROCESS_VALUE);
}
static int cmd_process_value(struct at_client *at, struct net_buf *buf,
const char *prefix, parse_val_t func)
{
return func(at);
}
static int cmd_state_end_lf(struct at_client *at, struct net_buf *buf,
const char *prefix, parse_val_t func)
{
int err;
err = at_check_byte(buf, '\n');
if (err < 0) {
return err;
}
at->cmd_state = CMD_START;
at->state = AT_STATE_START;
return 0;
}
/* The order of handler function should match the enum at_cmd_state */
static handle_cmd_input_t cmd_parser_cb[] = {
cmd_start, /* CMD_START */
cmd_get_value, /* CMD_GET_VALUE */
cmd_process_value, /* CMD_PROCESS_VALUE */
cmd_state_end_lf /* CMD_STATE_END_LF */
};
int at_parse_cmd_input(struct at_client *at, struct net_buf *buf,
const char *prefix, parse_val_t func)
{
int ret;
while (buf->len) {
if (at->cmd_state < CMD_START ||
at->cmd_state >= CMD_STATE_END) {
return -EINVAL;
}
ret = cmd_parser_cb[at->cmd_state](at, buf, prefix, func);
if (ret < 0) {
return ret;
}
/* Check for main state, the end of cmd parsing and return. */
if (at->state == AT_STATE_START) {
return 0;
}
}
return 0;
}
void at_register(struct at_client *at, at_resp_cb_t resp, at_finish_cb_t finish)
{
at->resp = resp;
at->finish = finish;
at->state = AT_STATE_START;
}