#include "air720u.h"
#include "../swapi/subjects/serial/serial.h"

// 模块名称
static const char MODULE_NAME[] = "Air720U";

#define MAX_CMD_CHARS    65535 // 单条指令最大字符数
#define MAX_ACK_CHARS    65535 // 指令应答最大字符数
#define WAIT_ACK_TIMEOUT 9000  // 等待应答超时时间ms
#define R_SILENT_TIMEOUT 50    // 应答静默超时时间ms

// 关闭命令回显模式
static const char *AT_USE_ECHO   = "ATE0"    ;
// 获取模块的IMEI号
static const char *AT_GET_IMEI   = "AT+CGSN" ;
// 查看网络注册状态
static const char *AT_GET_CREG   = "AT+CREG?";
// 获取当前信号强度
static const char *AT_GET_CSQ    = "AT+CSQ"  ;
// 获取SIM-PIN状态
static const char *AT_GET_PINSTA = "AT+CPIN?";
// 获取SIM-ICCID号
static const char *AT_GET_ICCID  = "AT+ICCID";

// AT指令请求和应答
typedef struct
{
  // 指令
  struct
  {
    char data[MAX_CMD_CHARS+1]; // 指令数据(字符串)
    int len; // 指令长度
  } cmd;
  // 等待执行结束的超时ms
  int timeout;
  // 等待执行结束的信号量
  void *hEndSgl;
  // 应答
  struct
  {
    int ret; // 返回值: >0时执行成功, 表明应答数据的长度; <0时出错(-1超时)
    char data[MAX_ACK_CHARS+1]; // 应答数据(字符串)
  } ack;
} SATCmdTrans;

// 与模块的串口通讯
typedef struct
{
  void *h;             // 打开的串口句柄
  void *do_cmd_mutex;  // 指令执行互斥量, 同一时刻正在执行的指令只能有一条
  SATCmdTrans *pTrans; // 执行的指令及其应答结果: 空闲时为空, 工作时不为空
} SAir720UCom;

static SAir720UCom s_myCom;

// 定义AT指令格式符
#define CR   '\r'      // 回车
#define LF   '\n'      // 换行
#define CRLF "\r\n"    // 回车换行
#define ATOK "\r\nOK\r\n" // 应答成功
#define ATER "ERROR"   // 应答失败

// 执行一条指令, 返回: 大于0成功; 小于0失败(-1命令超时)
static int comio_doCmd(SATCmdTrans *pTrans/*要执行的指令(IN&OUT)*/, int timeout/*等待应答的超时时间, 单位是毫秒*/)
{
  SAir720UCom *pComIO = &s_myCom; const char *log_prefix = serial_get_log_prefix(pComIO->h);
  unsigned long stime, wtime, etime; char *pAtCmdStr; int atCmdLen, ret;

  xgettickcount(&stime);
  ret = sw_mutex_lock(pComIO->do_cmd_mutex, timeout);
  if(ret != 0) { pTrans->ack.ret = -1; goto end_p; } // 互斥量上锁超时
  xgettickcount(&etime); wtime = etime - stime;

  pTrans->hEndSgl = sw_signal_create();
  if(!pTrans->hEndSgl) { pTrans->ack.ret = -2; goto end_p; }

  pAtCmdStr = (char *)sw_heap_malloc(pTrans->cmd.len+2);
  if(!pAtCmdStr) { pTrans->ack.ret = -3; goto end_p; }
  memcpy(pAtCmdStr, pTrans->cmd.data, pTrans->cmd.len);
  pAtCmdStr[pTrans->cmd.len] = CR; pAtCmdStr[pTrans->cmd.len+1] = '\0';
  atCmdLen = strlen((const char *)pAtCmdStr);

  serial_recvThrd_pause(pComIO->h); sw_thrd_delay(THRDWAIT_DELAY); // "pComIO->pTrans"的控制权转移前, 先暂停串口数据的接收
  ret = serial_send_data(pComIO->h, (unsigned char *)pAtCmdStr, atCmdLen); // 发送一条指令
  sw_heap_free(pAtCmdStr); // 释放AT指令字符串内存
  if(ret == atCmdLen)
  {
    sw_log_debug("[%s] %s send a cmd(%d bytes): %s", MODULE_NAME, log_prefix, pTrans->cmd.len, pTrans->cmd.data);
  }
  else
  {
    sw_log_error("[%s] %s failed to send a cmd(%d bytes, ret=%d): %s!!", MODULE_NAME, log_prefix, pTrans->cmd.len, ret, pTrans->cmd.data);
    serial_recvThrd_resume(pComIO->h); pTrans->ack.ret = -4; goto end_p;
  }

  pTrans->timeout = (wtime > timeout ? 0 : (timeout - wtime));
  pComIO->pTrans = pTrans;  serial_recvThrd_resume(pComIO->h); // 恢复串口数据接收, "pComIO->pTrans"的控制权转移给接收线程.
  sw_signal_wait(pTrans->hEndSgl, WAIT_FOREVER); // 等待指令执行结束, "pComIO->pTrans"的控制权收回

end_p:
  if(pTrans->hEndSgl) sw_signal_destroy(pTrans->hEndSgl);
  pComIO->pTrans = NULL;
  sw_mutex_unlock(pComIO->do_cmd_mutex);
  return pTrans->ack.ret;
}

