/* * AT Command Utilities (and terminal stuff for now) * * Copyright (C) 2010 by Multi-Tech Systems * * Author: James Maki * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #define _GNU_SOURCE #define __ATCMD_C 1 #include #include #include #include #include #include #include #include #include #include #include #include "global.h" #include "log.h" #include "utils.h" #include "atcmd.h" #include "sms_utils.h" static const struct baud_map __baud_map[] = { {B300, 300}, {B600, 600}, {B1200, 1200}, {B1800, 1800}, {B2400, 2400}, {B4800, 4800}, {B9600, 9600}, {B19200, 19200}, {B38400, 38400}, {B57600, 57600}, {B115200, 115200}, {B230400, 230400}, {B460800, 460800}, {B921600, 921600}, }; speed_t value_to_baud(speed_t value) { int n = ARRAY_SIZE(__baud_map); int i; for (i = 0; i < n; ++i) { if (__baud_map[i].value == value) { return __baud_map[i].baud; } } log_warning("baud rate not valid: %lu", (unsigned long) value); return (speed_t) -1; } int rts_get(int fd) { int err; int status; err = ioctl(fd, TIOCMGET, &status); if (err < 0) { return -1; } return status & TIOCM_RTS ? 1 : 0; } int dtr_get(int fd) { int err; int status; err = ioctl(fd, TIOCMGET, &status); if (err < 0) { return -1; } return status & TIOCM_DTR ? 1 : 0; } int cts_get(int fd) { int err; int status; err = ioctl(fd, TIOCMGET, &status); if (err < 0) { return -1; } return status & TIOCM_CTS ? 1 : 0; } int dsr_get(int fd) { int err; int status; err = ioctl(fd, TIOCMGET, &status); if (err < 0) { return -1; } return status & TIOCM_DSR ? 1 : 0; } int cd_get(int fd) { int err; int status; err = ioctl(fd, TIOCMGET, &status); if (err < 0) { return -1; } return status & TIOCM_CD ? 1 : 0; } int ri_get(int fd) { int err; int status; err = ioctl(fd, TIOCMGET, &status); if (err < 0) { return -1; } return status & TIOCM_RI ? 1 : 0; } int line_signal_set(int fd, int signal, int value) { int err; int status; err = ioctl(fd, TIOCMGET, &status); if (err < 0) { return -1; } if (value) { status &= ~signal; } else { status |= signal; } err = ioctl(fd, TIOCMSET, &status); if (err < 0) { return -1; } return status; } int rts_set(int fd, int value) { return line_signal_set(fd, TIOCM_RTS, value); } int dtr_set(int fd, int value) { return line_signal_set(fd, TIOCM_DTR, value); } int tty_configure(int fd, speed_t baud_rate) { int err; struct termios params; err = tcgetattr(fd, ¶ms); if (err < 0) { log_error("tcgetattr failed: %m"); return -1; } cfmakeraw(¶ms); cfsetspeed(¶ms, baud_rate); err = tcsetattr(fd, TCSANOW, ¶ms); if (err < 0) { log_error("tcsetattr failed: %m"); return -1; } return 0; } int tty_open(const char *dev, speed_t baud_rate) { int fd; fd = open(dev, O_RDWR | O_NOCTTY); if (fd < 0) { log_error("failed to open %s: %m", dev); return fd; } if (!isatty(fd)) { log_error("%s is not a tty device", dev); close(fd); return -1; } tty_configure(fd, baud_rate); tcflush(fd, TCIOFLUSH); log_debug("rts: %d", rts_get(fd)); log_debug("dtr: %d", dtr_get(fd)); log_debug("cts: %d", cts_get(fd)); log_debug("dsr: %d", dsr_get(fd)); log_debug("cd: %d", cd_get(fd)); log_debug("ri: %d", ri_get(fd)); return fd; } int tty_set_read_timeout(int fd, int timeout) { int err; struct termios params; err = tcgetattr(fd, ¶ms); if (err < 0) { log_error("tcgetattr failed: %m"); return -1; } if (timeout < 0) { params.c_cc[VTIME] = 0; params.c_cc[VMIN] = 0; } else if (timeout == 0) { params.c_cc[VTIME] = 0; params.c_cc[VMIN] = 1; } else { params.c_cc[VTIME] = (timeout + 50) / 100; params.c_cc[VMIN] = 0; } err = tcsetattr(fd, TCSANOW, ¶ms); if (err < 0) { log_error("tcsetattr failed: %m"); return -1; } return 0; } int tty_close(int fd) { if (fd < 0) { return -1; } tcflush(fd, TCIOFLUSH); return close(fd); } int atcmd_read(int fd, char *buf, size_t len) { int err; err = read(fd, buf, len); if (!err) { log_notice("read timeout"); } else if (err < 0) { log_error("read failed: %m"); } return err; } int atcmd_read_until(int fd, char *buf, size_t len, const char *stop) { int err; char c; ssize_t total = 0; size_t stop_len = strlen(stop); if (!stop_len) { BUG("specify a stop string"); } while (1) { err = atcmd_read(fd, &c, 1); if (err <= 0) { buf[total] = '\0'; return err; } if (total < len - 1) { buf[total++] = c; } if (stop_len <= total && c == stop[stop_len - 1] && !memcmp(buf + (total - stop_len), stop, stop_len)) { buf[total] = '\0'; break; } if (total >= len - 1) { buf[total] = '\0'; log_notice("buffer exceeded before finding stop sequence"); log_notice("buffer so far: %s", buf); return -1; } } debug_buffer("read:", buf, strlen(buf)); return total; } int atcmd_readline(int fd, char *buf, size_t len) { int tmp; tmp = atcmd_read_until(fd, buf, len, ATCMD_RESPONSE_EOL); if (tmp <= 0) { log_debug("atcmd_read_until failed with %d", tmp); return tmp; } buf = strpbrk(buf, ATCMD_RESPONSE_EOL); if (buf) { *buf = '\0'; } return tmp; } int atcmd_expect_line(int fd, char *buf, size_t len, const char *expect) { int tmp; while (1) { tmp = atcmd_readline(fd, buf, len); if (tmp <= 0) { *buf = '\0'; log_debug("atcmd_readline failed"); return tmp; } if (strstr(buf, expect)) { return tmp; } else if (!strcmp(buf, "OK")) { log_error("expected %s but got %s", expect, buf); return -1; } else if (!strcmp(buf, "ERROR")) { log_error("expected %s but got %s", expect, buf); return -1; } else if (!strncmp(buf, "+CMS ERROR: ", sizeof("+CMS ERROR: ") - 1) || !strncmp(buf, "+CME ERROR: ", sizeof("+CME ERROR: ") - 1)) { log_error("expected %s but got %s", expect, buf); return -1; } } return -1; } int atcmd_write(int fd, const char *buf, size_t len) { int err; debug_buffer("writing:", buf, len); err = full_write(fd, buf, len); if (err < 0) { log_error("full_write failed: %m"); } return err; } int atcmd_write_str(int fd, const char *buf) { return atcmd_write(fd, buf, strlen(buf)); } int atcmd_vprintf(int fd, char *fmt, va_list ap) { char *buf; int err; err = vasprintf(&buf, fmt, ap); if (err < 0) { log_error("out of memory"); return err; } err = atcmd_write_str(fd, buf); if (err < 0) { log_debug("atcmd_write failed"); } free(buf); return err; } int atcmd_printf(int fd, char *fmt, ...) { va_list ap; int err; va_start(ap, fmt); err = atcmd_vprintf(fd, fmt, ap); if (err < 0) { log_debug("atcmd_write failed"); } va_end(ap); return err; } int atcmd_writeline(int fd, char *fmt, ...) { va_list ap; int err; int total = 0; va_start(ap, fmt); err = atcmd_vprintf(fd, fmt, ap); va_end(ap); if (err < 0) { log_debug("atcmd_vprintf failed"); return err; } total += err; err = atcmd_write_str(fd, ATCMD_EOL); if (err < 0) { log_debug("atcmd_write_str failed"); return err; } total += err; return total; } /** * atcmd_value_tok - Tokenize an AT compound value. * @str: The compound value to tokenize. * * Return: A pointer to the next token is returned and @str is updated for the next * call. Returns NULL on error or when there are no more tokens. * */ char *atcmd_value_tok(char **str) { char *next; char *begin; begin = *str; if (!begin || !*begin) { return NULL; } begin += strspn(begin, " \t"); switch (*begin) { case '\"': next = ++begin; next = strchr(next, '\"'); if (!next) { log_notice("unterminated string"); return NULL; } *next++ = '\0'; next = strchr(next, ','); if (next) { *next++ = '\0'; } break; case '(': next = ++begin; int count = 0; while (1) { switch (*next) { case ')': if (!count) { goto found; } count--; break; case '(': count++; break; case '\0': log_notice("unterminated group"); return NULL; } next++; } found: *next++ = '\0'; next = strchr(next, ','); if (next) { *next++ = '\0'; } break; default: next = begin; next = strchr(next, ','); if (next) { *next++ = '\0'; } strrstrip(begin); } *str = next; return begin; } /** * atcmd_response_brk - Break an AT command response info line in half. * @str: The response info line to break in half * * expected format: ": ". can be the empty string. * * Return: A pointer to is returned. @str is set to . * */ char *atcmd_response_brk(char **str) { char *next; char *begin; begin = *str; if (!begin || !*begin) { return NULL; } begin += strspn(begin, " \t"); next = begin; next = strstr(next, ": "); if (!next) { log_notice("separator \": \" not found"); return NULL; } *next++ = '\0'; next += strspn(next, " \t"); *str = next; return begin; } int atcmd_e_write(int fd, int mode) { char buf[ATCMD_LINE_SIZE]; int tmp; atcmd_writeline(fd, "ATE%d", mode); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); if (tmp <= 0) { log_debug("expected OK but it was not received"); return -1; } return 0; } int atcmd_v_write(int fd, int mode) { char buf[ATCMD_LINE_SIZE]; int tmp; atcmd_writeline(fd, "ATV%d", mode); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); if (tmp <= 0) { log_debug("expected OK but it was not received"); return -1; } return 0; } int atcmd_q_write(int fd, int mode) { char buf[ATCMD_LINE_SIZE]; int tmp; atcmd_writeline(fd, "ATQ%d", mode); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); if (tmp <= 0) { log_debug("expected OK but it was not received"); return -1; } return 0; } int atcmd_plus_cmgf_write(int fd, int mode) { char buf[ATCMD_LINE_SIZE]; int tmp; atcmd_writeline(fd, "AT+CMGF=%d", mode); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); if (tmp <= 0) { log_debug("expected OK but it was not received"); return -1; } return 0; } int atcmd_plus_cmgw_write(int fd, const char *msg, size_t msg_len) { char buf[ATCMD_LINE_SIZE]; int tmp; int mem_index; atcmd_writeline(fd, "AT+CMGW=%d", msg_len); tmp = atcmd_read_until(fd, buf, sizeof(buf), "> "); if (tmp <= 0) { log_debug("expected > start sequence but it was not received"); return -1; } tmp = atcmd_write(fd, msg, strlen(msg)); tmp = atcmd_write_str(fd, CONTROL_Z_STR); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CMGW: "); if (tmp <= 0) { log_debug("expected +CMGW: but it was not received"); return -1; } mem_index = atoi(buf + strlen("+CMGW: ")); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); if (tmp <= 0) { log_debug("expected OK but it was not received"); return -1; } return mem_index; } int atcmd_plus_cmgw_write_text(int fd, const char *addr, int addr_type, const char *status, const char *msg, size_t msg_len) { char buf[ATCMD_LINE_SIZE]; int tmp; int mem_index; atcmd_printf(fd, "AT+CMGW"); if (addr) { atcmd_printf(fd, "=\"%s\"", addr); if (addr_type != SMS_ADDR_UNSPEC) { atcmd_printf(fd, ",%d", addr_type); if (status) { atcmd_printf(fd, ",\"%s\"", status); } } } atcmd_write_str(fd, ATCMD_EOL); tmp = atcmd_read_until(fd, buf, sizeof(buf), "> "); if (tmp <= 0) { log_debug("expected > start sequence but it was not received"); return -1; } tmp = atcmd_write(fd, msg, strlen(msg)); tmp = atcmd_write_str(fd, CONTROL_Z_STR); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CMGW: "); if (tmp <= 0) { log_debug("expected +CMGW: but it was not received"); return -1; } mem_index = atoi(buf + strlen("+CMGW: ")); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); if (tmp <= 0) { log_debug("expected OK but it was not received"); return -1; } return mem_index; } int atcmd_plus_cmgs_write(int fd, const char *msg, size_t msg_len) { char buf[ATCMD_LINE_SIZE]; int tmp; int msg_ref; atcmd_writeline(fd, "AT+CMGS=%d", msg_len); tmp = atcmd_read_until(fd, buf, sizeof(buf), "> "); if (tmp <= 0) { log_debug("expected > start sequence but it was not received"); return -1; } tmp = atcmd_write(fd, msg, strlen(msg)); tmp = atcmd_write_str(fd, CONTROL_Z_STR); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CMGS: "); if (tmp <= 0) { log_debug("expected +CMGS: but it was not received"); return -1; } msg_ref = atoi(buf + strlen("+CMGS: ")); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); if (tmp <= 0) { log_debug("expected OK but it was not received"); return -1; } return msg_ref; } int atcmd_plus_cmgs_write_text(int fd, const char *addr, const char *msg, size_t msg_len) { char buf[ATCMD_LINE_SIZE]; int tmp; int msg_ref; atcmd_writeline(fd, "AT+CMGS=\"%s\"", addr); tmp = atcmd_read_until(fd, buf, sizeof(buf), "> "); if (tmp <= 0) { log_debug("expected > start sequence but it was not received"); return -1; } tmp = atcmd_write(fd, msg, strlen(msg)); tmp = atcmd_write_str(fd, CONTROL_Z_STR); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CMGS: "); if (tmp <= 0) { log_debug("expected +CMGS: but it was not received"); return -1; } msg_ref = atoi(buf + strlen("+CMGS: ")); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); if (tmp <= 0) { log_debug("expected OK but it was not received"); return -1; } return msg_ref; } int atcmd_plus_cmss_write(int fd, int index, const char *addr, int addr_type) { char buf[ATCMD_LINE_SIZE]; int tmp; int msg_ref; atcmd_printf(fd, "AT+CMSS=%d", index); if (addr) { atcmd_printf(fd, ",\"%s\"", addr); if (addr_type != SMS_ADDR_UNSPEC) { atcmd_printf(fd, ",%d", addr_type); } } atcmd_write_str(fd, ATCMD_EOL); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CMSS: "); if (tmp <= 0) { log_debug("expected +CMSS: but it was not received"); return -1; } msg_ref = atoi(buf + strlen("+CMSS: ")); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); if (tmp <= 0) { log_debug("expected OK but it was not received"); return -1; } return msg_ref; } int atcmd_plus_cmgd_write(int fd, int index) { char buf[ATCMD_LINE_SIZE]; int tmp; atcmd_writeline(fd, "AT+CMGD=%d", index); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); if (tmp <= 0) { log_debug("expected OK but it was not received"); return -1; } return 0; } int atcmd_plus_qcfg_write(int fd, int sms_format) { char buf[ATCMD_LINE_SIZE]; int tmp; atcmd_writeline(fd, "AT+QCFG=\"ltesms/format\",%d", sms_format); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); if (tmp <= 0) { log_debug("expected OK but it was not received"); return -1; } return 0; } int atcmd_response_foreach_line(int fd, atcmd_response_callback_t call, void *prv) { char buf[ATCMD_LINE_SIZE]; int tmp; while (1) { tmp = atcmd_readline(fd, buf, sizeof(buf)); if (tmp <= 0) { return -1; } tmp = call(buf, strlen(buf), prv); if (tmp) { return tmp; } } return -1; } int atcmd_plus_cpms_read(int fd, struct data_store *read_store, struct data_store *send_store, struct data_store *new_store, const char *model) { char buf[ATCMD_LINE_SIZE]; char *save; char *token; int tmp; int i; int data_stores_size; struct data_store *store; struct data_store *data_stores[] = {read_store, send_store, new_store}; if (is_telit_3gpp2_format()) { data_stores_size = 2; } else { data_stores_size = 3; } atcmd_writeline(fd, "AT+CPMS?"); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CPMS: "); if (tmp <= 0) { log_debug("expected +CPMS: but it was not received"); return -1; } save = buf; token = atcmd_response_brk(&save); if (!token) { log_debug("atcmd_response_brk failed"); return -1; } for (i = 0; i < data_stores_size; i++) { store = data_stores[i]; token = atcmd_value_tok(&save); if (!token) { return -1; } strncpy(store->name, token, STORE_NAME_LEN); token = atcmd_value_tok(&save); if (!token) { return -1; } store->used = atoi(token); token = atcmd_value_tok(&save); if (!token) { return -1; } store->max = atoi(token); log_debug("name[%d]: %s", i, store->name); log_debug("used[%d]: %d", i, store->used); log_debug("max[%d]: %d", i, store->max); } tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); if (tmp <= 0) { log_debug("expected OK but it was not received"); return -1; } return 0; } int atcmd_plus_cpms_test(int fd, struct store_locations *read_loc, struct store_locations *send_loc, struct store_locations *new_loc) { char buf[ATCMD_LINE_SIZE]; char *save; char *token; char *choices; int tmp; int i, j; struct store_locations *locations[] = {read_loc, send_loc, new_loc}; struct store_locations *loc; atcmd_writeline(fd, "AT+CPMS=?"); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CPMS: "); if (tmp <= 0) { log_debug("expected +CPMS: but it was not received"); return -1; } save = buf; token = atcmd_response_brk(&save); if (!token) { log_debug("atcmd_response_brk failed"); return -1; } for (i = 0; i < ARRAY_SIZE(locations); i++) { loc = locations[i]; token = atcmd_value_tok(&save); if (!token) { break; } if (i == 0 && *token == '(') { save = token; token = atcmd_value_tok(&save); if (!token) { break; } } choices = token; for (j = 0; j < STORE_LOCATIONS_MAX; j++) { token = atcmd_value_tok(&choices); if (!token) { break; } strncpy(loc->names[j], token, STORE_NAME_LEN); log_debug("loc[%d] choice[%d]: %s", i, j, token); loc->nr_locations++; } } tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); if (tmp <= 0) { log_debug("expected OK but it was not received"); return -1; } return 0; } int atcmd_plus_cpms_write(int fd, const char *read_name, const char *send_name, const char *new_name, const char *model) { char buf[ATCMD_LINE_SIZE]; int tmp; if (is_telit_3gpp2_format()) { atcmd_writeline(fd, "AT+CPMS=\"%s\",\"%s\"", read_name, send_name); } else { atcmd_writeline(fd, "AT+CPMS=\"%s\",\"%s\",\"%s\"", read_name, send_name, new_name); } tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); if (tmp <= 0) { log_debug("expected OK but it was not received"); return -1; } return 0; } int atcmd_plus_cpbs_read(int fd, struct data_store *store) { char buf[ATCMD_LINE_SIZE]; char *save; char *token; int tmp; atcmd_writeline(fd, "AT+CPBS?"); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CPBS: "); if (tmp <= 0) { log_debug("expected +CPBS: but it was not received"); return -1; } save = buf; token = atcmd_response_brk(&save); if (!token) { log_debug("atcmd_response_brk failed"); return -1; } token = atcmd_value_tok(&save); if (!token) { log_debug("atcmd_value_tok name"); return -1; } strncpy(store->name, token, STORE_NAME_LEN); token = atcmd_value_tok(&save); if (!token) { log_debug("atcmd_value_tok used"); return -1; } store->used = atoi(token); token = atcmd_value_tok(&save); if (!token) { log_debug("atcmd_value_tok max"); return -1; } store->max = atoi(token); log_debug("name: %s", store->name); log_debug("used: %d", store->used); log_debug("max: %d", store->max); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); if (tmp <= 0) { log_debug("expected OK but it was not received"); return -1; } return 0; } int atcmd_plus_cpbs_test(int fd, struct store_locations *loc) { char buf[ATCMD_LINE_SIZE]; char *save; char *token; char *choices; int tmp; int i; atcmd_writeline(fd, "AT+CPBS=?"); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CPBS: "); if (tmp <= 0) { log_debug("expected +CPBS: but it was not received"); return -1; } save = buf; token = atcmd_response_brk(&save); if (!token) { log_debug("atcmd_response_brk failed"); return -1; } token = atcmd_value_tok(&save); if (!token) { log_debug("atcmd_value_tok group"); return -1; } choices = token; for (i = 0; i < STORE_LOCATIONS_MAX; i++) { token = atcmd_value_tok(&choices); if (!token) { break; } strncpy(loc->names[i], token, STORE_NAME_LEN); log_debug("choice[%d]: %s", i, token); loc->nr_locations++; } tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); if (tmp <= 0) { log_debug("expected OK but it was not received"); return -1; } return 0; } int atcmd_plus_cpbs_write(int fd, const char *name) { char buf[ATCMD_LINE_SIZE]; int tmp; atcmd_writeline(fd, "AT+CPBS=\"%s\"", name); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); if (tmp <= 0) { log_debug("expected OK but it was not received"); return -1; } return 0; } int atcmd_plus_cpbr_test(int fd, struct phonebook_store *store) { char buf[ATCMD_LINE_SIZE]; char *save; char *range; char *token; int tmp; atcmd_writeline(fd, "AT+CPBR=?"); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CPBR: "); if (tmp <= 0) { log_debug("expected +CPBR: but it was not received"); return -1; } save = buf; token = atcmd_response_brk(&save); if (!token) { log_debug("atcmd_response_brk failed"); return -1; } token = atcmd_value_tok(&save); if (!token) { log_debug("atcmd_value_tok start"); return -1; } range = token; token = atcmd_value_tok(&range); if (!token) { log_debug("atcmd_value_tok range"); return -1; } range = strchr(token, '-'); if (!range) { log_debug("break range"); return -1; } store->index_min = atoi(token); store->index_max = atoi(range + 1); token = atcmd_value_tok(&save); if (!token) { log_debug("atcmd_value_tok addr_max"); return -1; } store->addr_max = atoi(token); token = atcmd_value_tok(&save); if (!token) { log_debug("atcmd_value_tok name_max"); return -1; } store->name_max = atoi(token); log_debug("index_min: %d", store->index_min); log_debug("index_max: %d", store->index_max); log_debug("addr_max: %d", store->addr_max); log_debug("name_max: %d", store->name_max); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); if (tmp <= 0) { log_debug("expected OK but it was not received"); return -1; } return 0; } int atcmd_plus_gmm_read(int fd) { char buf[ATCMD_LINE_SIZE]; char *save; char *token; int tmp; atcmd_writeline(fd, "AT+GMM"); //Swallow extra "\r\n" tmp = atcmd_readline(fd, buf, sizeof(buf)); if (tmp <= 0) { log_debug("expected \\r\\n but it was not received"); return -1; } //Read model string tmp = atcmd_readline(fd, buf, sizeof(buf)); if (tmp <= 0) { log_debug("expected model but it was not received"); return -1; } save = buf; token = atcmd_value_tok(&save); if (!token) { log_debug("atcmd_value_tok model"); return -1; } strncpy(Global.core.model, token, MODEL_LEN); log_debug("model: %s", Global.core.model); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); if (tmp <= 0) { log_debug("expected OK but it was not received"); return -1; } return 0; } /* Manufacturer identification is needed to pick proper manufacturer-specific commands. * For example, in atcmd_plus_iccid_read() function. */ int atcmd_plus_gmi_read(int fd) { char buf[ATCMD_LINE_SIZE]; char *save; char *token; int tmp; atcmd_writeline(fd, "AT+GMI"); //Swallow extra "\r\n" tmp = atcmd_readline(fd, buf, sizeof(buf)); if (tmp <= 0) { log_debug("expected \\r\\n but it was not received"); return -1; } //Read manufacturer string tmp = atcmd_readline(fd, buf, sizeof(buf)); if (tmp <= 0) { log_debug("expected manufacturer but it was not received"); return -1; } save = buf; token = atcmd_value_tok(&save); if (!token) { log_debug("atcmd_value_tok manufacturer"); return -1; } strncpy(Global.core.manufacturer, token, MANUFACTURER_LEN); log_debug("manufacturer: %s", Global.core.manufacturer); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); if (tmp <= 0) { log_debug("expected OK but it was not received"); return -1; } return 0; } /* ICCID needed to detect Verizon on multi-carrier radios */ int atcmd_plus_iccid_read(int fd) { char buf[ATCMD_LINE_SIZE]; char *save; char *token; int tmp; char *tmp_buf; char* command; if (is_telit_model()) { command = "AT#CCID"; if (strstr(Global.core.model, "FN980")) { command = "AT+ICCID"; } } else if (is_quectel_model()) { command = "AT+QCCID"; } else { log_error("unsupported modem manufacturer, unable to detect ICCID"); return -1; } atcmd_writeline(fd, command); //Swallow extra "\r\n" tmp = atcmd_readline(fd, buf, sizeof(buf)); if (tmp <= 0) { log_debug("expected \\r\\n but it was not received"); return -1; } //Read iccid string tmp = atcmd_readline(fd, buf, sizeof(buf)); if (tmp <= 0) { log_debug("expected ICCID but it was not received"); /* Currently only LNA3 models will need the ICCID */ return -1; } save = buf; log_debug("buf=%s for %d", buf,strlen(buf)); token = atcmd_value_tok(&save); if (!token) { log_debug("atcmd_value_tok iccid"); return -1; } else { log_debug("token is %s",token); } tmp_buf = strrchr(token,' '); if (tmp_buf) { token = ++tmp_buf; log_debug("Found blank, incrementing tmp"); } else { tmp_buf = strrchr(token,'\t'); log_debug("Found tab, incrementing tmp"); if(tmp_buf) token = ++tmp_buf; } log_debug("token[0]=%2.2x token[1]=%2.2x",token[0],token[1]); strncpy(Global.core.iccid, token, ICCID_LEN); log_debug("iccid: %s", Global.core.iccid); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); if (tmp <= 0) { log_debug("expected OK but it was not received"); return -1; } return 0; } /* IMSI needed to detect Verizon when the ICCID is unrecognized */ int atcmd_plus_imsi_read(int fd) { char buf[ATCMD_LINE_SIZE]; char *save; char *token; int tmp; char *tmp_buf; char* command = "AT+CIMI"; atcmd_writeline(fd, command); //Swallow extra "\r\n" tmp = atcmd_readline(fd, buf, sizeof(buf)); if (tmp <= 0) { log_debug("expected \\r\\n but it was not received"); return -1; } //Read imsi string tmp = atcmd_readline(fd, buf, sizeof(buf)); if (tmp <= 0) { log_debug("expected IMSI but it was not received"); /* Currently only LNA7/LNA7D/L4G1 models will need the IMSI */ return -1; } save = buf; log_debug("buf=%s for %d", buf,strlen(buf)); token = atcmd_value_tok(&save); if (!token) { log_debug("atcmd_value_tok imsi"); return -1; } else { log_debug("token is %s",token); } tmp_buf = strrchr(token,' '); if (tmp_buf) { token = ++tmp_buf; log_debug("Found blank, incrementing tmp"); } else { tmp_buf = strrchr(token,'\t'); log_debug("Found tab, incrementing tmp"); if(tmp_buf) token = ++tmp_buf; } log_debug("token[0]=%2.2x token[1]=%2.2x",token[0],token[1]); strncpy(Global.core.imsi, token, IMSI_LEN); log_debug("imsi: %s", Global.core.imsi); tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK"); if (tmp <= 0) { log_debug("expected OK but it was not received"); return -1; } return 0; } int atcmd_init(int fd, int read_timeout) { int tmp; tmp = atcmd_q_write(fd, 0); if (tmp < 0) { return tmp; } tmp = atcmd_v_write(fd, 1); if (tmp < 0) { return tmp; } tmp = atcmd_e_write(fd, 0); if (tmp < 0) { return tmp; } return 0; } static int msg_store_choice(struct msg_store *store, const char *name) { int i; for (i = 0; i < store->choices.nr_locations; i++) { if(!strcmp(store->choices.names[i], name)) { return true; } } return false; } static int sms_atcmd_init(int fd) { int tmp; struct msg_store read_store; struct msg_store send_store; struct msg_store new_store; tmp = atcmd_init(fd, Global.core.read_timeout); if (tmp < 0) { return -1; } tmp = atcmd_plus_gmm_read(fd); if (tmp < 0) { log_error("failed to read radio model"); return -1; } tmp = atcmd_plus_gmi_read(fd); if (tmp < 0) { log_error("failed to read radio manufacturer"); return -1; } tmp = atcmd_plus_iccid_read(fd); if (tmp < 0) { log_error("failed to read SIM ICCID"); return -1; } tmp = atcmd_plus_imsi_read(fd); if (tmp < 0) { log_error("failed to read SIM IMSI"); return -1; } tmp = atcmd_plus_cmgf_write(fd, SMS_PDU_MODE); if (tmp < 0) { return -1; } memset(&read_store, 0, sizeof(read_store)); memset(&send_store, 0, sizeof(send_store)); memset(&new_store, 0, sizeof(new_store)); if (!strcmp(Global.core.model, "CE910-DUAL") || !strcmp(Global.core.model, "DE910-DUAL")) { log_info("Changing message storage locations to ME for CE/DE910-DUAL"); free(Global.core.msg_store_read); free(Global.core.msg_store_send); free(Global.core.pb_store); Global.core.msg_store_read = strdup("ME"); Global.core.msg_store_send = strdup("ME"); Global.core.pb_store = strdup("ME"); } atcmd_plus_cpms_test(fd, &read_store.choices, &send_store.choices, &new_store.choices); atcmd_plus_cpms_read(fd, &read_store.selected, &send_store.selected, &new_store.selected, Global.core.model); if (Global.core.msg_store_read && Global.core.msg_store_send && Global.core.msg_store_new) { if (!msg_store_choice(&read_store, Global.core.msg_store_read)) { log_error("message storage location %s is not a choice", Global.core.msg_store_read); return -1; } if (!msg_store_choice(&send_store, Global.core.msg_store_send)) { log_error("message storage location %s is not a choice", Global.core.msg_store_send); return -1; } //Doesn't apply to CDMA type radios if (!is_telit_3gpp2_format() && !msg_store_choice(&new_store, Global.core.msg_store_new)) { log_error("message storage location %s is not a choice", Global.core.msg_store_new); return -1; } tmp = atcmd_plus_cpms_write(fd, Global.core.msg_store_read, Global.core.msg_store_send, Global.core.msg_store_new, Global.core.model); if (tmp < 0) { return -1; } } return 0; } static int lock_file; int sms_device_close(int fd) { int ret; ret = tty_close(fd); lock_file = device_unlock(lock_file); return ret; } int sms_device_open(void) { int fd = -1; int tmp; if (!Global.core.device) { log_error("device is not set"); return -1; } lock_file = device_lock(Global.core.device); if (lock_file < 0) { sms_device_close(fd); return -1; } fd = tty_open(Global.core.device, Global.core.baud_rate); if (fd < 0) { sms_device_close(fd); return -1; } tmp = tty_set_read_timeout(fd, Global.core.read_timeout); if (tmp < 0) { sms_device_close(fd); return tmp; } if (Global.core.sms_init) { tmp = sms_atcmd_init(fd); if (tmp < 0) { sms_device_close(fd); return tmp; } } return fd; } /* Detect Telit LTE radios that use the 3GPP2 SMS format for Verizon */ int is_telit_lte_vzw_3gpp2_format(void) { if (!strcmp(Global.core.model, "LE910-NA1")) { log_debug("Found LE910-NA1"); /* Verizon Wireless SIM */ if (strncmp(Global.core.iccid,"891480",6) == 0) { log_debug("Found VZW SIM"); return 1; } } return (!strncmp(Global.core.model, "LE910-SVG", MODEL_LEN) || !strncmp(Global.core.model, "LE910-SV1", MODEL_LEN)); } /* Detect LNA7/LNA7D/L4G1 radios with Verizon SIM that support both 3GPP and 3GPP2 */ int is_quectel_dual_format(void) { if (!strncmp(Global.core.model, "EG95", MODEL_LEN) || !strncmp(Global.core.model, "EG25", MODEL_LEN)) { log_debug("Found Quectel radio with dual SMS format support"); /* Verizon Wireless SIM ICCID */ if (strncmp(Global.core.iccid,"891480",6) == 0) { log_debug("Found VZW SIM by ICCID"); return 1; } /* Extract PLMN ID - the first 5 or 6 digits of IMSI. Assume 6 digits for Verizon. NOTE: Other carriers may use shorter codes */ char plmn_id_str[PLMN_ID_SIZE_VZW] = {0}; strncpy(plmn_id_str, Global.core.imsi, PLMN_ID_LEN_VZW); /* Parse as integer for easier comparison */ int plmn_id_num = atoi(plmn_id_str); /* Verizon Wireless SIM MCC/MNC */ switch (plmn_id_num) { case 310590: case 310890: case 311270: case 311480: case 312770: log_debug("Found VZW SIM by IMSI"); return 1; default: /* ignore, not Verizon */ break; }; } return 0; } /* Detect Telit LTE and CDMA radios that use the 3GPP2 SMS format for Verizon */ int is_telit_3gpp2_format() { /* Test for possible dual firmware model */ if (is_telit_lte_vzw_3gpp2_format()) return 1; return (!strncmp(Global.core.model, "DE910-DUAL", MODEL_LEN) || !strncmp(Global.core.model, "CE910-DUAL", MODEL_LEN)); } int is_telit_model() { if (!strncmp(Global.core.manufacturer, "Telit", MANUFACTURER_LEN)) { log_debug("Found Telit model"); return 1; } return 0; } int is_quectel_model() { if (!strncmp(Global.core.manufacturer, "Quectel", MANUFACTURER_LEN)) { log_debug("Found Quectel model"); return 1; } return 0; }