335 lines
6.9 KiB
C
335 lines
6.9 KiB
C
|
/**
|
||
|
* @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;
|
||
|
}
|