// 接收、处理来自Air720U模块的数据报文帧, 串口-线程回调
static int comio_data_recv_proc(unsigned long wParam/*传递打开的串口句柄*/, unsigned long lParam/*保留暂未使用*/)
{
  SAir720UCom *pComIO = &s_myCom; void *pSerial = pComIO->h; SATCmdTrans *pTrans = pComIO->pTrans;
  const unsigned char *pRecvBuf = serial_get_recv_buffer(pSerial); int nRecvBytes = serial_get_recv_buffer_bytes(pSerial);
  const char *log_prefix = serial_get_log_prefix(pSerial); bool bPrintRecvBuf = true;
  static unsigned long enteredWaitAckTime = 0, now; static unsigned long enteredSilentTime = 0; static int nSilentRecvBytes = 0;

  if(!pTrans) { goto ret_p2; } // 无主动指令时

  // 1, 计算命令应答超时
  if(enteredWaitAckTime == 0) xgettickcount(&enteredWaitAckTime);
  xgettickcount(&now);

  // 2, 分析应答回复内容
  if(strstr((const char *)pRecvBuf, ATOK) != NULL || strstr((const char *)pRecvBuf, ATER) != NULL)
  { // 已应答
    if(nRecvBytes > nSilentRecvBytes) { enteredSilentTime = now; nSilentRecvBytes = nRecvBytes; }
    if((now-enteredSilentTime) < R_SILENT_TIMEOUT) { goto ret_p3; } // 等待静默期结束(延期多接收一会数据, 避免"内容"在"ATOK"或"ATER"之后)
    memcpy(pTrans->ack.data, pRecvBuf, nRecvBytes);
    pTrans->ack.ret = nRecvBytes; pTrans->ack.data[pTrans->ack.ret] = '\0'; bPrintRecvBuf = false;
    sw_log_debug("[%s] %s received a cmd ack(%d bytes): %s", MODULE_NAME, log_prefix, pTrans->ack.ret, pTrans->ack.data);
    goto ret_p1;
  }
  else if((now-enteredWaitAckTime) >= pTrans->timeout)
  { // 已超时
    sw_log_error("[%s] %s timeout occurred while waiting for the \"%s\" command's ack!!", MODULE_NAME, log_prefix, pTrans->cmd.data);
    pTrans->ack.ret = -1; pTrans->ack.data[0] = '\0';
    goto ret_p1;
  }
  else
  { // 继续收
    goto ret_p3;
  }

  // 3, 缓存数据接收控制
ret_p1: // 点亮信号量, 表明本指令执行结束
  sw_signal_give(pComIO->pTrans->hEndSgl); enteredWaitAckTime = 0; enteredSilentTime = 0; nSilentRecvBytes = 0;
  goto ret_p2;

ret_p2: // 清接收缓存, 重新开始一轮新指令
  if(nRecvBytes > 0 && bPrintRecvBuf)
  {
    serial_printf_recv_buffer(pSerial, LEVEL_TRACE); // 十六进制打印当前串口接收数据缓存
    sw_log_warn("[%s] %s discarded %d bytes of meaningless data!", MODULE_NAME, log_prefix, nRecvBytes);
  }
  if(nRecvBytes > 0) serial_clear_recv_buffer(pSerial);
  goto ret_p3;

ret_p3: return 1; // 持续累加的接收新数据
}

