├── .gitignore ├── COPYING ├── Makefile ├── README.md ├── include └── attentive │ ├── at-unix.h │ ├── at.h │ ├── cellular.h │ └── parser.h ├── src ├── .gitignore ├── at-unix.c ├── cellular.c ├── example-at.c ├── example-sim800.c ├── modem │ ├── common.c │ ├── common.h │ ├── generic.c │ ├── sim800.c │ └── telit2.c └── parser.c └── tests ├── .gitignore └── test-parser.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.dSYM 3 | *.exe 4 | *~ 5 | *.diff 6 | .*.swp 7 | .*.swo 8 | html/ 9 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright © 2014 Kosma Moczek 2 | # This program is free software. It comes without any warranty, to the extent 3 | # permitted by applicable law. You can redistribute it and/or modify it under 4 | # the terms of the Do What The Fuck You Want To Public License, Version 2, as 5 | # published by Sam Hocevar. See the COPYING file for more details. 6 | 7 | CFLAGS = $(shell pkg-config --cflags $(LIBRARIES)) -std=c99 -g -Wall -Wextra -Werror -Iinclude 8 | LDLIBS = $(shell pkg-config --libs $(LIBRARIES)) 9 | 10 | LIBRARIES = check glib-2.0 11 | 12 | all: test example 13 | @echo "+++ All good.""" 14 | 15 | test: tests/test-parser 16 | @echo "+++ Running parser test suite." 17 | tests/test-parser 18 | 19 | clean: 20 | $(RM) src/example-at src/example-sim800 tests/test-parser 21 | $(RM) src/*.o src/modem/*.o tests/*.o 22 | 23 | PARSER = include/attentive/parser.h 24 | AT = include/attentive/at.h include/attentive/at-unix.h $(PARSER) 25 | CELLULAR = include/attentive/cellular.h $(AT) 26 | MODEM = src/modem/common.h $(CELLULAR) 27 | 28 | src/parser.o: src/parser.c $(PARSER) 29 | src/at-unix.o: src/at-unix.c $(AT) 30 | src/cellular.o: src/cellular.c $(CELLULAR) 31 | src/modem/common.o: src/modem/common.c $(MODEM) 32 | src/modem/generic.o: src/modem/generic.c $(MODEM) 33 | src/modem/sim800.o: src/modem/sim800.c $(MODEM) 34 | src/modem/telit2.o: src/modem/telit2.c $(MODEM) 35 | tests/test-parser.o: tests/test-parser.c $(MODEM) 36 | src/example-at.o: src/example-at.c $(AT) 37 | src/example-sim800.o: src/example-sim800.c $(CELLULAR) 38 | 39 | tests/test-parser: tests/test-parser.o src/parser.o 40 | 41 | src/example-at: src/example-at.o src/parser.o src/at-unix.o 42 | src/example-sim800: src/example-sim800.o src/modem/sim800.o src/modem/common.o src/cellular.o src/at-unix.o src/parser.o 43 | 44 | .PHONY: all test clean 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Attentive: AT/GSM stack in pure C 2 | 3 | *[NOTE: Open sourced internal company product. Work in progress. Not production/release ready yet.]* 4 | 5 | ## Features 6 | 7 | * Full AT stack (command generation + response parsing). 8 | * Modular design. 9 | * Written in pure ANSI C99 with optional POSIX addons. 10 | * Compliant with [ITU V.250](https://www.itu.int/rec/T-REC-V.250/en) and 11 | [3GPP TS 27.007](http://www.3gpp.org/DynaReport/27007.htm) for maximum interoperatibility. 12 | * Tolerant to even the most misbehaving modems out there (if instructed so). 13 | * Easily extendable to support new modems. 14 | 15 | ## Supported modem APIs 16 | 17 | * TCP/IP support (sockets). 18 | * FTP transfers. 19 | * Date/time operations. 20 | * BTS-based location. 21 | 22 | ## Supported modems 23 | 24 | * Generic (Hayes AT, GSM, etc.) 25 | * Telit modems (SELINT 2 compatibility level) 26 | * Simcom SIM800 series 27 | 28 | ## License 29 | 30 | Attentive was written by Kosma Moczek at [Cloud Your Car](https://cloudyourcar.com/). 31 | 32 | > Copyright © 2014 Kosma Moczek \ 33 | > 34 | > This program is free software. It comes without any warranty, to the extent 35 | > permitted by applicable law. You can redistribute it and/or modify it under 36 | > the terms of the Do What The Fuck You Want To Public License, Version 2, as 37 | > published by Sam Hocevar. See the COPYING file for more details. 38 | -------------------------------------------------------------------------------- /include/attentive/at-unix.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2014 Kosma Moczek 3 | * This program is free software. It comes without any warranty, to the extent 4 | * permitted by applicable law. You can redistribute it and/or modify it under 5 | * the terms of the Do What The Fuck You Want To Public License, Version 2, as 6 | * published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | #ifndef ATTENTIVE_AT_UNIX_H 10 | #define ATTENTIVE_AT_UNIX_H 11 | 12 | #include 13 | 14 | #include 15 | 16 | /** 17 | * Create an AT channel instance. 18 | * 19 | * @param devpath Device path. 20 | * @param baudrate If non-zero, sets device baudrate (see termios.h). 21 | * @returns Instance pointer on success, NULL and sets errno on failure. 22 | */ 23 | struct at *at_alloc_unix(const char *devpath, speed_t baudrate); 24 | 25 | #endif 26 | 27 | /* vim: set ts=4 sw=4 et: */ 28 | -------------------------------------------------------------------------------- /include/attentive/at.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2014 Kosma Moczek 3 | * This program is free software. It comes without any warranty, to the extent 4 | * permitted by applicable law. You can redistribute it and/or modify it under 5 | * the terms of the Do What The Fuck You Want To Public License, Version 2, as 6 | * published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | #ifndef ATTENTIVE_AT_H 10 | #define ATTENTIVE_AT_H 11 | 12 | #include 13 | 14 | /* 15 | * Publicly accessible fields. Platform-specific implementations may add private 16 | * fields at the end of this struct. 17 | */ 18 | struct at { 19 | struct at_parser *parser; 20 | const struct at_callbacks *cbs; 21 | void *arg; 22 | at_line_scanner_t command_scanner; 23 | }; 24 | 25 | struct at_callbacks { 26 | at_line_scanner_t scan_line; 27 | at_response_handler_t handle_urc; 28 | }; 29 | 30 | /** 31 | * Create an AT channel instance. 32 | * 33 | * NOTE: This is a generic interface; you should include a platform-specific 34 | * file and call at_alloc_platform() variant instead. 35 | * 36 | * @returns Instance pointer on success, NULL and sets errno on failure. 37 | */ 38 | struct at *at_alloc(void); 39 | 40 | /** 41 | * Open the AT channel. 42 | * 43 | * @param at AT channel instance. 44 | * @returns Zero on success, -1 and sets errno on failure. 45 | */ 46 | int at_open(struct at *at); 47 | 48 | /** 49 | * Close the AT channel. 50 | * 51 | * @param at AT channel instance. 52 | * @returns Zero on success, -1 and sets errno on failure. 53 | */ 54 | int at_close(struct at *at); 55 | 56 | /** 57 | * Close and free an AT channel instance. 58 | * 59 | * @param at AT channel instance. 60 | */ 61 | void at_free(struct at *at); 62 | 63 | /** 64 | * Set AT channel callbacks. 65 | * 66 | * @param at AT channel instance. 67 | * @param cbs Set of callbacks. Not copied. 68 | * @param arg Private argument passed to callbacks. 69 | */ 70 | void at_set_callbacks(struct at *at, const struct at_callbacks *cbs, void *arg); 71 | 72 | /** 73 | * Set custom per-command line scanner for the next command. 74 | * 75 | * @param at AT channel instance. 76 | * @param scanner Line scanner callback. 77 | */ 78 | void at_set_command_scanner(struct at *at, at_line_scanner_t scanner); 79 | 80 | /** 81 | * Expect "> " dataprompt as a response for the next command. 82 | * 83 | * @param at AT channel instance. 84 | */ 85 | void at_expect_dataprompt(struct at *at); 86 | 87 | /** 88 | * Set command timeout. 89 | * 90 | * @param at AT channel instance. 91 | * @param timeout Timeout in seconds (zero to disable). 92 | */ 93 | void at_set_timeout(struct at *at, int timeout); 94 | 95 | /** 96 | * Send an AT command and receive a response. Accepts printf-compatible 97 | * format and arguments. 98 | * 99 | * @param at AT channel instance. 100 | * @param format printf-comaptible format. 101 | * @returns Pointer to response (valid until next at_command) or NULL 102 | * if a timeout occurs. Response is newline-delimited and does 103 | * not include the final "OK". 104 | */ 105 | __attribute__ ((format (printf, 2, 3))) 106 | const char *at_command(struct at *at, const char *format, ...); 107 | 108 | /** 109 | * Send raw data over the AT channel. 110 | * 111 | * @param at AT channel instance. 112 | * @param data Raw data to send. 113 | * @param size Data size in bytes. 114 | * @returns Pointer to response (valid until next at_command) or NULL 115 | * if a timeout occurs. 116 | */ 117 | const char *at_command_raw(struct at *at, const void *data, size_t size); 118 | 119 | /** 120 | * Send an AT command and return -1 if it doesn't return OK. 121 | */ 122 | #define at_command_simple(at, cmd...) \ 123 | do { \ 124 | const char *_response = at_command(at, cmd); \ 125 | if (!_response) \ 126 | return -1; /* timeout */ \ 127 | if (strcmp(_response, "")) { \ 128 | errno = EINVAL; \ 129 | return -1; \ 130 | } \ 131 | } while (0) 132 | 133 | /** 134 | * Send raw data and return -1 if it doesn't return OK. 135 | */ 136 | #define at_command_raw_simple(at, cmd...) \ 137 | do { \ 138 | const char *_response = at_command_raw(at, cmd); \ 139 | if (!_response) \ 140 | return -1; /* timeout */ \ 141 | if (strcmp(_response, "")) { \ 142 | errno = EINVAL; \ 143 | return -1; \ 144 | } \ 145 | } while (0) 146 | 147 | /** 148 | * Count macro arguments. Source: 149 | * http://stackoverflow.com/questions/2124339/c-preprocessor-va-args-number-of-arguments 150 | */ 151 | #define _NUMARGS(...) (sizeof((void *[]){0, ##__VA_ARGS__})/sizeof(void *)-1) 152 | 153 | /** 154 | * Scanf a response and return -1 if it fails. 155 | */ 156 | #define at_simple_scanf(_response, format, ...) \ 157 | do { \ 158 | if (!_response) \ 159 | return -1; /* timeout */ \ 160 | if (sscanf(_response, format, __VA_ARGS__) != _NUMARGS(__VA_ARGS__)) { \ 161 | errno = EINVAL; \ 162 | return -1; \ 163 | } \ 164 | } while (0) 165 | 166 | #endif 167 | 168 | /* vim: set ts=4 sw=4 et: */ 169 | -------------------------------------------------------------------------------- /include/attentive/cellular.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2014 Kosma Moczek 3 | * This program is free software. It comes without any warranty, to the extent 4 | * permitted by applicable law. You can redistribute it and/or modify it under 5 | * the terms of the Do What The Fuck You Want To Public License, Version 2, as 6 | * published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | #ifndef ATTENTIVE_CELLULAR_H 10 | #define ATTENTIVE_CELLULAR_H 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | 20 | #define CELLULAR_IMEI_LENGTH 15 21 | #define CELLULAR_MEID_LENGTH 14 22 | #define CELLULAR_ICCID_LENGTH 19 23 | 24 | 25 | enum { 26 | CREG_NOT_REGISTERED = 0, 27 | CREG_REGISTERED_HOME = 1, 28 | CREG_SEARCHING = 2, 29 | CREG_REGISTRATION_DENIED = 3, 30 | CREG_UNKNOWN = 4, 31 | CREG_REGISTERED_ROAMING = 5, 32 | }; 33 | 34 | struct cellular { 35 | const struct cellular_ops *ops; 36 | struct at *at; 37 | 38 | /* Private fields. */ 39 | const char *apn; 40 | int pdp_failures; 41 | int pdp_threshold; 42 | }; 43 | 44 | struct cellular_ops { 45 | int (*attach)(struct cellular *modem); 46 | int (*detach)(struct cellular *modem); 47 | int (*pdp_open)(struct cellular *modem, const char *apn); 48 | int (*pdp_close)(struct cellular *modem); 49 | 50 | /** Read GSM modem serial number (IMEI). */ 51 | int (*imei)(struct cellular *modem, char *buf, size_t len); 52 | /** Read CDMA modem serial number (MEID). */ 53 | int (*meid)(struct cellular *modem, char *buf, size_t len); 54 | /** Read SIM serial number (ICCID). */ 55 | int (*iccid)(struct cellular *modem, char *iccid, size_t len); 56 | 57 | /** Get network registration status. */ 58 | int (*creg)(struct cellular *modem); 59 | /** Get signal strength. */ 60 | int (*rssi)(struct cellular *modem); 61 | 62 | /** Read RTC date and time. Compatible with clock_gettime(). */ 63 | int (*clock_gettime)(struct cellular *modem, struct timespec *ts); 64 | /** Set RTC date and time. Compatible with clock_settime(). */ 65 | int (*clock_settime)(struct cellular *modem, const struct timespec *ts); 66 | /** Get network date and time. */ 67 | int (*clock_ntptime)(struct cellular *modem, struct timespec *ts); 68 | 69 | int (*socket_connect)(struct cellular *modem, int connid, const char *host, uint16_t port); 70 | ssize_t (*socket_send)(struct cellular *modem, int connid, const void *buffer, size_t amount, int flags); 71 | ssize_t (*socket_recv)(struct cellular *modem, int connid, void *buffer, size_t length, int flags); 72 | int (*socket_waitack)(struct cellular *modem, int connid); 73 | int (*socket_close)(struct cellular *modem, int connid); 74 | 75 | int (*ftp_open)(struct cellular *modem, const char *host, uint16_t port, const char *username, const char *password, bool passive); 76 | int (*ftp_get)(struct cellular *modem, const char *filename); 77 | int (*ftp_getdata)(struct cellular *modem, char *buffer, size_t length); 78 | int (*ftp_close)(struct cellular *modem); 79 | 80 | int (*locate)(struct cellular *modem, float *latitude, float *longitude, float *altitude); 81 | }; 82 | 83 | 84 | /** 85 | * Allocate a cellular modem instance. 86 | * 87 | * NOTE: This is an abstract interface. You sould be using the modem-specific 88 | * cellular_alloc_*() function instead. 89 | * 90 | * @returns Instance pointer on success, NULL and sets errno on failure. 91 | */ 92 | struct cellular *cellular_alloc(void); 93 | 94 | /** 95 | * Attach cellular modem instance to an AT channel. 96 | * Performs initialization, attaches callbacks, etc. 97 | * 98 | * @param modem Cellular modem instance. 99 | * @param at AT channel instance. 100 | * @param apn APN name. Not copied. 101 | * @returns Zero on success, -1 and sets errno on failure. 102 | */ 103 | int cellular_attach(struct cellular *modem, struct at *at, const char *apn); 104 | 105 | /** 106 | * Detach cellular modem instance. 107 | * @param modem Cellular modem instance. 108 | * @returns Zero on success, -1 and sets errno on failure. 109 | */ 110 | int cellular_detach(struct cellular *modem); 111 | 112 | /** 113 | * Free a cellular modem instance. 114 | * 115 | * @param Modem instance allocated with cellular_alloc(). 116 | */ 117 | void cellular_free(struct cellular *modem); 118 | 119 | 120 | /* Modem-specific variants below. */ 121 | 122 | struct cellular *cellular_generic_alloc(void); 123 | void cellular_generic_free(struct cellular *modem); 124 | 125 | struct cellular *cellular_telit2_alloc(void); 126 | void cellular_telit2_free(struct cellular *modem); 127 | 128 | struct cellular *cellular_sim800_alloc(void); 129 | void cellular_sim800_free(struct cellular *modem); 130 | 131 | #endif 132 | 133 | /* vim: set ts=4 sw=4 et: */ 134 | -------------------------------------------------------------------------------- /include/attentive/parser.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2014 Kosma Moczek 3 | * This program is free software. It comes without any warranty, to the extent 4 | * permitted by applicable law. You can redistribute it and/or modify it under 5 | * the terms of the Do What The Fuck You Want To Public License, Version 2, as 6 | * published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | #ifndef ATTENTIVE_PARSER_H 10 | #define ATTENTIVE_PARSER_H 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | /** 18 | * AT response type. 19 | * 20 | * Describes response lines that can be received from the modem. See V.25ter 21 | * and 3GPP TS 27.007 for the vocabulary. 22 | */ 23 | enum at_response_type { 24 | AT_RESPONSE_UNEXPECTED = -1, /**< Unexpected line; usually an unhandled URC. */ 25 | AT_RESPONSE_UNKNOWN = 0, /**< Pass the response to next parser in chain. */ 26 | AT_RESPONSE_INTERMEDIATE, /**< Intermediate response. Stored. */ 27 | AT_RESPONSE_FINAL_OK, /**< Final response. NOT stored. */ 28 | AT_RESPONSE_FINAL, /**< Final response. Stored. */ 29 | AT_RESPONSE_URC, /**< Unsolicited Result Code. Passed to URC handler. */ 30 | _AT_RESPONSE_RAWDATA_FOLLOWS, /**< @internal (see AT_RESPONSE_RAWDATA_FOLLOWS) */ 31 | _AT_RESPONSE_HEXDATA_FOLLOWS, /**< @internal (see AT_RESPONSE_HEXDATA_FOLLOWS) */ 32 | 33 | _AT_RESPONSE_ENUM_SIZE_SHOULD_BE_INT32 = INT32_MAX 34 | }; 35 | /** The line is followed by a newline and a block of raw data. */ 36 | #define AT_RESPONSE_RAWDATA_FOLLOWS(amount) \ 37 | (_AT_RESPONSE_RAWDATA_FOLLOWS | ((amount) << 8)) 38 | /** The line is followed by a newline and a block of hex-escaped data. */ 39 | #define AT_RESPONSE_HEXDATA_FOLLOWS(amount) \ 40 | (_AT_RESPONSE_HEXDATA_FOLLOWS | ((amount) << 8)) 41 | /** @internal */ 42 | #define _AT_RESPONSE_TYPE_MASK 0xff 43 | 44 | /** Line scanner. Should return one of the AT_RESPONSE_* values if the line is 45 | * identified or AT_RESPONSE_UNKNOWN to fall back to the default scanner. */ 46 | typedef enum at_response_type (*at_line_scanner_t)(const char *line, size_t len, void *priv); 47 | 48 | /** Response handler. */ 49 | typedef void (*at_response_handler_t)(const char *line, size_t len, void *priv); 50 | 51 | struct at_parser_callbacks { 52 | at_line_scanner_t scan_line; 53 | at_response_handler_t handle_response; 54 | at_response_handler_t handle_urc; 55 | }; 56 | 57 | /** 58 | * Allocate a parser instance. 59 | * 60 | * @param cbs Parser callbacks. Structure is not copied; must persist for 61 | * the lifetime of the parser. 62 | * @param bufsize Response buffer size on bytes. 63 | * @param priv Private argument; passed to callbacks. 64 | * @returns Parser instance pointer. 65 | */ 66 | struct at_parser *at_parser_alloc(const struct at_parser_callbacks *cbs, size_t bufsize, void *priv); 67 | 68 | /** 69 | * Reset parser instance to initial state. 70 | * 71 | * @param parser Parser instance. 72 | */ 73 | void at_parser_reset(struct at_parser *parser); 74 | 75 | /** 76 | * Make the parser expect a dataprompt for the next command. 77 | * 78 | * Some AT commands, mostly those used for transmitting raw data, return a "> " 79 | * prompt (without a newline). The parser must be told explicitly to expect it 80 | * on a per-command basis. 81 | * 82 | * @param parser Parser instance. 83 | */ 84 | void at_parser_expect_dataprompt(struct at_parser *parser); 85 | 86 | /** 87 | * Inform the parser that a command will be invoked. Causes a response callback 88 | * at the next command completion. 89 | * 90 | * @param parser Parser instance. 91 | */ 92 | void at_parser_await_response(struct at_parser *parser); 93 | 94 | /** 95 | * Feed parser. Callbacks are always called from this function's context. 96 | * 97 | * @param parser Parser instance. 98 | * @param data Bytes to feed. 99 | * @param len Number of bytes in data. 100 | */ 101 | void at_parser_feed(struct at_parser *parser, const void *data, size_t len); 102 | 103 | /** 104 | * Deallocate a parser instance. 105 | * 106 | * @param parser Parser instance allocated with at_parser_alloc. 107 | */ 108 | void at_parser_free(struct at_parser *parser); 109 | 110 | /** 111 | * Check if a response starts with one of the prefixes in a table. 112 | * 113 | * @param line AT response line. 114 | * @param table List of prefixes. 115 | * @returns True if found, false otherwise. 116 | */ 117 | bool at_prefix_in_table(const char *line, const char *const table[]); 118 | 119 | #endif 120 | 121 | /* vim: set ts=4 sw=4 et: */ 122 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | example-at 2 | example-sim800 3 | -------------------------------------------------------------------------------- /src/at-unix.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2014 Kosma Moczek 3 | * This program is free software. It comes without any warranty, to the extent 4 | * permitted by applicable law. You can redistribute it and/or modify it under 5 | * the terms of the Do What The Fuck You Want To Public License, Version 2, as 6 | * published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #if _POSIX_TIMERS > 0 22 | #include 23 | #else 24 | #include 25 | #endif 26 | 27 | // Remove once you refactor this out. 28 | #define AT_COMMAND_LENGTH 80 29 | 30 | struct at_unix { 31 | struct at at; 32 | 33 | const char *devpath; /**< Serial port device path. */ 34 | speed_t baudrate; /**< Serial port baudate. */ 35 | 36 | int timeout; /**< Command timeout in seconds. */ 37 | const char *response; 38 | 39 | pthread_t thread; /**< Reader thread. */ 40 | pthread_mutex_t mutex; /**< Protects variables below and the parser. */ 41 | pthread_cond_t cond; /**< For signalling open/busy release. */ 42 | 43 | int fd; /**< Serial port file descriptor. */ 44 | bool running : 1; /**< Reader thread should be running. */ 45 | bool open : 1; /**< FD is valid. Set/cleared by open()/close(). */ 46 | bool busy : 1; /**< FD is in use. Set/cleared by reader thread. */ 47 | bool waiting : 1; /**< Waiting for response callback to arrive. */ 48 | }; 49 | 50 | void *at_reader_thread(void *arg); 51 | 52 | static void handle_sigusr1(int signal) 53 | { 54 | (void)signal; 55 | } 56 | 57 | static void handle_response(const char *buf, size_t len, void *arg) 58 | { 59 | struct at_unix *priv = (struct at_unix *) arg; 60 | 61 | /* The mutex is held by the reader thread; don't reacquire. */ 62 | priv->response = buf; 63 | (void) len; 64 | priv->waiting = false; 65 | pthread_cond_signal(&priv->cond); 66 | } 67 | 68 | static void handle_urc(const char *buf, size_t len, void *arg) 69 | { 70 | struct at *at = (struct at *) arg; 71 | 72 | /* Forward to caller's URC callback, if any. */ 73 | if (at->cbs->handle_urc) 74 | at->cbs->handle_urc(buf, len, at->arg); 75 | } 76 | 77 | enum at_response_type scan_line(const char *line, size_t len, void *arg) 78 | { 79 | struct at *at = (struct at *) arg; 80 | 81 | enum at_response_type type = AT_RESPONSE_UNKNOWN; 82 | if (at->command_scanner) 83 | type = at->command_scanner(line, len, at->arg); 84 | if (!type && at->cbs && at->cbs->scan_line) 85 | type = at->cbs->scan_line(line, len, at->arg); 86 | return type; 87 | } 88 | 89 | static const struct at_parser_callbacks parser_callbacks = { 90 | .handle_response = handle_response, 91 | .handle_urc = handle_urc, 92 | .scan_line = scan_line, 93 | }; 94 | 95 | struct at *at_alloc_unix(const char *devpath, speed_t baudrate) 96 | { 97 | /* allocate instance */ 98 | struct at_unix *priv = malloc(sizeof(struct at_unix)); 99 | if (!priv) { 100 | errno = ENOMEM; 101 | return NULL; 102 | } 103 | memset(priv, 0, sizeof(struct at_unix)); 104 | 105 | /* allocate underlying parser */ 106 | priv->at.parser = at_parser_alloc(&parser_callbacks, 256, (void *) priv); 107 | if (!priv->at.parser) { 108 | free(priv); 109 | return NULL; 110 | } 111 | 112 | /* copy over device parameters */ 113 | priv->devpath = devpath; 114 | priv->baudrate = baudrate; 115 | 116 | /* install empty SIGUSR1 handler */ 117 | struct sigaction sa = { 118 | .sa_handler = handle_sigusr1, 119 | }; 120 | sigaction(SIGUSR1, &sa, NULL); 121 | 122 | /* initialize and start reader thread */ 123 | priv->running = true; 124 | pthread_mutex_init(&priv->mutex, NULL); 125 | pthread_cond_init(&priv->cond, NULL); 126 | pthread_create(&priv->thread, NULL, at_reader_thread, (void *) priv); 127 | 128 | return (struct at *) priv; 129 | } 130 | 131 | int at_open(struct at *at) 132 | { 133 | struct at_unix *priv = (struct at_unix *) at; 134 | 135 | pthread_mutex_lock(&priv->mutex); 136 | if (priv->open) { 137 | pthread_mutex_unlock(&priv->mutex); 138 | return 0; 139 | } 140 | 141 | priv->fd = open(priv->devpath, O_RDWR); 142 | if (priv->fd == -1) { 143 | pthread_mutex_unlock(&priv->mutex); 144 | return -1; 145 | } 146 | 147 | if (priv->baudrate) { 148 | struct termios attr; 149 | tcgetattr(priv->fd, &attr); 150 | cfsetspeed(&attr, priv->baudrate); 151 | tcsetattr(priv->fd, TCSANOW, &attr); 152 | } 153 | 154 | priv->open = true; 155 | pthread_cond_signal(&priv->cond); 156 | pthread_mutex_unlock(&priv->mutex); 157 | 158 | return 0; 159 | } 160 | 161 | int at_close(struct at *at) 162 | { 163 | struct at_unix *priv = (struct at_unix *) at; 164 | 165 | pthread_mutex_lock(&priv->mutex); 166 | if (!priv->open) { 167 | pthread_mutex_unlock(&priv->mutex); 168 | return 0; 169 | } 170 | 171 | /* Mark the port descriptor as invalid. */ 172 | priv->open = false; 173 | 174 | /* Interrupt read() in the reader thread. */ 175 | pthread_kill(priv->thread, SIGUSR1); 176 | 177 | /* Wait for the read operation to complete. */ 178 | while (priv->busy) 179 | pthread_cond_wait(&priv->cond, &priv->mutex); 180 | 181 | /* Close the file descriptor. */ 182 | close(priv->fd); 183 | priv->fd = -1; 184 | 185 | pthread_mutex_unlock(&priv->mutex); 186 | return 0; 187 | } 188 | 189 | void at_free(struct at *at) 190 | { 191 | struct at_unix *priv = (struct at_unix *) at; 192 | 193 | /* make sure the channel is closed */ 194 | at_close(at); 195 | 196 | /* ask the reader thread to terminate */ 197 | pthread_mutex_lock(&priv->mutex); 198 | priv->running = false; 199 | pthread_mutex_unlock(&priv->mutex); 200 | 201 | /* wait for the reader thread to terminate */ 202 | pthread_kill(priv->thread, SIGUSR1); 203 | pthread_join(priv->thread, NULL); 204 | pthread_cond_destroy(&priv->cond); 205 | pthread_mutex_destroy(&priv->mutex); 206 | 207 | /* free up resources */ 208 | free(priv->at.parser); 209 | free(priv); 210 | } 211 | 212 | void at_set_callbacks(struct at *at, const struct at_callbacks *cbs, void *arg) 213 | { 214 | at->cbs = cbs; 215 | at->arg = arg; 216 | } 217 | 218 | void at_set_command_scanner(struct at *at, at_line_scanner_t scanner) 219 | { 220 | at->command_scanner = scanner; 221 | } 222 | 223 | void at_set_timeout(struct at *at, int timeout) 224 | { 225 | struct at_unix *priv = (struct at_unix *) at; 226 | 227 | priv->timeout = timeout; 228 | } 229 | 230 | void at_expect_dataprompt(struct at *at) 231 | { 232 | at_parser_expect_dataprompt(at->parser); 233 | } 234 | 235 | static const char *_at_command(struct at_unix *priv, const void *data, size_t size) 236 | { 237 | pthread_mutex_lock(&priv->mutex); 238 | 239 | /* Bail out if the channel is closing or closed. */ 240 | if (!priv->open) { 241 | pthread_mutex_unlock(&priv->mutex); 242 | errno = ENODEV; 243 | return NULL; 244 | } 245 | 246 | /* Prepare parser. */ 247 | at_parser_await_response(priv->at.parser); 248 | 249 | /* Send the command. */ 250 | // FIXME: handle interrupts, short writes, errors, etc. 251 | write(priv->fd, data, size); 252 | 253 | /* Wait for the parser thread to collect a response. */ 254 | priv->waiting = true; 255 | if (priv->timeout) { 256 | struct timespec ts; 257 | #if _POSIX_TIMERS > 0 258 | clock_gettime(CLOCK_REALTIME, &ts); 259 | #else 260 | struct timeval tv; 261 | gettimeofday(&tv, NULL); 262 | ts.tv_sec = tv.tv_sec; 263 | ts.tv_nsec = tv.tv_usec * 1000; 264 | #endif 265 | ts.tv_sec += priv->timeout; 266 | 267 | while (priv->open && priv->waiting) 268 | if (pthread_cond_timedwait(&priv->cond, &priv->mutex, &ts) == ETIMEDOUT) 269 | break; 270 | } else { 271 | while (priv->open && priv->waiting) 272 | pthread_cond_wait(&priv->cond, &priv->mutex); 273 | } 274 | 275 | const char *result; 276 | if (!priv->open) { 277 | /* The serial port was closed behind our back. */ 278 | errno = ENODEV; 279 | result = NULL; 280 | } else if (priv->waiting) { 281 | /* Timed out waiting for a response. */ 282 | at_parser_reset(priv->at.parser); 283 | errno = ETIMEDOUT; 284 | result = NULL; 285 | } else { 286 | /* Response arrived. */ 287 | result = priv->response; 288 | } 289 | 290 | /* Reset per-command settings. */ 291 | priv->at.command_scanner = NULL; 292 | 293 | pthread_mutex_unlock(&priv->mutex); 294 | 295 | return result; 296 | } 297 | 298 | const char *at_command(struct at *at, const char *format, ...) 299 | { 300 | struct at_unix *priv = (struct at_unix *) at; 301 | 302 | /* Build command string. */ 303 | va_list ap; 304 | va_start(ap, format); 305 | char line[AT_COMMAND_LENGTH]; 306 | int len = vsnprintf(line, sizeof(line)-1, format, ap); 307 | va_end(ap); 308 | 309 | /* Bail out if we run out of space. */ 310 | if (len >= (int)(sizeof(line)-1)) { 311 | errno = ENOMEM; 312 | return NULL; 313 | } 314 | 315 | printf("> %s\n", line); 316 | 317 | /* Append modem-style newline. */ 318 | line[len++] = '\r'; 319 | 320 | /* Send the command. */ 321 | return _at_command(priv, line, len); 322 | } 323 | 324 | const char *at_command_raw(struct at *at, const void *data, size_t size) 325 | { 326 | struct at_unix *priv = (struct at_unix *) at; 327 | 328 | printf("> [%zu bytes]\n", size); 329 | 330 | return _at_command(priv, data, size); 331 | } 332 | 333 | void *at_reader_thread(void *arg) 334 | { 335 | struct at_unix *priv = (struct at_unix *)arg; 336 | 337 | printf("at_reader_thread[%s]: starting\n", priv->devpath); 338 | 339 | while (true) { 340 | pthread_mutex_lock(&priv->mutex); 341 | 342 | /* Wait for the port descriptor to be valid. */ 343 | while (priv->running && !priv->open) 344 | pthread_cond_wait(&priv->cond, &priv->mutex); 345 | 346 | if (!priv->running) { 347 | /* Time to die. */ 348 | pthread_mutex_unlock(&priv->mutex); 349 | break; 350 | } 351 | 352 | /* Lock access to the port descriptor. */ 353 | priv->busy = true; 354 | pthread_mutex_unlock(&priv->mutex); 355 | 356 | /* Attempt to read some data. */ 357 | char ch; 358 | int result = read(priv->fd, &ch, 1); 359 | int why = errno; 360 | 361 | pthread_mutex_lock(&priv->mutex); 362 | /* Unlock access to the port descriptor. */ 363 | priv->busy = false; 364 | /* Notify at_close() that the port is now free. */ 365 | pthread_cond_signal(&priv->cond); 366 | pthread_mutex_unlock(&priv->mutex); 367 | 368 | if (result == 1) { 369 | /* Data received, feed the parser. */ 370 | pthread_mutex_lock(&priv->mutex); 371 | at_parser_feed(priv->at.parser, &ch, 1); 372 | pthread_mutex_unlock(&priv->mutex); 373 | } else if (result == -1) { 374 | printf("at_reader_thread[%s]: %s\n", priv->devpath, strerror(why)); 375 | if (why == EINTR) 376 | continue; 377 | else 378 | break; 379 | } else { 380 | printf("at_reader_thread[%s]: received EOF\n", priv->devpath); 381 | break; 382 | } 383 | } 384 | 385 | printf("at_reader_thread[%s]: finished\n", priv->devpath); 386 | 387 | return NULL; 388 | } 389 | 390 | /* vim: set ts=4 sw=4 et: */ 391 | -------------------------------------------------------------------------------- /src/cellular.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2014 Kosma Moczek 3 | * This program is free software. It comes without any warranty, to the extent 4 | * permitted by applicable law. You can redistribute it and/or modify it under 5 | * the terms of the Do What The Fuck You Want To Public License, Version 2, as 6 | * published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | #include 10 | 11 | #include "modem/common.h" 12 | 13 | 14 | int cellular_attach(struct cellular *modem, struct at *at, const char *apn) 15 | { 16 | /* Do nothing if we're already attached. */ 17 | if (modem->at) 18 | return 0; 19 | 20 | modem->at = at; 21 | modem->apn = apn; 22 | 23 | /* Reset PDP failure counters. */ 24 | cellular_pdp_success(modem); 25 | 26 | return modem->ops->attach ? modem->ops->attach(modem) : 0; 27 | } 28 | 29 | int cellular_detach(struct cellular *modem) 30 | { 31 | /* Do nothing if we're not attached. */ 32 | if (!modem->at) 33 | return 0; 34 | 35 | int result = modem->ops->detach? modem->ops->detach(modem) : 0; 36 | modem->at = NULL; 37 | return result; 38 | } 39 | 40 | /* vim: set ts=4 sw=4 et: */ 41 | -------------------------------------------------------------------------------- /src/example-at.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2014 Kosma Moczek 3 | * This program is free software. It comes without any warranty, to the extent 4 | * permitted by applicable law. You can redistribute it and/or modify it under 5 | * the terms of the Do What The Fuck You Want To Public License, Version 2, as 6 | * published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | static void generic_modem_handle_urc(const void *line, size_t len, void *arg) 18 | { 19 | printf("[%p] URC: %.*s\n", arg, (int) len, (char *) line); 20 | } 21 | 22 | static const struct at_callbacks generic_modem_callbacks = { 23 | .handle_urc = generic_modem_handle_urc, 24 | }; 25 | 26 | int main(int argc, char *argv[]) 27 | { 28 | assert(argc-1 == 1); 29 | const char *devpath = argv[1]; 30 | 31 | printf("allocating channel...\n"); 32 | struct at *at = at_alloc_unix(devpath, B115200); 33 | 34 | printf("opening port...\n"); 35 | assert(at_open(at) == 0); 36 | 37 | printf("attaching callbacks\n"); 38 | at_set_callbacks(at, &generic_modem_callbacks, NULL); 39 | 40 | const char *commands[] = { 41 | "AT", 42 | "ATE0", 43 | "AT+CGMR", 44 | "AT+CGSN", 45 | "AT+CCID", 46 | "AT+CMEE=0", 47 | "AT+BLAH", 48 | "AT+CMEE=2", 49 | "AT+BLAH", 50 | NULL 51 | }; 52 | 53 | printf("sending commands...\n"); 54 | at_set_timeout(at, 10); 55 | for (const char **command=commands; *command; command++) { 56 | const char *result = at_command(at, *command); 57 | printf("%s => %s\n", *command, result ? result : strerror(errno)); 58 | } 59 | 60 | printf("freeing resources...\n"); 61 | at_free(at); 62 | 63 | return 0; 64 | } 65 | 66 | /* vim: set ts=4 sw=4 et: */ 67 | -------------------------------------------------------------------------------- /src/example-sim800.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2014 Kosma Moczek 3 | * This program is free software. It comes without any warranty, to the extent 4 | * permitted by applicable law. You can redistribute it and/or modify it under 5 | * the terms of the Do What The Fuck You Want To Public License, Version 2, as 6 | * published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | 19 | int main(int argc, char *argv[]) 20 | { 21 | assert(argc-1 == 2); 22 | const char *devpath = argv[1]; 23 | const char *apn = argv[2]; 24 | 25 | struct at *at = at_alloc_unix(devpath, B115200); 26 | struct cellular *modem = cellular_sim800_alloc(); 27 | 28 | assert(at_open(at) == 0); 29 | assert(cellular_attach(modem, at, apn) == 0); 30 | 31 | printf("* getting network status\n"); 32 | int creg, rssi; 33 | if ((creg = modem->ops->creg(modem)) != -1) { 34 | printf("registration status: %d\n", creg); 35 | } else { 36 | perror("creg"); 37 | } 38 | 39 | if ((rssi = modem->ops->rssi(modem)) != -1) { 40 | printf("signal strength: %d\n", rssi); 41 | } else { 42 | perror("rssi"); 43 | } 44 | 45 | printf("* getting modem time\n"); 46 | struct timespec ts; 47 | if (modem->ops->clock_gettime(modem, &ts) == 0) { 48 | printf("gettime: %s", ctime(&ts.tv_sec)); 49 | } else { 50 | perror("gettime"); 51 | } 52 | 53 | printf("* setting modem time\n"); 54 | if (modem->ops->clock_settime(modem, &ts) != 0) { 55 | perror("settime"); 56 | } 57 | 58 | char imei[CELLULAR_IMEI_LENGTH+1]; 59 | modem->ops->imei(modem, imei, sizeof(imei)); 60 | printf("imei: %s\n", imei); 61 | 62 | /* network stuff. */ 63 | int socket = 2; 64 | 65 | if (modem->ops->socket_connect(modem, socket, "google.com", 80) == 0) { 66 | printf("connect successful\n"); 67 | } else { 68 | perror("connect"); 69 | } 70 | 71 | const char *request = "GET / HTTP/1.0\r\n\r\n"; 72 | if (modem->ops->socket_send(modem, socket, request, strlen(request), 0) == (int) strlen(request)) { 73 | printf("send successful\n"); 74 | } else { 75 | perror("send"); 76 | } 77 | 78 | int len; 79 | char buf[32]; 80 | while ((len = modem->ops->socket_recv(modem, socket, buf, sizeof(buf), 0)) >= 0) { 81 | if (len > 0) 82 | printf("Received: >\x1b[0;1;33m%.*s\x1b[0m<\n", len, buf); 83 | else 84 | sleep(1); 85 | } 86 | 87 | if (modem->ops->socket_close(modem, socket) == 0) { 88 | printf("close successful\n"); 89 | } else { 90 | perror("close"); 91 | } 92 | 93 | assert(cellular_detach(modem) == 0); 94 | assert(at_close(at) == 0); 95 | 96 | cellular_sim800_free(modem); 97 | at_free(at); 98 | 99 | return 0; 100 | } 101 | 102 | /* vim: set ts=4 sw=4 et: */ 103 | -------------------------------------------------------------------------------- /src/modem/common.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2014 Kosma Moczek 3 | * This program is free software. It comes without any warranty, to the extent 4 | * permitted by applicable law. You can redistribute it and/or modify it under 5 | * the terms of the Do What The Fuck You Want To Public License, Version 2, as 6 | * published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include "common.h" 15 | 16 | 17 | #define PDP_RETRY_THRESHOLD_INITIAL 3 18 | #define PDP_RETRY_THRESHOLD_MULTIPLIER 2 19 | 20 | /* 21 | * PDP management logic. 22 | * 23 | * 1. PDP contexts cannot be activated too often. Common GSM etiquette requires 24 | * that some kind of backoff strategy should be implemented to avoid hammering 25 | * the network with requests. Here we use a simple exponential backoff which 26 | * is reset every time a connection succeeds. 27 | * 28 | * 2. Contexts can get stuck sometimes; the modem reports active context but no 29 | * data can be transmitted. Telit modems are especially prone to this if 30 | * AT+CGDCONT is invoked while the context is active. Our logic should handle 31 | * this after a few connection failures. 32 | */ 33 | 34 | int cellular_pdp_request(struct cellular *modem) 35 | { 36 | if (modem->pdp_failures >= modem->pdp_threshold) { 37 | /* Possibly stuck PDP context; close it. */ 38 | modem->ops->pdp_close(modem); 39 | /* Perform exponential backoff. */ 40 | modem->pdp_threshold *= (1+PDP_RETRY_THRESHOLD_MULTIPLIER); 41 | } 42 | 43 | if (modem->ops->pdp_open(modem, modem->apn) != 0) { 44 | cellular_pdp_failure(modem); 45 | return -1; 46 | } 47 | 48 | return 0; 49 | } 50 | 51 | void cellular_pdp_success(struct cellular *modem) 52 | { 53 | modem->pdp_failures = 0; 54 | modem->pdp_threshold = PDP_RETRY_THRESHOLD_INITIAL; 55 | } 56 | 57 | void cellular_pdp_failure(struct cellular *modem) 58 | { 59 | modem->pdp_failures++; 60 | } 61 | 62 | 63 | int cellular_op_imei(struct cellular *modem, char *buf, size_t len) 64 | { 65 | char fmt[16]; 66 | if (snprintf(fmt, sizeof(fmt), "%%[0-9]%ds", (int) len) >= (int) sizeof(fmt)) { 67 | errno = ENOSPC; 68 | return -1; 69 | } 70 | 71 | at_set_timeout(modem->at, 1); 72 | const char *response = at_command(modem->at, "AT+CGSN"); 73 | at_simple_scanf(response, fmt, buf); 74 | buf[len-1] = '\0'; 75 | 76 | return 0; 77 | } 78 | 79 | int cellular_op_iccid(struct cellular *modem, char *buf, size_t len) 80 | { 81 | char fmt[16]; 82 | if (snprintf(fmt, sizeof(fmt), "%%[0-9]%ds", (int) len) >= (int) sizeof(fmt)) { 83 | errno = ENOSPC; 84 | return -1; 85 | } 86 | 87 | at_set_timeout(modem->at, 5); 88 | const char *response = at_command(modem->at, "AT+CCID"); 89 | at_simple_scanf(response, fmt, buf); 90 | buf[len-1] = '\0'; 91 | 92 | return 0; 93 | } 94 | 95 | int cellular_op_creg(struct cellular *modem) 96 | { 97 | int creg; 98 | 99 | at_set_timeout(modem->at, 1); 100 | const char *response = at_command(modem->at, "AT+CREG?"); 101 | at_simple_scanf(response, "+CREG: %*d,%d", &creg); 102 | 103 | return creg; 104 | } 105 | 106 | int cellular_op_rssi(struct cellular *modem) 107 | { 108 | int rssi; 109 | 110 | at_set_timeout(modem->at, 1); 111 | const char *response = at_command(modem->at, "AT+CSQ"); 112 | at_simple_scanf(response, "+CSQ: %d,%*d", &rssi); 113 | 114 | return rssi; 115 | } 116 | 117 | int cellular_op_clock_gettime(struct cellular *modem, struct timespec *ts) 118 | { 119 | struct tm tm; 120 | 121 | at_set_timeout(modem->at, 1); 122 | const char *response = at_command(modem->at, "AT+CCLK?"); 123 | memset(&tm, 0, sizeof(struct tm)); 124 | at_simple_scanf(response, "+CCLK: \"%d/%d/%d,%d:%d:%d%*d\"", 125 | &tm.tm_year, &tm.tm_mon, &tm.tm_mday, 126 | &tm.tm_hour, &tm.tm_min, &tm.tm_sec); 127 | 128 | /* Most modems report some starting date way in the past when they have 129 | * no date/time estimation. */ 130 | if (tm.tm_year < 14) { 131 | errno = EINVAL; 132 | return 1; 133 | } 134 | 135 | /* Adjust values and perform conversion. */ 136 | tm.tm_year += 2000 - 1900; 137 | tm.tm_mon -= 1; 138 | time_t unix_time = timegm(&tm); 139 | if (unix_time == -1) { 140 | errno = EINVAL; 141 | return -1; 142 | } 143 | 144 | /* All good. Return the result. */ 145 | ts->tv_sec = unix_time; 146 | ts->tv_nsec = 0; 147 | return 0; 148 | } 149 | 150 | int cellular_op_clock_settime(struct cellular *modem, const struct timespec *ts) 151 | { 152 | /* Convert time_t to broken-down UTC time. */ 153 | struct tm tm; 154 | gmtime_r(&ts->tv_sec, &tm); 155 | 156 | /* Adjust values to match 3GPP TS 27.007. */ 157 | tm.tm_year += 1900 - 2000; 158 | tm.tm_mon += 1; 159 | 160 | /* Set the time. */ 161 | at_set_timeout(modem->at, 1); 162 | at_command_simple(modem->at, "AT+CCLK=\"%02d/%02d/%02d,%02d:%02d:%02d+00\"", 163 | tm.tm_year, tm.tm_mon, tm.tm_mday, 164 | tm.tm_hour, tm.tm_min, tm.tm_sec); 165 | 166 | return 0; 167 | } 168 | 169 | /* vim: set ts=4 sw=4 et: */ 170 | -------------------------------------------------------------------------------- /src/modem/common.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2014 Kosma Moczek 3 | * This program is free software. It comes without any warranty, to the extent 4 | * permitted by applicable law. You can redistribute it and/or modify it under 5 | * the terms of the Do What The Fuck You Want To Public License, Version 2, as 6 | * published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | #ifndef MODEM_COMMON_H 10 | #define MODEM_COMMON_H 11 | 12 | #include 13 | 14 | /** 15 | * Request a PDP context. Opens one if isn't already active. 16 | * 17 | * @returns Zero on success, -1 and sets errno on failure 18 | */ 19 | int cellular_pdp_request(struct cellular *modem); 20 | 21 | /** 22 | * Signal network connection success. 23 | */ 24 | void cellular_pdp_success(struct cellular *modem); 25 | 26 | /** 27 | * Signal network connection failure. 28 | */ 29 | void cellular_pdp_failure(struct cellular *modem); 30 | 31 | /** 32 | * Perform a network command, requesting a PDP context and signalling success 33 | * or failure to the PDP machinery. Returns -1 on failure. 34 | */ 35 | #define cellular_command_simple_pdp(modem, command...) \ 36 | do { \ 37 | /* Attempt to establish a PDP context. */ \ 38 | if (cellular_pdp_request(modem) != 0) \ 39 | return -1; \ 40 | /* Send the command */ \ 41 | const char *netresponse = at_command(modem->at, command); \ 42 | if (netresponse == NULL || strcmp(netresponse, "")) { \ 43 | cellular_pdp_failure(modem); \ 44 | return -1; \ 45 | } else { \ 46 | cellular_pdp_success(modem); \ 47 | } \ 48 | } while (0) 49 | 50 | /* 51 | * 3GPP TS 27.007 compatible operations. 52 | */ 53 | 54 | int cellular_op_imei(struct cellular *modem, char *buf, size_t len); 55 | int cellular_op_iccid(struct cellular *modem, char *buf, size_t len); 56 | int cellular_op_creg(struct cellular *modem); 57 | int cellular_op_rssi(struct cellular *modem); 58 | int cellular_op_clock_gettime(struct cellular *modem, struct timespec *ts); 59 | int cellular_op_clock_settime(struct cellular *modem, const struct timespec *ts); 60 | 61 | #endif 62 | 63 | /* vim: set ts=4 sw=4 et: */ 64 | -------------------------------------------------------------------------------- /src/modem/generic.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2014 Kosma Moczek 3 | * This program is free software. It comes without any warranty, to the extent 4 | * permitted by applicable law. You can redistribute it and/or modify it under 5 | * the terms of the Do What The Fuck You Want To Public License, Version 2, as 6 | * published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include "common.h" 15 | 16 | 17 | struct cellular_generic { 18 | struct cellular dev; 19 | }; 20 | 21 | static const struct cellular_ops generic_ops = { 22 | .imei = cellular_op_imei, 23 | .iccid = cellular_op_iccid, 24 | .creg = cellular_op_creg, 25 | .rssi = cellular_op_rssi, 26 | .clock_gettime = cellular_op_clock_gettime, 27 | .clock_settime = cellular_op_clock_settime, 28 | }; 29 | 30 | 31 | struct cellular *cellular_generic_alloc(void) 32 | { 33 | struct cellular_generic *modem = malloc(sizeof(struct cellular_generic)); 34 | if (modem == NULL) { 35 | errno = ENOMEM; 36 | return NULL; 37 | } 38 | 39 | modem->dev.ops = &generic_ops; 40 | 41 | return (struct cellular *) modem; 42 | } 43 | 44 | void cellular_generic_free(struct cellular *modem) 45 | { 46 | free(modem); 47 | } 48 | 49 | /* vim: set ts=4 sw=4 et: */ 50 | -------------------------------------------------------------------------------- /src/modem/sim800.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2014 Kosma Moczek 3 | * This program is free software. It comes without any warranty, to the extent 4 | * permitted by applicable law. You can redistribute it and/or modify it under 5 | * the terms of the Do What The Fuck You Want To Public License, Version 2, as 6 | * published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "common.h" 16 | 17 | /* 18 | * SIM800 probably holds the highly esteemed position of the world's worst 19 | * behaving GSM modem, ever. The following quirks have been spotted so far: 20 | * - response continues after OK (AT+CIPSTATUS) 21 | * - response without a final OK (AT+CIFSR) 22 | * - freeform URCs coming at random moments like "DST: 1" (AT+CLTS=1) 23 | * - undocumented URCs like "+CIEV: ..." (AT+CLTS=1) 24 | * - text-only URCs like "NORMAL POWER DOWN" 25 | * - suffix-based URCs like "1, CONNECT OK" (AT+CIPSTART) 26 | * - bizarre OK responses like "SHUT OK" (AT+CIPSHUT) 27 | * - responses without a final OK (sic!) (AT+CIFSR) 28 | * - no response at all (AT&K0) 29 | * We work it all around, but it makes the code unnecessarily complex. 30 | */ 31 | 32 | #define SIM800_AUTOBAUD_ATTEMPTS 5 33 | #define SIM800_WAITACK_TIMEOUT 40 34 | #define SIM800_FTP_TIMEOUT 60 35 | #define SET_TIMEOUT 60 36 | #define NTP_BUF_SIZE 4 37 | 38 | enum sim800_socket_status { 39 | SIM800_SOCKET_STATUS_ERROR = -1, 40 | SIM800_SOCKET_STATUS_UNKNOWN = 0, 41 | SIM800_SOCKET_STATUS_CONNECTED = 1, 42 | }; 43 | 44 | #define SIM800_NSOCKETS 6 45 | #define SIM800_CONNECT_TIMEOUT 20 46 | #define SIM800_CIPCFG_RETRIES 10 47 | 48 | static const char *const sim800_urc_responses[] = { 49 | "+CIPRXGET: 1,", /* incoming socket data notification */ 50 | "+FTPGET: 1,", /* FTP state change notification */ 51 | "+PDP: DEACT", /* PDP disconnected */ 52 | "+SAPBR 1: DEACT", /* PDP disconnected (for SAPBR apps) */ 53 | "*PSNWID: ", /* AT+CLTS network name */ 54 | "*PSUTTZ: ", /* AT+CLTS time */ 55 | "+CTZV: ", /* AT+CLTS timezone */ 56 | "DST: ", /* AT+CLTS dst information */ 57 | "+CIEV: ", /* AT+CLTS undocumented indicator */ 58 | "RDY", /* Assorted crap on newer firmware releases. */ 59 | "+CPIN: READY", 60 | "Call Ready", 61 | "SMS Ready", 62 | "NORMAL POWER DOWN", 63 | "UNDER-VOLTAGE POWER DOWN", 64 | "UNDER-VOLTAGE WARNNING", 65 | "OVER-VOLTAGE POWER DOWN", 66 | "OVER-VOLTAGE WARNNING", 67 | NULL 68 | }; 69 | 70 | struct cellular_sim800 { 71 | struct cellular dev; 72 | 73 | int ftpget1_status; 74 | enum sim800_socket_status socket_status[SIM800_NSOCKETS]; 75 | }; 76 | 77 | static enum at_response_type scan_line(const char *line, size_t len, void *arg) 78 | { 79 | (void) len; 80 | struct cellular_sim800 *priv = arg; 81 | 82 | if (at_prefix_in_table(line, sim800_urc_responses)) 83 | return AT_RESPONSE_URC; 84 | 85 | /* Socket status notifications in form of "%d, ". */ 86 | if (line[0] >= '0' && line[0] <= '0'+SIM800_NSOCKETS && 87 | !strncmp(line+1, ", ", 2)) 88 | { 89 | int socket = line[0] - '0'; 90 | 91 | if (!strcmp(line+3, "CONNECT OK")) 92 | { 93 | priv->socket_status[socket] = SIM800_SOCKET_STATUS_CONNECTED; 94 | return AT_RESPONSE_URC; 95 | } 96 | 97 | if (!strcmp(line+3, "CONNECT FAIL") || 98 | !strcmp(line+3, "ALREADY CONNECT") || 99 | !strcmp(line+3, "CLOSED")) 100 | { 101 | priv->socket_status[socket] = SIM800_SOCKET_STATUS_ERROR; 102 | return AT_RESPONSE_URC; 103 | } 104 | } 105 | 106 | return AT_RESPONSE_UNKNOWN; 107 | } 108 | 109 | static void handle_urc(const char *line, size_t len, void *arg) 110 | { 111 | struct cellular_sim800 *priv = arg; 112 | 113 | printf("[sim800@%p] urc: %.*s\n", priv, (int) len, line); 114 | 115 | if (sscanf(line, "+FTPGET: 1,%d", &priv->ftpget1_status) == 1) 116 | return; 117 | } 118 | 119 | static const struct at_callbacks sim800_callbacks = { 120 | .scan_line = scan_line, 121 | .handle_urc = handle_urc, 122 | }; 123 | 124 | 125 | /** 126 | * SIM800 IP configuration commands fail if the IP application is running, 127 | * even though the configuration settings are already right. The following 128 | * monkey dance is therefore needed. 129 | */ 130 | static int sim800_config(struct cellular *modem, const char *option, const char *value, int attempts) 131 | { 132 | at_set_timeout(modem->at, 10); 133 | 134 | for (int i=0; iat, "AT+%s=%s", option, value); 137 | 138 | /* Query the setting status. */ 139 | const char *response = at_command(modem->at, "AT+%s?", option); 140 | /* Bail out on timeouts. */ 141 | if (response == NULL) 142 | return -1; 143 | 144 | /* Check if the setting has the correct value. */ 145 | char expected[16]; 146 | if (snprintf(expected, sizeof(expected), "+%s: %s", option, value) >= (int) sizeof(expected)) { 147 | errno = ENOBUFS; 148 | return -1; 149 | } 150 | if (!strcmp(response, expected)) 151 | return 0; 152 | 153 | sleep(1); 154 | } 155 | 156 | errno = ETIMEDOUT; 157 | return -1; 158 | } 159 | 160 | 161 | static int sim800_attach(struct cellular *modem) 162 | { 163 | at_set_callbacks(modem->at, &sim800_callbacks, (void *) modem); 164 | 165 | at_set_timeout(modem->at, 1); 166 | 167 | /* Perform autobauding. */ 168 | for (int i=0; iat, "AT"); 170 | if (response != NULL) 171 | /* Modem replied. Good. */ 172 | break; 173 | } 174 | 175 | /* Disable local echo. */ 176 | at_command(modem->at, "ATE0"); 177 | 178 | /* Disable local echo again; make sure it was disabled successfully. */ 179 | at_command_simple(modem->at, "ATE0"); 180 | 181 | /* Initialize modem. */ 182 | static const char *const init_strings[] = { 183 | "AT+IPR=0", /* Enable autobauding if not already enabled. */ 184 | "AT+IFC=0,0", /* Disable hardware flow control. */ 185 | "AT+CMEE=2", /* Enable extended error reporting. */ 186 | "AT+CLTS=0", /* Don't sync RTC with network time, it's broken. */ 187 | "AT+CIURC=0", /* Disable "Call Ready" URC. */ 188 | "AT&W0", /* Save configuration. */ 189 | NULL 190 | }; 191 | for (const char *const *command=init_strings; *command; command++) 192 | at_command_simple(modem->at, "%s", *command); 193 | 194 | /* Configure IP application. */ 195 | 196 | /* Switch to multiple connections mode; it's less buggy. */ 197 | if (sim800_config(modem, "CIPMUX", "1", SIM800_CIPCFG_RETRIES) != 0) 198 | return -1; 199 | /* Receive data manually. */ 200 | if (sim800_config(modem, "CIPRXGET", "1", SIM800_CIPCFG_RETRIES) != 0) 201 | return -1; 202 | /* Enable quick send mode. */ 203 | if (sim800_config(modem, "CIPQSEND", "1", SIM800_CIPCFG_RETRIES) != 0) 204 | return -1; 205 | 206 | return 0; 207 | } 208 | 209 | static int sim800_detach(struct cellular *modem) 210 | { 211 | at_set_callbacks(modem->at, NULL, NULL); 212 | return 0; 213 | } 214 | 215 | static int sim800_clock_gettime(struct cellular *modem, struct timespec *ts) 216 | { 217 | /* TODO: See CYC-1255. */ 218 | errno = ENOSYS; 219 | return -1; 220 | } 221 | 222 | static int sim800_clock_settime(struct cellular *modem, const struct timespec *ts) 223 | { 224 | /* TODO: See CYC-1255. */ 225 | errno = ENOSYS; 226 | return -1; 227 | } 228 | 229 | static int sim800_clock_ntptime(struct cellular *modem, struct timespec *ts) 230 | { 231 | /* network stuff. */ 232 | int socket = 2; 233 | 234 | if (modem->ops->socket_connect(modem, socket, "time-nw.nist.gov", 37) == 0) { 235 | printf("sim800: connect successful\n"); 236 | } else { 237 | perror("sim800: connect failed"); 238 | goto close_conn; 239 | } 240 | 241 | int len = 0; 242 | char buf[NTP_BUF_SIZE]; 243 | while ((len = modem->ops->socket_recv(modem, socket, buf, NTP_BUF_SIZE, 0)) >= 0) 244 | { 245 | if (len > 0) 246 | { 247 | printf("Received: >\x1b[1m"); 248 | for (int i = 0; itv_sec = 0; 257 | for (int i = 0; i<4; i++) 258 | { 259 | ts->tv_sec = (long int)buf[i] + ts->tv_sec*256; 260 | } 261 | printf("sim800: catched UTC timestamp -> %d\n", ts->tv_sec); 262 | ts->tv_sec -= 2208988800L; //UTC to UNIX time conversion 263 | printf("sim800: final UNIX timestamp -> %d\n", ts->tv_sec); 264 | goto close_conn; 265 | } 266 | 267 | len = 0; 268 | } 269 | else 270 | sleep(1); 271 | } 272 | 273 | #if 0 274 | // FIXME: It's wrong. It should be removed 275 | while (modem->ops->socket_recv(modem, socket, NULL, 1, 0) != 0) 276 | {} //flush 277 | #endif 278 | 279 | close_conn: 280 | if (modem->ops->socket_close(modem, socket) == 0) 281 | { 282 | printf("sim800: close successful\n"); 283 | } else { 284 | perror("sim800: close"); 285 | } 286 | 287 | return 0; 288 | } 289 | 290 | static enum at_response_type scanner_cipstatus(const char *line, size_t len, void *arg) 291 | { 292 | (void) len; 293 | (void) arg; 294 | 295 | /* There are response lines after OK. Keep reading. */ 296 | if (!strcmp(line, "OK")) 297 | return AT_RESPONSE_INTERMEDIATE; 298 | /* Collect the entire post-OK response until the last C: line. */ 299 | if (!strncmp(line, "C: 5", strlen("C: 5"))) 300 | return AT_RESPONSE_FINAL; 301 | return AT_RESPONSE_UNKNOWN; 302 | } 303 | 304 | /** 305 | * Retrieve AT+CIPSTATUS state. 306 | * 307 | * @returns Zero if context is open, -1 and sets errno otherwise. 308 | */ 309 | static int sim800_ipstatus(struct cellular *modem) 310 | { 311 | at_set_timeout(modem->at, 10); 312 | at_set_command_scanner(modem->at, scanner_cipstatus); 313 | const char *response = at_command(modem->at, "AT+CIPSTATUS"); 314 | 315 | if (response == NULL) 316 | return -1; 317 | 318 | const char *state = strstr(response, "STATE: "); 319 | if (!state) { 320 | errno = EPROTO; 321 | return -1; 322 | } 323 | state += strlen("STATE: "); 324 | if (!strncmp(state, "IP STATUS", strlen("IP STATUS"))) 325 | return 0; 326 | if (!strncmp(state, "IP PROCESSING", strlen("IP PROCESSING"))) 327 | return 0; 328 | 329 | errno = ENETDOWN; 330 | return -1; 331 | } 332 | 333 | static enum at_response_type scanner_cifsr(const char *line, size_t len, void *arg) 334 | { 335 | (void) len; 336 | (void) arg; 337 | 338 | /* Accept an IP address as an OK response. */ 339 | int ip[4]; 340 | if (sscanf(line, "%d.%d.%d.%d", &ip[0], &ip[1], &ip[2], &ip[3]) == 4) 341 | return AT_RESPONSE_FINAL_OK; 342 | return AT_RESPONSE_UNKNOWN; 343 | } 344 | 345 | static int sim800_pdp_open(struct cellular *modem, const char *apn) 346 | { 347 | at_set_timeout(modem->at, SET_TIMEOUT); 348 | 349 | /* Configure and open context for FTP/HTTP applications. */ 350 | at_command_simple(modem->at, "AT+SAPBR=3,1,APN,\"%s\"", apn); 351 | at_command(modem->at, "AT+SAPBR=1,1"); 352 | 353 | /* Skip the configuration if context is already open. */ 354 | if (sim800_ipstatus(modem) == 0) 355 | return 0; 356 | 357 | /* Commands below don't check the response. This is intentional; instead 358 | * of trying to stay in sync with the GPRS state machine we blindly issue 359 | * the command sequence needed to transition through all the states and 360 | * reach IP STATUS. See SIM800 Series_TCPIP_Application Note_V1.01.pdf for 361 | * the GPRS states documentation. */ 362 | 363 | /* Configure context for TCP/IP applications. */ 364 | at_command(modem->at, "AT+CSTT=\"%s\"", apn); 365 | /* Establish context. */ 366 | at_command(modem->at, "AT+CIICR"); 367 | /* Read local IP address. Switches modem to IP STATUS state. */ 368 | at_set_command_scanner(modem->at, scanner_cifsr); 369 | at_command(modem->at, "AT+CIFSR"); 370 | 371 | return sim800_ipstatus(modem); 372 | } 373 | 374 | 375 | static enum at_response_type scanner_cipshut(const char *line, size_t len, void *arg) 376 | { 377 | (void) len; 378 | (void) arg; 379 | if (!strcmp(line, "SHUT OK")) 380 | return AT_RESPONSE_FINAL_OK; 381 | return AT_RESPONSE_UNKNOWN; 382 | } 383 | 384 | static int sim800_pdp_close(struct cellular *modem) 385 | { 386 | at_set_timeout(modem->at, SET_TIMEOUT); 387 | at_set_command_scanner(modem->at, scanner_cipshut); 388 | at_command_simple(modem->at, "AT+CIPSHUT"); 389 | 390 | return 0; 391 | } 392 | 393 | 394 | static int sim800_socket_connect(struct cellular *modem, int connid, const char *host, uint16_t port) 395 | { 396 | struct cellular_sim800 *priv = (struct cellular_sim800 *) modem; 397 | 398 | /* Send connection request. */ 399 | at_set_timeout(modem->at, SET_TIMEOUT); 400 | priv->socket_status[connid] = SIM800_SOCKET_STATUS_UNKNOWN; 401 | cellular_command_simple_pdp(modem, "AT+CIPSTART=%d,TCP,\"%s\",%d", connid, host, port); 402 | 403 | /* Wait for socket status URC. */ 404 | for (int i=0; isocket_status[connid] == SIM800_SOCKET_STATUS_CONNECTED) { 406 | return 0; 407 | } else if (priv->socket_status[connid] == SIM800_SOCKET_STATUS_ERROR) { 408 | errno = ECONNABORTED; 409 | return -1; 410 | } 411 | sleep(1); 412 | } 413 | 414 | errno = ETIMEDOUT; 415 | return -1; 416 | } 417 | 418 | static enum at_response_type scanner_cipsend(const char *line, size_t len, void *arg) 419 | { 420 | (void) len; 421 | (void) arg; 422 | 423 | int connid, amount; 424 | char last; 425 | if (sscanf(line, "DATA ACCEPT:%d,%d", &connid, &amount) == 2) 426 | return AT_RESPONSE_FINAL_OK; 427 | if (sscanf(line, "%d, SEND O%c", &connid, &last) == 2 && last == 'K') 428 | return AT_RESPONSE_FINAL_OK; 429 | if (sscanf(line, "%d, SEND FAI%c", &connid, &last) == 2 && last == 'L') 430 | return AT_RESPONSE_FINAL; 431 | if (!strcmp(line, "SEND OK")) 432 | return AT_RESPONSE_FINAL_OK; 433 | if (!strcmp(line, "SEND FAIL")) 434 | return AT_RESPONSE_FINAL; 435 | return AT_RESPONSE_UNKNOWN; 436 | } 437 | 438 | static ssize_t sim800_socket_send(struct cellular *modem, int connid, const void *buffer, size_t amount, int flags) 439 | { 440 | (void) flags; 441 | 442 | /* Request transmission. */ 443 | at_set_timeout(modem->at, SET_TIMEOUT); 444 | at_expect_dataprompt(modem->at); 445 | at_command_simple(modem->at, "AT+CIPSEND=%d,%zu", connid, amount); 446 | 447 | /* Send raw data. */ 448 | at_set_command_scanner(modem->at, scanner_cipsend); 449 | at_command_raw_simple(modem->at, buffer, amount); 450 | 451 | return amount; 452 | } 453 | 454 | static enum at_response_type scanner_ciprxget(const char *line, size_t len, void *arg) 455 | { 456 | (void) len; 457 | (void) arg; 458 | 459 | int requested, confirmed; 460 | if (sscanf(line, "+CIPRXGET: 2,%*d,%d,%d", &requested, &confirmed) == 2) 461 | if (requested > 0) 462 | return AT_RESPONSE_RAWDATA_FOLLOWS(requested); 463 | 464 | return AT_RESPONSE_UNKNOWN; 465 | } 466 | 467 | static ssize_t sim800_socket_recv(struct cellular *modem, int connid, void *buffer, size_t length, int flags) 468 | { 469 | (void) flags; 470 | 471 | int cnt = 0; 472 | // TODO its dumb and exceptions should be handled in other right way 473 | // FIXME: It has to be changed. Leave for now 474 | char tries = 127; 475 | while ( (cnt < (int) length) && tries-- ){ 476 | int chunk = (int) length - cnt; 477 | /* Limit read size to avoid overflowing AT response buffer. */ 478 | if (chunk > 128) 479 | chunk = 128; 480 | 481 | /* Perform the read. */ 482 | at_set_timeout(modem->at, SET_TIMEOUT); 483 | at_set_command_scanner(modem->at, scanner_ciprxget); 484 | const char *response = at_command(modem->at, "AT+CIPRXGET=2,%d,%d", connid, chunk); 485 | if (response == NULL) 486 | return -1; 487 | 488 | /* Find the header line. */ 489 | int requested, confirmed; 490 | // TODO: 491 | // 1. connid is not checked 492 | // 2. there is possible a bug here. if not all data are ready (confirmed < requested) 493 | // then wierd things can happen. see memcpy 494 | // requested should be equal to chunk 495 | // confirmed is that what can be read 496 | at_simple_scanf(response, "+CIPRXGET: 2,%*d,%d,%d", &requested, &confirmed); 497 | 498 | /* Bail out if we're out of data. */ 499 | /* FIXME: We should maybe block until we receive something? */ 500 | if (requested == 0) 501 | break; 502 | 503 | /* Locate the payload. */ 504 | /* TODO: what if no \n is in input stream? 505 | * should use strnchr at least */ 506 | const char *data = strchr(response, '\n'); 507 | if (data == NULL) { 508 | errno = EPROTO; 509 | return -1; 510 | } 511 | data += 1; 512 | 513 | /* Copy payload to result buffer. */ 514 | memcpy((char *)buffer + cnt, data, requested); 515 | cnt += requested; 516 | } 517 | 518 | return cnt; 519 | } 520 | 521 | static int sim800_socket_waitack(struct cellular *modem, int connid) 522 | { 523 | const char *response; 524 | 525 | at_set_timeout(modem->at, 5); 526 | for (int i=0; iat, "AT+CIPACK=%d", connid); 530 | at_simple_scanf(response, "+CIPACK: %*d,%*d,%d", &nacklen); 531 | 532 | /* Return if all bytes were acknowledged. */ 533 | if (nacklen == 0) 534 | return 0; 535 | 536 | sleep(1); 537 | } 538 | 539 | errno = ETIMEDOUT; 540 | return -1; 541 | } 542 | 543 | static enum at_response_type scanner_cipclose(const char *line, size_t len, void *arg) 544 | { 545 | (void) len; 546 | (void) arg; 547 | 548 | int connid; 549 | char last; 550 | if (sscanf(line, "%d, CLOSE O%c", &connid, &last) == 2 && last == 'K') 551 | return AT_RESPONSE_FINAL_OK; 552 | return AT_RESPONSE_UNKNOWN; 553 | } 554 | 555 | int sim800_socket_close(struct cellular *modem, int connid) 556 | { 557 | at_set_timeout(modem->at, SET_TIMEOUT); 558 | at_set_command_scanner(modem->at, scanner_cipclose); 559 | at_command_simple(modem->at, "AT+CIPCLOSE=%d", connid); 560 | 561 | return 0; 562 | } 563 | 564 | static int sim800_ftp_open(struct cellular *modem, const char *host, uint16_t port, const char *username, const char *password, bool passive) 565 | { 566 | /* Configure server parameters. */ 567 | at_command_simple(modem->at, "AT+FTPCID=1"); 568 | at_command_simple(modem->at, "AT+FTPSERV=\"%s\"", host); 569 | at_command_simple(modem->at, "AT+FTPPORT=%d", port); 570 | at_command_simple(modem->at, "AT+FTPUN=\"%s\"", username); 571 | at_command_simple(modem->at, "AT+FTPPW=\"%s\"", password); 572 | at_command_simple(modem->at, "AT+FTPMODE=%d", (int) passive); 573 | at_command_simple(modem->at, "AT+FTPTYPE=I"); 574 | 575 | return 0; 576 | } 577 | 578 | static int sim800_ftp_get(struct cellular *modem, const char *filename) 579 | { 580 | struct cellular_sim800 *priv = (struct cellular_sim800 *) modem; 581 | 582 | /* Configure filename. */ 583 | at_command_simple(modem->at, "AT+FTPGETPATH=\"/\""); 584 | at_command_simple(modem->at, "AT+FTPGETNAME=\"%s\"", filename); 585 | 586 | /* Try to open the connection. */ 587 | priv->ftpget1_status = -1; 588 | cellular_command_simple_pdp(modem, "AT+FTPGET=1"); 589 | 590 | /* Wait for the operation result. */ 591 | for (int i=0; iftpget1_status == 1) 593 | return 0; 594 | 595 | if (priv->ftpget1_status != -1) { 596 | errno = ECONNABORTED; 597 | return -1; 598 | } 599 | 600 | sleep(1); 601 | } 602 | 603 | errno = ETIMEDOUT; 604 | return -1; 605 | } 606 | 607 | static enum at_response_type scanner_ftpget2(const char *line, size_t len, void *arg) 608 | { 609 | (void) len; 610 | (void) arg; 611 | 612 | int cnflength; 613 | /* TODO: Verify if cnflength is indeed the size of raw payload. */ 614 | if (sscanf(line, "+FTPGET: 2,%d", &cnflength) == 1) 615 | return AT_RESPONSE_RAWDATA_FOLLOWS(cnflength); 616 | return AT_RESPONSE_UNKNOWN; 617 | } 618 | 619 | static int sim800_ftp_getdata(struct cellular *modem, char *buffer, size_t length) 620 | { 621 | struct cellular_sim800 *priv = (struct cellular_sim800 *) modem; 622 | 623 | int retries = 0; 624 | retry: 625 | at_set_timeout(modem->at, SET_TIMEOUT); 626 | at_set_command_scanner(modem->at, scanner_ftpget2); 627 | const char *response = at_command(modem->at, "AT+FTPGET=2,%zu", length); 628 | 629 | if (response == NULL) 630 | return -1; 631 | 632 | int cnflength; 633 | if (sscanf(response, "+FTPGET: 2,%d", &cnflength) == 1) { 634 | /* Zero means no data is available. Wait for it. */ 635 | if (cnflength == 0) { 636 | /* Bail out on timeout. */ 637 | if (++retries >= SIM800_FTP_TIMEOUT) { 638 | errno = ETIMEDOUT; 639 | return -1; 640 | } 641 | sleep(1); 642 | goto retry; 643 | } 644 | 645 | /* Locate the payload. */ 646 | const char *data = strchr(response, '\n'); 647 | if (data == NULL) { 648 | errno = EPROTO; 649 | return -1; 650 | } 651 | data += 1; 652 | 653 | /* Copy payload to result buffer. */ 654 | memcpy((char *)buffer, data, cnflength); 655 | return cnflength; 656 | } else if (priv->ftpget1_status == 0) { 657 | /* Transfer finished. */ 658 | return 0; 659 | } else { 660 | errno = EPROTO; 661 | return -1; 662 | } 663 | } 664 | 665 | static int sim800_ftp_close(struct cellular *modem) 666 | { 667 | /* Requires fairly recent SIM800 firmware. */ 668 | at_command_simple(modem->at, "AT+FTPQUIT"); 669 | 670 | return 0; 671 | } 672 | 673 | static const struct cellular_ops sim800_ops = { 674 | .attach = sim800_attach, 675 | .detach = sim800_detach, 676 | 677 | .pdp_open = sim800_pdp_open, 678 | .pdp_close = sim800_pdp_close, 679 | 680 | .imei = cellular_op_imei, 681 | .iccid = cellular_op_iccid, 682 | .creg = cellular_op_creg, 683 | .rssi = cellular_op_rssi, 684 | .clock_gettime = sim800_clock_gettime, 685 | .clock_settime = sim800_clock_settime, 686 | .clock_ntptime = sim800_clock_ntptime, 687 | .socket_connect = sim800_socket_connect, 688 | .socket_send = sim800_socket_send, 689 | .socket_recv = sim800_socket_recv, 690 | .socket_waitack = sim800_socket_waitack, 691 | .socket_close = sim800_socket_close, 692 | .ftp_open = sim800_ftp_open, 693 | .ftp_get = sim800_ftp_get, 694 | .ftp_getdata = sim800_ftp_getdata, 695 | .ftp_close = sim800_ftp_close, 696 | }; 697 | 698 | struct cellular *cellular_sim800_alloc(void) 699 | { 700 | struct cellular_sim800 *modem = malloc(sizeof(struct cellular_sim800)); 701 | if (modem == NULL) { 702 | errno = ENOMEM; 703 | return NULL; 704 | } 705 | memset(modem, 0, sizeof(*modem)); 706 | 707 | modem->dev.ops = &sim800_ops; 708 | 709 | return (struct cellular *) modem; 710 | } 711 | 712 | void cellular_sim800_free(struct cellular *modem) 713 | { 714 | free(modem); 715 | } 716 | 717 | /* vim: set ts=4 sw=4 et: */ 718 | -------------------------------------------------------------------------------- /src/modem/telit2.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2014 Kosma Moczek 3 | * This program is free software. It comes without any warranty, to the extent 4 | * permitted by applicable law. You can redistribute it and/or modify it under 5 | * the terms of the Do What The Fuck You Want To Public License, Version 2, as 6 | * published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "common.h" 16 | 17 | 18 | #define TELIT2_WAITACK_TIMEOUT 60 19 | #define TELIT2_FTP_TIMEOUT 60 20 | #define TELIT2_LOCATE_TIMEOUT 150 21 | 22 | static const char *const telit2_urc_responses[] = { 23 | "SRING: ", 24 | "#AGPSRING: ", 25 | NULL 26 | }; 27 | 28 | struct cellular_telit2 { 29 | struct cellular dev; 30 | 31 | int locate_status; 32 | float latitude, longitude, altitude; 33 | }; 34 | 35 | static enum at_response_type scan_line(const char *line, size_t len, void *arg) 36 | { 37 | (void) line; 38 | (void) len; 39 | 40 | struct cellular_telit2 *priv = arg; 41 | (void) priv; 42 | 43 | if (at_prefix_in_table(line, telit2_urc_responses)) 44 | return AT_RESPONSE_URC; 45 | 46 | return AT_RESPONSE_UNKNOWN; 47 | } 48 | 49 | static void handle_urc(const char *line, size_t len, void *arg) 50 | { 51 | struct cellular_telit2 *priv = arg; 52 | 53 | int status; 54 | if (sscanf(line, "#AGPSRING: %d", &status) == 1) { 55 | priv->locate_status = status; 56 | sscanf(line, "#AGPSRING: %*d,%f,%f,%f", &priv->latitude, &priv->longitude, &priv->altitude); 57 | return; 58 | } 59 | 60 | printf("[telit2@%p] urc: %.*s\n", priv, (int) len, line); 61 | } 62 | 63 | static const struct at_callbacks telit2_callbacks = { 64 | .scan_line = scan_line, 65 | .handle_urc = handle_urc, 66 | }; 67 | 68 | static int telit2_attach(struct cellular *modem) 69 | { 70 | at_set_callbacks(modem->at, &telit2_callbacks, (void *) modem); 71 | 72 | at_set_timeout(modem->at, 1); 73 | at_command(modem->at, "AT"); /* Aid autobauding. Always a good idea. */ 74 | at_command(modem->at, "ATE0"); /* Disable local echo. */ 75 | 76 | /* Initialize modem. */ 77 | static const char *const init_strings[] = { 78 | "AT&K0", /* Disable hardware flow control. */ 79 | "AT#SELINT=2", /* Set Telit module compatibility level. */ 80 | "AT+CMEE=2", /* Enable extended error reporting. */ 81 | NULL 82 | }; 83 | for (const char *const *command=init_strings; *command; command++) 84 | at_command_simple(modem->at, "%s", *command); 85 | 86 | return 0; 87 | } 88 | 89 | static int telit2_detach(struct cellular *modem) 90 | { 91 | at_set_callbacks(modem->at, NULL, NULL); 92 | return 0; 93 | } 94 | 95 | static int telit2_pdp_open(struct cellular *modem, const char *apn) 96 | { 97 | at_set_timeout(modem->at, 5); 98 | at_command_simple(modem->at, "AT+CGDCONT=1,IP,\"%s\"", apn); 99 | 100 | at_set_timeout(modem->at, 150); 101 | const char *response = at_command(modem->at, "AT#SGACT=1,1"); 102 | 103 | if (response == NULL) 104 | return -1; 105 | 106 | if (!strcmp(response, "+CME ERROR: context already activated")) 107 | return 0; 108 | 109 | int ip[4]; 110 | at_simple_scanf(response, "#SGACT: %d.%d.%d.%d", &ip[0], &ip[1], &ip[2], &ip[3]); 111 | 112 | return 0; 113 | } 114 | 115 | static int telit2_pdp_close(struct cellular *modem) 116 | { 117 | at_set_timeout(modem->at, 150); 118 | at_command_simple(modem->at, "AT#SGACT=1,0"); 119 | 120 | return 0; 121 | } 122 | 123 | static int telit2_op_iccid(struct cellular *modem, char *buf, size_t len) 124 | { 125 | char fmt[24]; 126 | if (snprintf(fmt, sizeof(fmt), "#CCID: %%[0-9]%ds", (int) len) >= (int) sizeof(fmt)) { 127 | errno = ENOSPC; 128 | return -1; 129 | } 130 | 131 | at_set_timeout(modem->at, 5); 132 | const char *response = at_command(modem->at, "AT#CCID"); 133 | at_simple_scanf(response, fmt, buf); 134 | buf[len-1] = '\0'; 135 | 136 | return 0; 137 | } 138 | 139 | static int telit2_op_clock_gettime(struct cellular *modem, struct timespec *ts) 140 | { 141 | struct tm tm; 142 | int offset; 143 | 144 | at_set_timeout(modem->at, 1); 145 | const char *response = at_command(modem->at, "AT+CCLK?"); 146 | memset(&tm, 0, sizeof(struct tm)); 147 | at_simple_scanf(response, "+CCLK: \"%d/%d/%d,%d:%d:%d%d\"", 148 | &tm.tm_year, &tm.tm_mon, &tm.tm_mday, 149 | &tm.tm_hour, &tm.tm_min, &tm.tm_sec, 150 | &offset); 151 | 152 | /* Most modems report some starting date way in the past when they have 153 | * no date/time estimation. */ 154 | if (tm.tm_year < 14) { 155 | errno = EINVAL; 156 | return 1; 157 | } 158 | 159 | /* Adjust values and perform conversion. */ 160 | tm.tm_year += 2000 - 1900; 161 | tm.tm_mon -= 1; 162 | time_t unix_time = timegm(&tm); 163 | if (unix_time == -1) { 164 | errno = EINVAL; 165 | return -1; 166 | } 167 | 168 | /* Telit modems return local date/time instead of UTC (as defined in 3GPP 169 | * 27.007). Remove the timezone shift. */ 170 | unix_time -= 15*60*offset; 171 | 172 | /* All good. Return the result. */ 173 | ts->tv_sec = unix_time; 174 | ts->tv_nsec = 0; 175 | return 0; 176 | } 177 | 178 | static int telit2_socket_connect(struct cellular *modem, int connid, const char *host, uint16_t port) 179 | { 180 | /* Reset socket configuration to default. */ 181 | at_set_timeout(modem->at, 5); 182 | at_command_simple(modem->at, "AT#SCFGEXT=%d,0,0,0,0,0", connid); 183 | at_command_simple(modem->at, "AT#SCFGEXT2=%d,0,0,0,0,0", connid); 184 | 185 | /* Open connection. */ 186 | cellular_command_simple_pdp(modem, "AT#SD=%d,0,%d,%s,0,0,1", connid, port, host); 187 | 188 | return 0; 189 | } 190 | 191 | static ssize_t telit2_socket_send(struct cellular *modem, int connid, const void *buffer, size_t amount, int flags) 192 | { 193 | (void) flags; 194 | 195 | /* Request transmission. */ 196 | at_set_timeout(modem->at, 150); 197 | at_expect_dataprompt(modem->at); 198 | at_command_simple(modem->at, "AT#SSENDEXT=%d,%zu", connid, amount); 199 | 200 | /* Send raw data. */ 201 | at_command_raw_simple(modem->at, buffer, amount); 202 | 203 | return amount; 204 | } 205 | 206 | static enum at_response_type scanner_srecv(const char *line, size_t len, void *arg) 207 | { 208 | (void) len; 209 | (void) arg; 210 | 211 | int chunk; 212 | if (sscanf(line, "#SRECV: %*d,%d", &chunk) == 1) 213 | return AT_RESPONSE_RAWDATA_FOLLOWS(chunk); 214 | 215 | return AT_RESPONSE_UNKNOWN; 216 | } 217 | 218 | static ssize_t telit2_socket_recv(struct cellular *modem, int connid, void *buffer, size_t length, int flags) 219 | { 220 | (void) flags; 221 | 222 | int cnt = 0; 223 | while (cnt < (int) length) { 224 | int chunk = (int) length - cnt; 225 | /* Limit read size to avoid overflowing AT response buffer. */ 226 | if (chunk > 128) 227 | chunk = 128; 228 | 229 | /* Perform the read. */ 230 | at_set_timeout(modem->at, 150); 231 | at_set_command_scanner(modem->at, scanner_srecv); 232 | const char *response = at_command(modem->at, "AT#SRECV=%d,%d", connid, chunk); 233 | if (response == NULL) 234 | return -1; 235 | 236 | /* Find the header line. */ 237 | int bytes; 238 | at_simple_scanf(response, "#SRECV: %*d,%d", &bytes); 239 | 240 | /* Bail out if we're out of data. Message is misleading. */ 241 | /* FIXME: We should maybe block until we receive something? */ 242 | if (!strcmp(response, "+CME ERROR: activation failed")) 243 | break; 244 | 245 | /* Locate the payload. */ 246 | const char *data = strchr(response, '\n'); 247 | if (data == NULL) { 248 | errno = EPROTO; 249 | return -1; 250 | } 251 | data += 1; 252 | 253 | /* Copy payload to result buffer. */ 254 | memcpy((char *)buffer + cnt, data, bytes); 255 | cnt += bytes; 256 | } 257 | 258 | return cnt; 259 | } 260 | 261 | static int telit2_socket_waitack(struct cellular *modem, int connid) 262 | { 263 | const char *response; 264 | 265 | at_set_timeout(modem->at, 5); 266 | for (int i=0; iat, "AT#SI=%d", connid); 270 | at_simple_scanf(response, "#SI: %*d,%*d,%*d,%*d,%d", &ack_waiting); 271 | 272 | /* ack_waiting is meaningless if socket is not connected. Check this. */ 273 | int socket_status; 274 | response = at_command(modem->at, "AT#SS=%d", connid); 275 | at_simple_scanf(response, "#SS: %*d,%d", &socket_status); 276 | if (socket_status == 0) { 277 | errno = ECONNRESET; 278 | return -1; 279 | } 280 | 281 | /* Return if all bytes were acknowledged. */ 282 | if (ack_waiting == 0) 283 | return 0; 284 | 285 | sleep(1); 286 | } 287 | 288 | errno = ETIMEDOUT; 289 | return -1; 290 | } 291 | 292 | static int telit2_socket_close(struct cellular *modem, int connid) 293 | { 294 | at_set_timeout(modem->at, 150); 295 | at_command_simple(modem->at, "AT#SH=%d", connid); 296 | 297 | return 0; 298 | } 299 | 300 | static int telit2_ftp_open(struct cellular *modem, const char *host, uint16_t port, const char *username, const char *password, bool passive) 301 | { 302 | cellular_command_simple_pdp(modem, "AT#FTPOPEN=%s:%d,%s,%s,%d", host, port, username, password, passive); 303 | 304 | return 0; 305 | } 306 | 307 | static int telit2_ftp_get(struct cellular *modem, const char *filename) 308 | { 309 | at_set_timeout(modem->at, 90); 310 | at_command_simple(modem->at, "AT#FTPGETPKT=\"%s\",0", filename); 311 | 312 | return 0; 313 | } 314 | 315 | static enum at_response_type scanner_ftprecv(const char *line, size_t len, void *arg) 316 | { 317 | (void) len; 318 | (void) arg; 319 | 320 | int bytes; 321 | if (sscanf(line, "#FTPRECV: %d", &bytes) == 1) 322 | return AT_RESPONSE_RAWDATA_FOLLOWS(bytes); 323 | return AT_RESPONSE_UNKNOWN; 324 | } 325 | 326 | static int telit2_ftp_getdata(struct cellular *modem, char *buffer, size_t length) 327 | { 328 | /* FIXME: This function's flow is really ugly. */ 329 | int retries = 0; 330 | retry: 331 | at_set_timeout(modem->at, 150); 332 | at_set_command_scanner(modem->at, scanner_ftprecv); 333 | const char *response = at_command(modem->at, "AT#FTPRECV=%zu", length); 334 | 335 | if (response == NULL) 336 | return -1; 337 | 338 | int bytes; 339 | if (sscanf(response, "#FTPRECV: %d", &bytes) == 1) { 340 | /* Zero means no data is available. Wait for it. */ 341 | if (bytes == 0) { 342 | /* Bail out on timeout. */ 343 | if (++retries >= TELIT2_FTP_TIMEOUT) { 344 | errno = ETIMEDOUT; 345 | return -1; 346 | } 347 | sleep(1); 348 | goto retry; 349 | } 350 | 351 | /* Locate the payload. */ 352 | const char *data = strchr(response, '\n'); 353 | if (data == NULL) { 354 | errno = EPROTO; 355 | return -1; 356 | } 357 | data += 1; 358 | 359 | /* Copy payload to result buffer. */ 360 | memcpy(buffer, data, bytes); 361 | return bytes; 362 | } 363 | 364 | /* Error or EOF? */ 365 | int eof; 366 | response = at_command(modem->at, "AT#FTPGETPKT?"); 367 | /* Expected response: #FTPGETPKT: ,, */ 368 | #if 0 369 | /* The %[] specifier is not supported on some embedded systems. */ 370 | at_simple_scanf(response, "#FTPGETPKT: %*[^,],%*d,%d", &eof); 371 | #else 372 | /* Parse manually. */ 373 | if (response == NULL) 374 | return -1; 375 | errno = EPROTO; 376 | /* Check the initial part of the response. */ 377 | if (strncmp(response, "#FTPGETPKT: ", 12)) 378 | return -1; 379 | /* Skip the filename. */ 380 | response = strchr(response, ','); 381 | if (response == NULL) 382 | return -1; 383 | response++; 384 | at_simple_scanf(response, "%*d,%d", &eof); 385 | #endif 386 | 387 | if (eof == 1) 388 | return 0; 389 | 390 | return -1; 391 | } 392 | 393 | static int telit2_locate(struct cellular *modem, float *latitude, float *longitude, float *altitude) 394 | { 395 | struct cellular_telit2 *priv = (struct cellular_telit2 *) modem; 396 | 397 | priv->locate_status = -1; 398 | at_set_timeout(modem->at, 150); 399 | cellular_command_simple_pdp(modem, "AT#AGPSSND"); 400 | 401 | for (int i=0; ilocate_status == 200) { 404 | *latitude = priv->latitude; 405 | *longitude = priv->longitude; 406 | *altitude = priv->altitude; 407 | return 0; 408 | } 409 | if (priv->locate_status != -1) { 410 | errno = ECONNABORTED; 411 | return -1; 412 | } 413 | } 414 | 415 | errno = ETIMEDOUT; 416 | return -1; 417 | } 418 | 419 | static int telit2_ftp_close(struct cellular *modem) 420 | { 421 | at_set_timeout(modem->at, 90); 422 | at_command_simple(modem->at, "AT#FTPCLOSE"); 423 | 424 | return 0; 425 | } 426 | 427 | static const struct cellular_ops telit2_ops = { 428 | .attach = telit2_attach, 429 | .detach = telit2_detach, 430 | 431 | .pdp_open = telit2_pdp_open, 432 | .pdp_close = telit2_pdp_close, 433 | 434 | .imei = cellular_op_imei, 435 | .iccid = telit2_op_iccid, 436 | .creg = cellular_op_creg, 437 | .rssi = cellular_op_rssi, 438 | .clock_gettime = telit2_op_clock_gettime, 439 | .clock_settime = cellular_op_clock_settime, 440 | .socket_connect = telit2_socket_connect, 441 | .socket_send = telit2_socket_send, 442 | .socket_recv = telit2_socket_recv, 443 | .socket_waitack = telit2_socket_waitack, 444 | .socket_close = telit2_socket_close, 445 | .ftp_open = telit2_ftp_open, 446 | .ftp_get = telit2_ftp_get, 447 | .ftp_getdata = telit2_ftp_getdata, 448 | .ftp_close = telit2_ftp_close, 449 | .locate = telit2_locate, 450 | }; 451 | 452 | struct cellular *cellular_telit2_alloc(void) 453 | { 454 | struct cellular_telit2 *modem = malloc(sizeof(struct cellular_telit2)); 455 | if (modem == NULL) { 456 | errno = ENOMEM; 457 | return NULL; 458 | } 459 | memset(modem, 0, sizeof(*modem)); 460 | 461 | modem->dev.ops = &telit2_ops; 462 | 463 | return (struct cellular *) modem; 464 | } 465 | 466 | void cellular_telit2_free(struct cellular *modem) 467 | { 468 | free(modem); 469 | } 470 | 471 | /* vim: set ts=4 sw=4 et: */ 472 | -------------------------------------------------------------------------------- /src/parser.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2014 Kosma Moczek 3 | * This program is free software. It comes without any warranty, to the extent 4 | * permitted by applicable law. You can redistribute it and/or modify it under 5 | * the terms of the Do What The Fuck You Want To Public License, Version 2, as 6 | * published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | enum at_parser_state { 15 | STATE_IDLE, 16 | STATE_READLINE, 17 | STATE_DATAPROMPT, 18 | STATE_RAWDATA, 19 | STATE_HEXDATA, 20 | }; 21 | 22 | struct at_parser { 23 | const struct at_parser_callbacks *cbs; 24 | void *priv; 25 | 26 | enum at_parser_state state; 27 | bool expect_dataprompt; 28 | size_t data_left; 29 | int nibble; 30 | 31 | char *buf; 32 | size_t buf_used; 33 | size_t buf_size; 34 | size_t buf_current; 35 | }; 36 | 37 | static const char *const final_ok_responses[] = { 38 | "OK", 39 | NULL 40 | }; 41 | 42 | static const char *const final_responses[] = { 43 | "OK", 44 | "ERROR", 45 | "NO CARRIER", 46 | "+CME ERROR:", 47 | "+CMS ERROR:", 48 | NULL 49 | }; 50 | 51 | static const char *const urc_responses[] = { 52 | "RING", 53 | NULL 54 | }; 55 | 56 | struct at_parser *at_parser_alloc(const struct at_parser_callbacks *cbs, size_t bufsize, void *priv) 57 | { 58 | /* Allocate parser struct. */ 59 | struct at_parser *parser = (struct at_parser *) malloc(sizeof(struct at_parser)); 60 | if (parser == NULL) { 61 | errno = ENOMEM; 62 | return NULL; 63 | } 64 | 65 | /* Allocate response buffer. */ 66 | parser->buf = malloc(bufsize); 67 | if (parser->buf == NULL) { 68 | free(parser); 69 | errno = ENOMEM; 70 | return NULL; 71 | } 72 | parser->cbs = cbs; 73 | parser->buf_size = bufsize; 74 | parser->priv = priv; 75 | 76 | /* Prepare instance. */ 77 | at_parser_reset(parser); 78 | 79 | return parser; 80 | } 81 | 82 | void at_parser_reset(struct at_parser *parser) 83 | { 84 | parser->state = STATE_IDLE; 85 | parser->expect_dataprompt = false; 86 | parser->buf_used = 0; 87 | parser->buf_current = 0; 88 | parser->data_left = 0; 89 | } 90 | 91 | void at_parser_expect_dataprompt(struct at_parser *parser) 92 | { 93 | parser->expect_dataprompt = true; 94 | } 95 | 96 | void at_parser_await_response(struct at_parser *parser) 97 | { 98 | parser->state = (parser->expect_dataprompt ? STATE_DATAPROMPT : STATE_READLINE); 99 | } 100 | 101 | bool at_prefix_in_table(const char *line, const char *const table[]) 102 | { 103 | for (int i=0; table[i] != NULL; i++) 104 | if (!strncmp(line, table[i], strlen(table[i]))) 105 | return true; 106 | 107 | return false; 108 | } 109 | 110 | static enum at_response_type generic_line_scanner(const char *line, size_t len, struct at_parser *parser) 111 | { 112 | (void) len; 113 | 114 | if (parser->state == STATE_DATAPROMPT) 115 | if (len == 2 && !memcmp(line, "> ", 2)) 116 | return AT_RESPONSE_FINAL_OK; 117 | 118 | if (at_prefix_in_table(line, urc_responses)) 119 | return AT_RESPONSE_URC; 120 | else if (at_prefix_in_table(line, final_ok_responses)) 121 | return AT_RESPONSE_FINAL_OK; 122 | else if (at_prefix_in_table(line, final_responses)) 123 | return AT_RESPONSE_FINAL; 124 | else 125 | return AT_RESPONSE_INTERMEDIATE; 126 | } 127 | 128 | static void parser_append(struct at_parser *parser, char ch) 129 | { 130 | if (parser->buf_used < parser->buf_size-1) 131 | parser->buf[parser->buf_used++] = ch; 132 | } 133 | 134 | static void parser_include_line(struct at_parser *parser) 135 | { 136 | /* Append a newline. */ 137 | parser_append(parser, '\n'); 138 | 139 | /* Advance the current command pointer to the new position. */ 140 | parser->buf_current = parser->buf_used; 141 | } 142 | 143 | static void parser_discard_line(struct at_parser *parser) 144 | { 145 | /* Rewind the end pointer back to the previous position. */ 146 | parser->buf_used = parser->buf_current; 147 | } 148 | 149 | static void parser_finalize(struct at_parser *parser) 150 | { 151 | /* Remove the last newline, if any. */ 152 | if (parser->buf_used > 0) 153 | parser->buf_used--; 154 | 155 | /* NULL-terminate the response. */ 156 | parser->buf[parser->buf_used] = '\0'; 157 | } 158 | 159 | /** 160 | * Helper, called whenever a full response line is collected. 161 | */ 162 | static void parser_handle_line(struct at_parser *parser) 163 | { 164 | /* Skip empty lines. */ 165 | if (parser->buf_used == parser->buf_current) 166 | return; 167 | 168 | /* NULL-terminate the response .*/ 169 | parser->buf[parser->buf_used] = '\0'; 170 | 171 | /* Extract line address & length for later use. */ 172 | const char *line = parser->buf + parser->buf_current; 173 | size_t len = parser->buf_used - parser->buf_current; 174 | 175 | /* Log the received line. */ 176 | printf("< '%.*s'\n", (int) len, line); 177 | 178 | /* Determine response type. */ 179 | enum at_response_type type = AT_RESPONSE_UNKNOWN; 180 | if (parser->cbs->scan_line) 181 | type = parser->cbs->scan_line(line, len, parser->priv); 182 | if (!type) 183 | type = generic_line_scanner(line, len, parser); 184 | 185 | /* Expected URCs and all unexpected lines are sent to URC handler. */ 186 | if (type == AT_RESPONSE_URC || parser->state == STATE_IDLE) 187 | { 188 | /* Fire the callback on the URC line. */ 189 | parser->cbs->handle_urc(parser->buf + parser->buf_current, 190 | parser->buf_used - parser->buf_current, 191 | parser->priv); 192 | 193 | /* Discard the URC line from the buffer. */ 194 | parser_discard_line(parser); 195 | 196 | return; 197 | } 198 | 199 | /* Accumulate everything that's not a final OK. */ 200 | if (type != AT_RESPONSE_FINAL_OK) { 201 | /* Include the line in the buffer. */ 202 | parser_include_line(parser); 203 | } else { 204 | /* Discard the line from the buffer. */ 205 | parser_discard_line(parser); 206 | } 207 | 208 | /* Act on the response type. */ 209 | switch (type & _AT_RESPONSE_TYPE_MASK) { 210 | case AT_RESPONSE_FINAL_OK: 211 | case AT_RESPONSE_FINAL: 212 | { 213 | /* Fire the response callback. */ 214 | parser_finalize(parser); 215 | parser->cbs->handle_response(parser->buf, parser->buf_used, parser->priv); 216 | 217 | /* Go back to idle state. */ 218 | at_parser_reset(parser); 219 | } 220 | break; 221 | 222 | case _AT_RESPONSE_RAWDATA_FOLLOWS: 223 | { 224 | /* Switch parser state to rawdata mode. */ 225 | parser->data_left = (int)type >> 8; 226 | parser->state = STATE_RAWDATA; 227 | } 228 | break; 229 | 230 | case _AT_RESPONSE_HEXDATA_FOLLOWS: 231 | { 232 | /* Switch parser state to hexdata mode. */ 233 | parser->data_left = (int)type >> 8; 234 | parser->nibble = -1; 235 | parser->state = STATE_HEXDATA; 236 | } 237 | break; 238 | 239 | default: 240 | { 241 | /* Keep calm and carry on. */ 242 | } 243 | break; 244 | } 245 | } 246 | 247 | static int hex2int(char c) 248 | { 249 | if (c >= '0' && c <= '9') 250 | return c - '0'; 251 | if (c >= 'A' && c <= 'F') 252 | return c - 'A' + 10; 253 | if (c >= 'a' && c <= 'f') 254 | return c - 'a' + 10; 255 | return -1; 256 | } 257 | 258 | void at_parser_feed(struct at_parser *parser, const void *data, size_t len) 259 | { 260 | const uint8_t *buf = data; 261 | 262 | while (len > 0) 263 | { 264 | /* Fetch next character. */ 265 | uint8_t ch = *buf++; len--; 266 | 267 | switch (parser->state) 268 | { 269 | case STATE_IDLE: 270 | case STATE_READLINE: 271 | case STATE_DATAPROMPT: 272 | { 273 | if ((ch != '\r') && (ch != '\n')) { 274 | /* Append the character if it's not a newline. */ 275 | parser_append(parser, ch); 276 | } 277 | 278 | /* Handle full lines. */ 279 | if ((ch == '\n') || 280 | (parser->state == STATE_DATAPROMPT && 281 | parser->buf_used == 2 && 282 | !memcmp(parser->buf, "> ", 2))) 283 | { 284 | parser_handle_line(parser); 285 | } 286 | } 287 | break; 288 | 289 | case STATE_RAWDATA: { 290 | if (parser->data_left > 0) { 291 | parser_append(parser, ch); 292 | parser->data_left--; 293 | } 294 | 295 | if (parser->data_left == 0) { 296 | parser_include_line(parser); 297 | parser->state = STATE_READLINE; 298 | } 299 | } break; 300 | 301 | case STATE_HEXDATA: { 302 | if (parser->data_left > 0) { 303 | int value = hex2int(ch); 304 | if (value != -1) { 305 | if (parser->nibble == -1) { 306 | parser->nibble = value; 307 | } else { 308 | value |= (parser->nibble << 4); 309 | parser->nibble = -1; 310 | parser_append(parser, value); 311 | parser->data_left--; 312 | } 313 | } 314 | } 315 | 316 | if (parser->data_left == 0) { 317 | parser_include_line(parser); 318 | parser->state = STATE_READLINE; 319 | } 320 | } break; 321 | } 322 | } 323 | } 324 | 325 | void at_parser_free(struct at_parser *parser) 326 | { 327 | free(parser->buf); 328 | free(parser); 329 | } 330 | 331 | /* vim: set ts=4 sw=4 et: */ 332 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | test-parser 2 | -------------------------------------------------------------------------------- /tests/test-parser.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2014 Kosma Moczek 3 | * This program is free software. It comes without any warranty, to the extent 4 | * permitted by applicable law. You can redistribute it and/or modify it under 5 | * the terms of the Do What The Fuck You Want To Public License, Version 2, as 6 | * published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | 20 | #define STR_LEN(s) s, strlen(s) 21 | 22 | 23 | GQueue expected_responses = G_QUEUE_INIT; 24 | GQueue expected_urcs = G_QUEUE_INIT; 25 | 26 | void assert_line_expected(const char *line, size_t len, GQueue *q) 27 | { 28 | const char *expected = g_queue_pop_head(q); 29 | ck_assert_msg(expected != NULL); 30 | ck_assert_str_eq(line, expected); 31 | ck_assert_int_eq(len, strlen(expected)); 32 | } 33 | 34 | void handle_response(const char *line, size_t len, void *priv) 35 | { 36 | (void) priv; 37 | //printf("response: >>>%.*s<<< (%d)\n", (int) len, (char *) line, (int) len); 38 | assert_line_expected(line, len, &expected_responses); 39 | } 40 | 41 | void handle_urc(const char *line, size_t len, void *priv) 42 | { 43 | (void) priv; 44 | //printf("urc: >>>%.*s<<< (%d)\n", (int) len, (char *) line, (int) len); 45 | assert_line_expected(line, len, &expected_urcs); 46 | } 47 | 48 | void expect_response(const char *line) 49 | { 50 | //printf("expecting response: '%s'\n", line); 51 | g_queue_push_tail(&expected_responses, (gpointer) line); 52 | } 53 | 54 | void expect_urc(const char *line) 55 | { 56 | //printf("expecting urc: '%s'\n", line); 57 | g_queue_push_tail(&expected_urcs, (gpointer) line); 58 | } 59 | 60 | void expect_prepare(void) 61 | { 62 | g_queue_clear(&expected_responses); 63 | g_queue_clear(&expected_urcs); 64 | } 65 | 66 | void expect_nothing(void) 67 | { 68 | ck_assert(g_queue_is_empty(&expected_responses)); 69 | ck_assert(g_queue_is_empty(&expected_urcs)); 70 | } 71 | 72 | START_TEST(test_parser_alloc) 73 | { 74 | printf(":: test_parser_alloc\n"); 75 | 76 | struct at_parser_callbacks cbs = { 77 | .handle_response = handle_response, 78 | .handle_urc = handle_urc, 79 | }; 80 | struct at_parser *parser = at_parser_alloc(&cbs, 256, NULL); 81 | ck_assert(parser != NULL); 82 | at_parser_free(parser); 83 | } 84 | END_TEST 85 | 86 | START_TEST(test_parser_response) 87 | { 88 | printf(":: test_parser_response\n"); 89 | 90 | struct at_parser_callbacks cbs = { 91 | .handle_response = handle_response, 92 | .handle_urc = handle_urc, 93 | }; 94 | struct at_parser *parser = at_parser_alloc(&cbs, 256, NULL); 95 | ck_assert(parser != NULL); 96 | 97 | expect_prepare(); 98 | 99 | expect_response("ERROR"); 100 | at_parser_await_response(parser); 101 | at_parser_feed(parser, STR_LEN("ERROR\r\n")); 102 | expect_nothing(); 103 | 104 | at_parser_await_response(parser); 105 | at_parser_feed(parser, STR_LEN("\r\n\r\n\r\n\r\n\r\n")); 106 | expect_nothing(); 107 | expect_response("ERROR"); 108 | at_parser_feed(parser, STR_LEN("ERROR\r\n")); 109 | expect_nothing(); 110 | 111 | expect_response(""); 112 | at_parser_await_response(parser); 113 | at_parser_feed(parser, STR_LEN("OK\r\n")); 114 | expect_nothing(); 115 | 116 | expect_response("123456789"); 117 | at_parser_await_response(parser); 118 | at_parser_feed(parser, STR_LEN("123456789\r\nOK\r\n")); 119 | expect_nothing(); 120 | 121 | expect_response("123456789\nERROR"); 122 | at_parser_await_response(parser); 123 | at_parser_feed(parser, STR_LEN("123456789\r\nERROR\r\n")); 124 | expect_nothing(); 125 | 126 | at_parser_free(parser); 127 | } 128 | END_TEST 129 | 130 | START_TEST(test_parser_urc) 131 | { 132 | printf(":: test_parser_urc\n"); 133 | 134 | struct at_parser_callbacks cbs = { 135 | .handle_response = handle_response, 136 | .handle_urc = handle_urc, 137 | }; 138 | struct at_parser *parser = at_parser_alloc(&cbs, 256, NULL); 139 | ck_assert(parser != NULL); 140 | 141 | expect_prepare(); 142 | 143 | expect_urc("RING"); 144 | at_parser_feed(parser, STR_LEN("RING\r\n")); 145 | expect_nothing(); 146 | 147 | expect_urc("+HERP"); 148 | expect_urc("+DERP"); 149 | expect_urc("+DERP"); 150 | at_parser_feed(parser, STR_LEN("+HER")); 151 | at_parser_feed(parser, STR_LEN("P\r\n+DERP\r\n+DERP")); 152 | at_parser_feed(parser, STR_LEN("\r\n")); 153 | expect_nothing(); 154 | 155 | at_parser_free(parser); 156 | } 157 | END_TEST 158 | 159 | START_TEST(test_parser_mixed) 160 | { 161 | printf(":: test_parser_mixed\n"); 162 | 163 | struct at_parser_callbacks cbs = { 164 | .handle_response = handle_response, 165 | .handle_urc = handle_urc, 166 | }; 167 | struct at_parser *parser = at_parser_alloc(&cbs, 256, NULL); 168 | ck_assert(parser != NULL); 169 | 170 | expect_prepare(); 171 | 172 | expect_response("12345\n67890"); 173 | expect_urc("RING"); 174 | expect_urc("RING"); 175 | expect_urc("RING"); 176 | at_parser_await_response(parser); 177 | at_parser_feed(parser, STR_LEN("\r\n12345\r\nRING\r\n67890\r\nRING\r\nOK\r\n\r\nRING\r\n")); 178 | expect_nothing(); 179 | 180 | at_parser_free(parser); 181 | } 182 | END_TEST 183 | 184 | START_TEST(test_parser_overflow) 185 | { 186 | printf(":: test_parser_overflow\n"); 187 | 188 | struct at_parser_callbacks cbs = { 189 | .handle_response = handle_response, 190 | .handle_urc = handle_urc, 191 | }; 192 | struct at_parser *parser = at_parser_alloc(&cbs, 8, NULL); 193 | ck_assert(parser != NULL); 194 | 195 | expect_prepare(); 196 | 197 | /* this one fits... */ 198 | expect_response("1234"); 199 | at_parser_await_response(parser); 200 | at_parser_feed(parser, STR_LEN("1234\r\nOK\r\n")); 201 | expect_nothing(); 202 | 203 | /* this one doesn't. */ 204 | /* TODO: We could be better behaved when it comes to overflows. Not crashing is enough for now. */ 205 | at_parser_await_response(parser); 206 | at_parser_feed(parser, STR_LEN("12345\r\nOK\r\n")); 207 | expect_nothing(); 208 | 209 | at_parser_free(parser); 210 | } 211 | END_TEST 212 | 213 | static enum at_response_type line_scanner(const char *line, size_t len, void *priv) 214 | { 215 | (void) len; 216 | (void) priv; 217 | 218 | int bytes; 219 | if (sscanf(line, "+RAWDATA: %d", &bytes) == 1) 220 | return AT_RESPONSE_RAWDATA_FOLLOWS(bytes); 221 | if (sscanf(line, "+HEXDATA: %d", &bytes) == 1) 222 | return AT_RESPONSE_HEXDATA_FOLLOWS(bytes); 223 | 224 | return AT_RESPONSE_UNKNOWN; 225 | } 226 | 227 | START_TEST(test_parser_rawdata) 228 | { 229 | printf(":: test_parser_rawdata\n"); 230 | 231 | struct at_parser_callbacks cbs = { 232 | .handle_response = handle_response, 233 | .handle_urc = handle_urc, 234 | .scan_line = line_scanner, 235 | }; 236 | struct at_parser *parser = at_parser_alloc(&cbs, 256, NULL); 237 | ck_assert(parser != NULL); 238 | 239 | expect_prepare(); 240 | 241 | /* I'd love to put 0x00 here, but the entire string handling in Check croaks then. */ 242 | expect_response("+RAWDATA: 16\nRING\r\nabcd\x01\xffxyzp"); 243 | expect_urc("RING"); 244 | expect_urc("RING"); 245 | expect_urc("RING"); 246 | at_parser_await_response(parser); 247 | at_parser_feed(parser, STR_LEN("\r\nRING\r\n+RAWDATA: 16\r\nRING\r\nabcd\x01\xFFxyzp\r\nRING\r\nOK\r\nRING\r\n")); 248 | expect_nothing(); 249 | 250 | at_parser_free(parser); 251 | } 252 | END_TEST 253 | 254 | START_TEST(test_parser_hexdata) 255 | { 256 | printf(":: test_parser_hexdata\n"); 257 | 258 | struct at_parser_callbacks cbs = { 259 | .handle_response = handle_response, 260 | .handle_urc = handle_urc, 261 | .scan_line = line_scanner, 262 | }; 263 | struct at_parser *parser = at_parser_alloc(&cbs, 256, NULL); 264 | ck_assert(parser != NULL); 265 | 266 | expect_prepare(); 267 | 268 | expect_response("+HEXDATA: 10\nabcd\x01\xffxyzp"); 269 | at_parser_await_response(parser); 270 | at_parser_feed(parser, STR_LEN("\r\n+HEXDATA: 10\r\n61 62 6364 01 ff 78797a70\r\nOK\r\n")); 271 | expect_nothing(); 272 | 273 | at_parser_free(parser); 274 | } 275 | END_TEST 276 | 277 | START_TEST(test_parser_dataprompt) 278 | { 279 | printf(":: test_parser_dataprompt\n"); 280 | 281 | struct at_parser_callbacks cbs = { 282 | .handle_response = handle_response, 283 | .handle_urc = handle_urc, 284 | }; 285 | struct at_parser *parser = at_parser_alloc(&cbs, 256, NULL); 286 | ck_assert(parser != NULL); 287 | 288 | expect_prepare(); 289 | 290 | /* Prompt is an OK when we expect it... */ 291 | at_parser_expect_dataprompt(parser); 292 | at_parser_await_response(parser); 293 | expect_response(""); 294 | at_parser_feed(parser, STR_LEN("\r\n> ")); 295 | expect_nothing(); 296 | 297 | /* ...but a normal response otherwise. */ 298 | at_parser_await_response(parser); 299 | expect_response("> "); 300 | at_parser_feed(parser, STR_LEN("\r\n> \r\nOK\r\n")); 301 | expect_nothing(); 302 | 303 | at_parser_free(parser); 304 | } 305 | END_TEST 306 | 307 | Suite *attentive_suite(void) 308 | { 309 | Suite *s = suite_create("attentive"); 310 | TCase *tc; 311 | 312 | tc = tcase_create("parser"); 313 | tcase_add_test(tc, test_parser_alloc); 314 | tcase_add_test(tc, test_parser_response); 315 | tcase_add_test(tc, test_parser_urc); 316 | tcase_add_test(tc, test_parser_mixed); 317 | tcase_add_test(tc, test_parser_overflow); 318 | tcase_add_test(tc, test_parser_rawdata); 319 | tcase_add_test(tc, test_parser_hexdata); 320 | tcase_add_test(tc, test_parser_dataprompt); 321 | suite_add_tcase(s, tc); 322 | 323 | return s; 324 | } 325 | 326 | int main() 327 | { 328 | int number_failed; 329 | Suite *s = attentive_suite(); 330 | SRunner *sr = srunner_create(s); 331 | srunner_run_all(sr, CK_NORMAL); 332 | number_failed = srunner_ntests_failed(sr); 333 | srunner_free(sr); 334 | return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; 335 | } 336 | 337 | /* vim: set ts=4 sw=4 et: */ 338 | --------------------------------------------------------------------------------