/* Wavecom Q26 gsmd plugin * * (C) 2012-2013 by Harald Welte * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include #include #include #include "gsmd.h" #include #include #include #include #include #include #include #include #define AISGPS_UDP_HOST "127.0.0.1" #define GPS_UDP_PORT 12345 #define AIS_UDP_PORT 12346 static int gps_udp_fd = -1; static int ais_udp_fd = -1; struct cell_info_fsq { uint8_t cur; uint8_t full; uint8_t sub; }; #define TOK_OR_OUT(in) do { tok = strchr(in, ','); if (!tok || *(tok+1) == 0) goto out; tok += 1; } while (0) static int cced_parse(const char *buf, int len, const char *param, struct gsmd *gsmd) { struct gsmd_evt_auxdata *aux; struct gsmd_ucmd *ucmd = usock_build_event(GSMD_MSG_EVENT, GSMD_EVT_CELLINFO, sizeof(*aux)); char *tok = param; int rc; if (!ucmd) return -ENOMEM; aux = (struct gsmd_evt_auxdata *) ucmd->buf; DEBUGP("entering cced_parse param=`%s'\n", param); aux->u.cell_info.mcc = atoi(tok); TOK_OR_OUT(tok); aux->u.cell_info.mnc = atoi(tok); TOK_OR_OUT(tok); sscanf(tok, "%hx", &aux->u.cell_info.lac); TOK_OR_OUT(tok); sscanf(tok, "%hx", &aux->u.cell_info.ci); TOK_OR_OUT(tok); aux->u.cell_info.bsic = atoi(tok); TOK_OR_OUT(tok); aux->u.cell_info.arfcn = atoi(tok); TOK_OR_OUT(tok); aux->u.cell_info.rxlev = atoi(tok); usock_evt_send(gsmd, ucmd, GSMD_EVT_SIGNAL); ucmd = NULL; TOK_OR_OUT(tok); TOK_OR_OUT(tok); TOK_OR_OUT(tok); TOK_OR_OUT(tok); TOK_OR_OUT(tok); TOK_OR_OUT(tok); TOK_OR_OUT(tok); /* iterate over neighbor cell records */ while (tok) { //DEBUGP("remaining: `%s'\n", tok); ucmd = usock_build_event(GSMD_MSG_EVENT, GSMD_EVT_CELLINFO, sizeof(*aux)); if (!ucmd) return -ENOMEM; aux = (struct gsmd_evt_auxdata *) ucmd->buf; aux->u.cell_info.mcc = atoi(tok); TOK_OR_OUT(tok); aux->u.cell_info.mnc = atoi(tok); TOK_OR_OUT(tok); sscanf(tok, "%hx", &aux->u.cell_info.lac); TOK_OR_OUT(tok); sscanf(tok, "%hx", &aux->u.cell_info.ci); TOK_OR_OUT(tok); aux->u.cell_info.bsic = atoi(tok); TOK_OR_OUT(tok); aux->u.cell_info.arfcn = atoi(tok); TOK_OR_OUT(tok); aux->u.cell_info.rxlev = atoi(tok); usock_evt_send(gsmd, ucmd, GSMD_EVT_SIGNAL); ucmd = NULL; TOK_OR_OUT(tok); } return 0; out: talloc_free(ucmd); return 0; } #define AIS_PFX "+SYSMOCOM_AIS: " struct gsmd_ucmd *usock_build_gps(uint8_t subtype, const char *data, uint16_t len) { struct gsmd_evt_auxdata *aux; struct gsmd_ucmd *ucmd; int remain_len; if (len <= strlen(AIS_PFX)) return NULL; remain_len = len - strlen(AIS_PFX); ucmd = usock_build_event(GSMD_MSG_EVENT, subtype, sizeof(*aux)+remain_len+1); if (!ucmd) return NULL; aux = (struct gsmd_evt_auxdata *) ucmd->buf; memcpy(aux->data, data+strlen(AIS_PFX), remain_len); aux->data[remain_len] = '\0'; return ucmd; } const char *crlf = "\r\n"; static int send_crlf(int fd, const char *buf, int len) { struct iovec iov[2]; struct msghdr mh; if (len <= strlen(AIS_PFX)) return 0; len -= strlen(AIS_PFX); buf += strlen(AIS_PFX); iov[0].iov_base = (void *) buf; iov[0].iov_len = len; iov[1].iov_base = (void *) crlf; iov[1].iov_len = strlen(crlf); memset(&mh, 0, sizeof(mh)); mh.msg_iov = iov; mh.msg_iovlen = ARRAY_SIZE(iov); return sendmsg(fd, &mh, MSG_DONTWAIT); } static int sgps_parse(const char *buf, int len, const char *param, struct gsmd *gsmd) { struct gsmd_ucmd *ucmd; send_crlf(gps_udp_fd, buf, len); ucmd = usock_build_gps(GSMD_EVT_GPS, buf, len); if (!ucmd) return -ENOMEM; usock_evt_send(gsmd, ucmd, GSMD_EVT_SIGNAL); return 0; }; static int sais_parse(const char *buf, int len, const char *param, struct gsmd *gsmd) { struct gsmd_ucmd *ucmd; send_crlf(ais_udp_fd, buf, len); ucmd = usock_build_gps(GSMD_EVT_AIS, buf, len); if (!ucmd) return -ENOMEM; usock_evt_send(gsmd, ucmd, GSMD_EVT_SIGNAL); return 0; }; static int cops_parse(const char *buf, int len, const char *param, struct gsmd *gsmd) { struct gsmd_atcmd fake_atcmd; if (!g_use_ATatCOPS) return 0; if (!strcmp(buf, "+COPS: ERROR\r\n")) return 0; if (!g_use_ATatCOPS || !g_last_cops_user) return 0; fake_atcmd.id = g_last_cops_id; network_opers_cb(&fake_atcmd, g_last_cops_user, buf); return 0; }; static const struct gsmd_unsolicit wavecom_unsolicit[] = { { "+CCED", &cced_parse }, /* Cell Environment Report */ { "+SYSMOCOM_GPS", &sgps_parse }, { "+SYSMOCOM_AIS", &sais_parse }, /* proprietary non-blocking response to AT@COPS=? */ { "+COPS", &cops_parse }, }; static int wavecom_detect(struct gsmd *g) { /* FIXME: do actual detection of vendor if we have multiple vendors */ return 1; } static int at_atcops_cb(struct gsmd_atcmd *cmd, void *ctx, char *resp) { /* if the result is successful, at@cops is supported */ if (cmd->ret == 0) g_use_ATatCOPS = 1; return 0; } static int wavecom_initsettings(struct gsmd *g) { int rc = 0; struct gsmd_atcmd *cmd; struct sockaddr_in sin; gps_udp_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); ais_udp_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (gps_udp_fd < 0 || ais_udp_fd < 0) return -1; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; inet_aton(AISGPS_UDP_HOST, &sin.sin_addr); sin.sin_port = htons(GPS_UDP_PORT); rc = connect(gps_udp_fd, (struct sockaddr *)&sin, sizeof(sin)); if (rc < 0) return rc; sin.sin_port = htons(AIS_UDP_PORT); rc = connect(ais_udp_fd, (struct sockaddr *)&sin, sizeof(sin)); if (rc < 0) return rc; /* use +WREGC to enter "spy mode" (no transmit / registration) */ rc |= gsmd_simplecmd(g, "AT+WREGC=0"); rc |= gsmd_simplecmd(g, "AT+CCED=1,3"); cmd = atcmd_fill("AT@COPS=?", 9+1, &at_atcops_cb, NULL, 0, NULL); rc |= atcmd_submit(g, cmd); return rc; } struct gsmd_vendor_plugin gsmd_vendor_plugin = { .name = "Wavecom Q26", .ext_chars = "", .num_unsolicit = ARRAY_SIZE(wavecom_unsolicit), .unsolicit = wavecom_unsolicit, .detect = &wavecom_detect, .initsettings = &wavecom_initsettings, };