// 打开与模块的通讯, 返回: 0成功, <0时失败
int Air720U_ComInit()
{
  s_myCom.do_cmd_mutex = sw_mutex_create();
  if(!s_myCom.do_cmd_mutex) { sw_log_error("[%s] error to create a mutex!!", MODULE_NAME); return -1; }
  s_myCom.pTrans = NULL;

  const char *serialName = "/dev/ttyUSB0"; int baudrate = 115200;
  const char *parityCheck = "none"; // 无校检
  s_myCom.h = serial_open(serialName, baudrate, parityCheck, \
                          comio_data_recv_proc, comio_data_recv_proc, NULL);
  if(!s_myCom.h)
  {
    sw_log_error("[%s] failed to open the \"%s:%d(%s parity)\" device!!", \
                 MODULE_NAME, serialName, baudrate, parityCheck);
    sw_mutex_destroy(s_myCom.do_cmd_mutex); return -1;
  }

  SATCmdTrans trans; memset(&trans, 0, sizeof(SATCmdTrans));
  strcpy(trans.cmd.data, AT_USE_ECHO); trans.cmd.len = strlen(trans.cmd.data);
  int ret = comio_doCmd(&trans, WAIT_ACK_TIMEOUT);
  if(!(ret > 0 && strstr(trans.ack.data, ATOK)))
  {
    sw_log_error("[%s] failed to close echo mode!!", MODULE_NAME);
    Air720U_ComExit(); return -2+ret;
  }

  return 0;
}

// 关闭与模块的通讯, 返回: 0成功, <0时失败
int Air720U_ComExit()
{
  if(s_myCom.h) serial_close(s_myCom.h, WAITTHRD_SAFEEXIT_TIMEOUT);
  if(s_myCom.do_cmd_mutex) sw_mutex_destroy(s_myCom.do_cmd_mutex);
  memset(&s_myCom, 0, sizeof(s_myCom));
  return 0;
}

// 改写sysfs虚拟文件, 返回: 0成功, <0失败
static int sysfs_write(const char *path, const char *value)
{
  int fd = open(path, O_WRONLY);
  if (fd < 0) return -1;
  ssize_t len = write(fd, value, strlen(value));
  if(len != strlen(value)) { close(fd); return -2; }
  close(fd); return 0;
}

// 控制模块断电重启, 返回: 0成功, <0时失败
// 138脚 -- gpio4.IO[20] (4-1)*32+20 = 116 -- 4G_POWER
int Air720U_Repower()
{
  sysfs_write("/sys/class/gpio/unexport",             "116");                // 撤销原导出
  if(sysfs_write("/sys/class/gpio/export",            "116") < 0) return -2; // 重设新导出
  if(sysfs_write("/sys/class/gpio/gpio116/direction", "out") < 0) return -3; // 设置为输出
  if(sysfs_write("/sys/class/gpio/gpio116/value",       "1") < 0) return -4; // 输出高电平-断电
  sw_thrd_delay(5000);                                                       // 等一段时间-毫秒
  if(sysfs_write("/sys/class/gpio/gpio116/value",       "0") < 0) return -5; // 输出低电平-上电
  return 0;
}

// 获取模块的标识号, 返回: =15成功, <0失败(-1命令超时)
int Air720U_GetIMEI(char buf[16]/*固定长15个十进制数字组成*/)
{
  SATCmdTrans trans; memset(&trans, 0, sizeof(SATCmdTrans));
  strcpy(trans.cmd.data, AT_GET_IMEI);
  trans.cmd.len = strlen(trans.cmd.data);
  int ret = comio_doCmd(&trans, WAIT_ACK_TIMEOUT);
  if(ret < 0) return ret; // 执行超时或出错
  else if(strstr(trans.ack.data, ATOK) == NULL) goto end_p; // 未收到成功应答

  const char *p = trans.ack.data, *pLineS, *pLineE; int lineLen, crlfLen = strlen(CRLF);
lsa_p: // 行扫描, 逐行分析应答结果
  if((*p) == '\0') goto end_p;
  pLineS = strstr(p, CRLF); pLineE = NULL; lineLen = 0;
  if(pLineS) { pLineS += crlfLen; pLineE = strstr(pLineS, CRLF); }
  if(pLineE) { lineLen = pLineE - pLineS; }
  if(lineLen == 0)
  { // 连续两个CRLF, 上面的逻辑保证了"lineLen"只可能大于或等于0, 不会是小于0
    if(pLineE) { p = pLineE; goto lsa_p; } // 跳第一个CRLF, 继续分析下一行
    else goto end_p;
  }
  //// 示例: "359759002514931"
  if(lineLen == 15)
  {
    bool isHex = true;
    for(int i = 0; i < lineLen; i++) if(!xisxdigit(pLineS[i])) { isHex = false; break; }
    if(isHex) { memcpy(buf, pLineS, lineLen); buf[lineLen] = '\0'; return lineLen; } // 成功读到模块的IMEI号
  }
  p = pLineE + crlfLen;
  goto lsa_p; // 继续分析下一行

end_p:
  return -5;
}

// 获取网络注册状态, 返回: >=0成功, <0失败(-1命令超时)
int Air720U_GetCregState()
{
  SATCmdTrans trans; memset(&trans, 0, sizeof(SATCmdTrans));
  strcpy(trans.cmd.data, AT_GET_CREG);
  trans.cmd.len = strlen(trans.cmd.data);
  int ret = comio_doCmd(&trans, WAIT_ACK_TIMEOUT);
  if(ret < 0) return ret; // 执行超时或出错
  else if(strstr(trans.ack.data, ATOK) == NULL) goto end_p; // 未收到成功应答

  const char *p = trans.ack.data, *pLineS, *pLineE; int lineLen, crlfLen = strlen(CRLF);
  const char *p1, *p2; char statBuf[3]; int statLen;
lsa_p: // 行扫描, 逐行分析应答结果
  if((*p) == '\0') goto end_p;
  pLineS = strstr(p, CRLF); pLineE = NULL; lineLen = 0;
  if(pLineS) { pLineS += crlfLen; pLineE = strstr(pLineS, CRLF); }
  if(pLineE) { lineLen = pLineE - pLineS; }
  if(lineLen == 0)
  { // 连续两个CRLF, 上面的逻辑保证了"lineLen"只可能大于或等于0, 不会是小于0
    if(pLineE) { p = pLineE; goto lsa_p; } // 跳第一个CRLF, 继续分析下一行
    else goto end_p;
  }
  //// 示例: "+CREG: <n>,<stat>"
  //// 说明: <n>注册状态报告模式, <stat>当前网络注册状态
  p1 = strstr(pLineS, "+CREG:"); p2 = NULL;
  if(p1 && p1 < pLineE) { p1 += 6; p2 = strchr(p1, ','); }
  if(p2 && p2 < pLineE)
  {
    p2++; while(*p2 == 0x20 && p2 < pLineE) { p2++; } // 跳过空格
    statLen = (pLineE - p2); if(statLen != 1 && statLen != 2) return -5; // 状态值范围: 1或2个数字
    memcpy(statBuf, p2, statLen); statBuf[statLen] = '\0';
    bool isDigit = true;
    for(int i = 0; i < strlen(statBuf); i++) if(!xisdigit(statBuf[i])) { isDigit = false; break; }
    if(isDigit) return atoi(statBuf); // 成功读到模块的网络注册状态值
    else return -6;
  }
  p = pLineE + crlfLen;
  goto lsa_p; // 继续分析下一行

end_p:
  return -7;
}

// 获取当前信号强度, 返回: >=0成功, <0失败(-1命令超时)
int Air720U_GetRSSIFromCSQ()
{
  SATCmdTrans trans; memset(&trans, 0, sizeof(SATCmdTrans));
  strcpy(trans.cmd.data, AT_GET_CSQ);
  trans.cmd.len = strlen(trans.cmd.data);
  int ret = comio_doCmd(&trans, WAIT_ACK_TIMEOUT);
  if(ret < 0) return ret; // 执行超时或出错
  else if(strstr(trans.ack.data, ATOK) == NULL) goto end_p; // 未收到成功应答

  const char *p = trans.ack.data, *pLineS, *pLineE; int lineLen, crlfLen = strlen(CRLF);
  const char *p1, *p2; char rssiBuf[3]; int rssiLen;
lsa_p: // 行扫描, 逐行分析应答结果
  if((*p) == '\0') goto end_p;
  pLineS = strstr(p, CRLF); pLineE = NULL; lineLen = 0;
  if(pLineS) { pLineS += crlfLen; pLineE = strstr(pLineS, CRLF); }
  if(pLineE) { lineLen = pLineE - pLineS; }
  if(lineLen == 0)
  { // 连续两个CRLF, 上面的逻辑保证了"lineLen"只可能大于或等于0, 不会是小于0
    if(pLineE) { p = pLineE; goto lsa_p; } // 跳第一个CRLF, 继续分析下一行
    else goto end_p;
  }
  //// 示例: "+CSQ: 15,99"
  //// 说明: 逗号前第一个数值是RSSI的值
  p1 = strstr(pLineS, "+CSQ:"); p2 = NULL;
  if(p1 && p1 < pLineE) { p1 += 5; p2 = strchr(p1, ','); }
  if(p2 && p2 < pLineE)
  {
    while(*p1 == 0x20 && p1 < p2) { p1++; } // 跳过空格
    rssiLen = (p2 - p1); if(rssiLen != 1 && rssiLen != 2) return -5; // 强度值范围: 0-99, 1或2个数字
    memcpy(rssiBuf, p1, rssiLen); rssiBuf[rssiLen] = '\0';
    bool isDigit = true;
    for(int i = 0; i < strlen(rssiBuf); i++) if(!xisdigit(rssiBuf[i])) { isDigit = false; break; }
    if(isDigit) return atoi(rssiBuf); // 成功读到模块的接收信号强度值
    else return -6;
  }
  p = pLineE + crlfLen;
  goto lsa_p; // 继续分析下一行

end_p:
  return -7;
}

// 返回电话卡的状态, 返回: 1已可用, <0失败(-1命令超时)
int Air720U_IsSimCardReady()
{
  SATCmdTrans trans; memset(&trans, 0, sizeof(SATCmdTrans));
  strcpy(trans.cmd.data, AT_GET_PINSTA);
  trans.cmd.len = strlen(trans.cmd.data);
  int ret = comio_doCmd(&trans, WAIT_ACK_TIMEOUT);
  if(ret < 0) return ret; // 执行超时或出错
  else if(strstr(trans.ack.data, ATOK) && strstr(trans.ack.data, "+CPIN: READY")) return 1;
  else return -5;
}

// 获取电话卡标识号, 返回: =20成功, <0失败(-1命令超时)
int Air720U_GetSimICCID(char buf[21]/*一般由20个十进制数字组成*/)
{
  SATCmdTrans trans; memset(&trans, 0, sizeof(SATCmdTrans));
  strcpy(trans.cmd.data, AT_GET_ICCID);
  trans.cmd.len = strlen(trans.cmd.data);
  int ret = comio_doCmd(&trans, WAIT_ACK_TIMEOUT);
  if(ret < 0) return ret; // 执行超时或出错
  else if(strstr(trans.ack.data, ATOK) == NULL) goto end_p; // 未收到成功应答

  const char *p = trans.ack.data, *pLineS, *pLineE; int lineLen, crlfLen = strlen(CRLF);
  const char *p1, *p2; int iccidLen;
lsa_p: // 行扫描, 逐行分析应答结果
  if((*p) == '\0') goto end_p;
  pLineS = strstr(p, CRLF); pLineE = NULL; lineLen = 0;
  if(pLineS) { pLineS += crlfLen; pLineE = strstr(pLineS, CRLF); }
  if(pLineE) { lineLen = pLineE - pLineS; }
  if(lineLen == 0)
  { // 连续两个CRLF, 上面的逻辑保证了"lineLen"只可能大于或等于0, 不会是小于0
    if(pLineE) { p = pLineE; goto lsa_p; } // 跳第一个CRLF, 继续分析下一行
    else goto end_p;
  }
  //// 示例: "+ICCID: 89860117831003134201"
  p1 = strstr(pLineS, "+ICCID:"); p2 = NULL;
  if(p1 && p1 < pLineE) { p1 += 7; p2 = pLineE; }
  if(p2)
  {
    while(*p1 == 0x20 && p1 < p2) { p1++; } // 跳过空格
    iccidLen = (p2 - p1); if(iccidLen != 20) return -5; // 长度不对
    bool isHex = true;
    for(int i = 0; i < iccidLen; i++) if(!xisxdigit(p1[i])) { isHex = false; break; }
    if(isHex) { memcpy(buf, p1, iccidLen); buf[iccidLen] = '\0'; return iccidLen; } // 成功读到SIM卡的ICCID号
    else return -6;
  }
  p = pLineE + crlfLen;
  goto lsa_p; // 继续分析下一行

end_p:
  return -7;
}
