#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <string.h>
#include <locale.h>


#define MODEMDEVICE 	  "/dev/ttyS0"
#define MODEMSPEED  	  B115200
#define MAX_MESSAGE_LEN 140

// 用户信息编码方式
#define GSM_7BIT        0
#define GSM_8BIT        4
#define GSM_UCS2        8

// 短消息参数结构，编码/解码共用
// 其中，字符串以0结尾
typedef struct
{
  char SCA[16];       // 短消息服务中心号码(SMSC地址)
  char TPA[16];       // 目标号码或回复号码(TP-DA或TP-RA)
  char TP_PID;        // 用户信息协议标识(TP-PID)
  char TP_DCS;        // 用户信息编码方式(TP-DCS)
  char TP_SCTS[16];   // 服务时间戳字符串(TP_SCTS), 接收时用到
  char TP_UD[161];    // 原始用户信息(编码前或解码后的TP-UD)
  char index;         // 短消息序号，在读取时用到
} SM_PARAM;


static unsigned char message[4096];
static unsigned char* start = message;
static int length = 0;

// 从短信息缓冲区中提取指定长度的字符到buf中
static int getMessage(unsigned char* buf, int len)
{
  if(length <= 0)
    return 0;

  if(length < len)
    len = length;
  memcpy(buf, start, len);
  length -= len;
  start += len;

  return len;
}

// UCS2编码
// pSrc: 源字符串指针
// pDst: 目标编码串指针
// nSrcLength: 源字符串长度
// 返回: 目标编码串长度
static int gsmEncodeUcs2(const char* pSrc, unsigned char* pDst, int nSrcLength)
{
  int nDstLength; // UNICODE宽字符数目
  wchar_t wchar[4096]; // UNICODE串缓冲区

  // 字符串-->UNICODE串
  setlocale(LC_CTYPE,"zh_CN.GB18030");  // 设置默认编码格式
  nDstLength = mbstowcs(wchar, pSrc, 4096);
  if(nDstLength < 0)
    return -1;

  // 高低字节对调，输出
  for(int i=0; i<nDstLength; i++)
  {
    // 先输出高位字节
    *pDst++ = wchar[i] >> 8;
    // 后输出低位字节
    *pDst++ = wchar[i] & 0xff;
  }

  // 返回目标编码串长度
  return nDstLength * 2;
}

// 字节数据转换为可打印字符串
// 如：{0xC8, 0x32, 0x9B, 0xFD, 0x0E, 0x01} --> "C8329BFD0E01" 
// pSrc: 源数据指针
// pDst: 目标字符串指针
// nSrcLength: 源数据长度
// 返回: 目标字符串长度
static int gsmBytes2String(const unsigned char* pSrc, char* pDst, int nSrcLength)
{
  const char tab[]="0123456789ABCDEF";    // 0x0-0xf的字符查找表

  for(int i=0; i<nSrcLength; i++)
  {
    // 输出低4位
    *pDst++ = tab[*pSrc >> 4];

    // 输出高4位
    *pDst++ = tab[*pSrc & 0x0f];

    pSrc++;
  }

  // 输出字符串加个结束符
  *pDst = '\0';

  // 返回目标字符串长度
  return nSrcLength * 2;
}

// 可打印字符串转换为字节数据
// 如："C8329BFD0E01" --> {0xC8, 0x32, 0x9B, 0xFD, 0x0E, 0x01}
// pSrc: 源字符串指针
// pDst: 目标数据指针
// nSrcLength: 源字符串长度
// 返回: 目标数据长度
static int gsmString2Bytes(const char* pSrc, unsigned char* pDst, int nSrcLength)
{
  for(int i=0; i<nSrcLength; i+=2)
  {
    // 输出高4位
    if(*pSrc>='0' && *pSrc<='9')
    {
      *pDst = (*pSrc - '0') << 4;
    }
    else
    {
      *pDst = (*pSrc - 'A' + 10) << 4;
    }

    pSrc++;

    // 输出低4位
    if(*pSrc>='0' && *pSrc<='9')
    {
      *pDst |= *pSrc - '0';
    }
    else
    {
      *pDst |= *pSrc - 'A' + 10;
    }
    pSrc++;
    pDst++;
  }

  // 返回目标数据长度
  return nSrcLength / 2;
}



// 正常顺序的字符串转换为两两颠倒的字符串，若长度为奇数，补'F'凑成偶数
// 如："8613851872468" --> "683158812764F8"
// pSrc: 源字符串指针
// pDst: 目标字符串指针
// nSrcLength: 源字符串长度
// 返回: 目标字符串长度
static int gsmInvertNumbers(const char* pSrc, char* pDst, int nSrcLength)
{
  int nDstLength;   // 目标字符串长度
  char ch;          // 用于保存一个字符

  // 复制串长度
  nDstLength = nSrcLength;

  // 两两颠倒
  for(int i=0; i<nSrcLength;i+=2)
  {
    ch = *pSrc++;        // 保存先出现的字符
    *pDst++ = *pSrc++;   // 复制后出现的字符
    *pDst++ = ch;        // 复制先出现的字符
  }

  // 源串长度是奇数吗？
  if(nSrcLength & 1)
  {
    *(pDst-2) = 'F';     // 补'F'
    nDstLength++;        // 目标串长度加1
  }

  // 输出字符串加个结束符
  *pDst = '\0';

  // 返回目标字符串长度
  return nDstLength;
}


// PDU编码，用于编制、发送短消息
// pSrc: 源PDU参数指针
// pDst: 目标PDU串指针 
// 返回: 目标PDU串长度
static int gsmEncodePdu(const SM_PARAM* pSrc, char* pDst)
{
  int nLength;             // 内部用的串长度
  int nDstLength;          // 目标PDU串长度
  unsigned char buf[256]; // 内部用的缓冲区

  // SMSC地址信息段
  nLength = strlen(pSrc->SCA);    // SMSC地址字符串的长度    
  buf[0] = (char)((nLength & 1) == 0 ? nLength : nLength + 1) / 2 + 1;    // SMSC地址信息长度
  buf[1] = 0x91;        // 固定: 用国际格式号码
  nDstLength = gsmBytes2String(buf, pDst, 2);        // 转换2个字节到目标PDU串
  nDstLength += gsmInvertNumbers(pSrc->SCA, &pDst[nDstLength], nLength);    // 转换SMSC到目标PDU串

  // TPDU段基本参数、目标地址等
  nLength = strlen(pSrc->TPA);    // TP-DA地址字符串的长度
  buf[0] = 0x11;            // 是发送短信(TP-MTI=01)，TP-VP用相对格式(TP-VPF=10)
  buf[1] = 0;               // TP-MR=0
  buf[2] = (char)nLength;   // 目标地址数字个数(TP-DA地址字符串真实长度)
  buf[3] = 0x81;            // 固定: 用国际格式号码是0x91, 不加86是0x81
  nDstLength += gsmBytes2String(buf, &pDst[nDstLength], 4); // 转换4个字节到目标PDU串
  nDstLength += gsmInvertNumbers(pSrc->TPA, &pDst[nDstLength], nLength); // 转换TP-DA到目标PDU串

  // TPDU段协议标识、编码方式、用户信息等
  nLength = strlen(pSrc->TP_UD);    // 用户信息字符串的长度
  buf[0] = pSrc->TP_PID;        // 协议标识(TP-PID)
  buf[1] = pSrc->TP_DCS;        // 用户信息编码方式(TP-DCS)
  buf[2] = 0xAA;            // 有效期(TP-VP)为5分钟，不知道为什么用0xAA
  if(pSrc->TP_DCS == GSM_7BIT)
  {
    // 7-bit编码方式
    buf[3] = nLength;            // 编码前长度
    // nLength = gsmEncode7bit(pSrc->TP_UD, &buf[4], nLength+1) + 4;    // 转换TP-DA到目标PDU串
  }
  else if(pSrc->TP_DCS == GSM_UCS2)
  {
    // UCS2编码方式
    // buf[3] = gsmEncodeUcs2(pSrc->TP_UD, &buf[4], nLength);    // 转换TP-DA到目标PDU串
    buf[3] = getMessage(&buf[4], MAX_MESSAGE_LEN);
    nLength = buf[3] + 4;        // nLength等于该段数据长度
  }
  else
  {
    // 8-bit编码方式
    // buf[3] = gsmEncode8bit(pSrc->TP_UD, &buf[4], nLength);    // 转换TP-DA到目标PDU串
    nLength = buf[3] + 4;        // nLength等于该段数据长度
  }
  nDstLength += gsmBytes2String(buf, &pDst[nDstLength], nLength);        // 转换该段数据到目标PDU串

  // 返回目标字符串长度
  return nDstLength;
}

// 打开串口并加文件锁
// 失败返回-1
static int initComm()
{
  int retry = 60;

  int mfd = open(MODEMDEVICE, O_RDWR | O_NOCTTY | O_NDELAY);  
  if(mfd < 0)
  {
    perror(MODEMDEVICE);   
    return -1;
  }

  // 串口打开成功后对串口加锁，如果长时间不能获得锁，则返回
  while(lockf(mfd, F_TLOCK, 0) < 0)
  {
    sleep(1);
    retry--;
    if(retry <= 0)
    {
      printf("Devcie %s locked.\n", MODEMDEVICE);
      close(mfd);
      return -1;
    }
  }

  struct termios Opt;
  tcgetattr(mfd, &Opt); 
  cfsetispeed(&Opt, MODEMSPEED);  
  cfsetospeed(&Opt, MODEMSPEED);   
  tcsetattr(mfd, TCSANOW, &Opt);  
  tcflush(mfd, TCIOFLUSH);

  tcgetattr(mfd, &Opt);

  Opt.c_cflag &= ~CSIZE; 
  Opt.c_cflag |= CS8;                                   /* 数据位 8 */
  Opt.c_cflag &= ~PARENB;                               /* Clear parity enable */
  Opt.c_cflag &= ~CSTOPB;                               /* 停止位 1 */
  Opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
  Opt.c_oflag  &= ~OPOST;
  Opt.c_cc[VTIME] = 150;                                /* 设置超时15 seconds*/   
  Opt.c_cc[VMIN] = 0;                                   /* Update the options and do it NOW */

  if(tcsetattr(mfd, TCSANOW, &Opt) < 0)
  {
    perror(MODEMDEVICE);   
    close(mfd);
    return -1;
  }
  tcflush(mfd, TCIOFLUSH);

  return mfd;
}

// 关闭由initComm 打开的串口句柄
static void destroyComm(int fd)
{
  if(fd < 0)
    return;

  lockf(fd, F_ULOCK, 0);
  close(fd);
}

static int writeComm(int fd, char* buf, int len)
{
  int rs = write(fd, buf, len);
  if(rs == -1)
  {
    perror("writeComm:");
    return -1;
  }
  printf("writeComm: %s\n", buf);
  return 0;
}

static int readComm(int fd, char* buf, int len, int timeout /* 毫秒 */)
{
  fd_set fds;
  FD_ZERO(&fds);
  FD_SET(fd, &fds);
  struct timeval tv = {0, 1000*timeout};
  int total = len;
  char* startRev = buf;
  while(total > 0)
  {
    int res = select(fd+1, &fds, NULL, NULL, &tv);
    if(res == -1)
    {
      if(errno == EINTR)
        continue;
      else
      {
        printf("socket error %d with select", errno);
        return -1;
      }
    }
    else if(res == 0)
    {
      buf[len - total] = '\0';
      printf("readComm: %s\n", buf);
      return len - total;
    }

    if(FD_ISSET(fd, &fds))
    {
      int rs = read(fd, startRev, len);
      if(rs < 0)
      {
        buf[len - total] = '\0';
        perror("readComm:");
        return -1;
      }
      // printf("total: %d %d\n", total, len);
      total -= rs;
      startRev += rs;
    }
  }

  printf("readComm: %s\n", buf);
  return len - total;
}

// 发送短消息
// pSrc: 源PDU参数指针
static bool gsmSendMessage(SM_PARAM* pSrc)
{
  int nPduLength;        // PDU串长度
  unsigned char nSmscLength;    // SMSC串长度
  int nLength;           // 串口收到的数据长度
  char cmd[16];          // 命令串
  char pdu[512];         // PDU串
  char ans[128];         // 应答串
  bool sendRes = false;

  int fd = initComm();
  if(fd < 0)
    return false;

  strcpy(cmd, "ATZ\r");
  writeComm(fd, cmd, strlen(cmd));
  nLength = readComm(fd, ans, 128, 300);

  strcpy(cmd, "ATE0\r");
  writeComm(fd, cmd, strlen(cmd));
  nLength = readComm(fd, ans, 128, 300);

  strcpy(cmd, "AT+CGMM\r");
  writeComm(fd, cmd, strlen(cmd));
  nLength = readComm(fd, ans, 128, 300);

  strcpy(cmd, "AT+CGMI\r");
  writeComm(fd, cmd, strlen(cmd));
  nLength = readComm(fd, ans, 128, 300);

  strcpy(cmd, "AT+CSCS?\r");
  writeComm(fd, cmd, strlen(cmd));
  nLength = readComm(fd, ans, 128, 300);

  strcpy(cmd, "AT+CSCA?\r");
  writeComm(fd, cmd, strlen(cmd));
  nLength = readComm(fd, ans, 128, 300);
  char* p = strstr(ans, "+86");
  if(p != NULL)
  {
    strncpy(pSrc->SCA, p+1, 13);
    printf("center: %s\n", pSrc->SCA);
  }
  
  strcpy(cmd, "AT+CMGF=0\r");
  writeComm(fd, cmd, strlen(cmd));
  nLength = readComm(fd, ans, 128, 300);

  nPduLength = gsmEncodePdu(pSrc, pdu);    // 根据PDU参数，编码PDU串
  strcat(pdu, "\x01a");        // 以Ctrl-Z结束

  gsmString2Bytes(pdu, &nSmscLength, 2);    // 取PDU串中的SMSC信息长度
  nSmscLength++;        // 加上长度字节本身

  // 命令中的长度，不包括SMSC信息长度，以数据字节计
  sprintf(cmd, "AT+CMGS=%d\r", nPduLength / 2 - nSmscLength);    // 生成命令

  writeComm(fd, cmd, strlen(cmd));    // 先输出命令串

  nLength = readComm(fd, ans, 128, 300);   // 读应答数据

  // 根据能否找到"> "决定成功与否
  if(strstr(ans, ">") != NULL)
  {
    writeComm(fd, pdu, strlen(pdu));                // 得到肯定回答，继续输出PDU串
    int retry = 10;
    nLength = 0;
    while(retry--)
    {
      nLength = readComm(fd, ans + nLength, 128, 1000);        // 读应答数据，这里延时设长一点等待猫把短信发出去
      if(nLength < 0)
        break;

      // 根据能否找到"+CMGS OK"决定成功与否
      if(strstr(ans, "+CMGS") != NULL && strstr(ans, "OK") != NULL)
      {
        sendRes = true; // 设置成功发送的标志
        break;
      }
      else if(strstr(ans, "ERROR") != NULL)
      {
        break;
      }
    }
  }

  destroyComm(fd);
  return sendRes;
}


int sendGsmMsg(const char* phonenumber, char* msg)
{
  SM_PARAM sms;
  strcpy(sms.SCA, "8613800100500"); // 信息中心号码
  strcpy(sms.TPA, phonenumber);     // 目标手机号
  sms.TP_PID = 0x00;                // 协议类型
  sms.TP_DCS = GSM_UCS2;            // 编码类型

  start = message;  // 初始化提取内容指针
  length = 0;       // 初始化内容长度

  // 先将长短信encode到message缓冲区，在发送短信时调用getMessage提取70个字，知道length为0，发动完成。
  length = gsmEncodeUcs2(msg, message, strlen(msg));

  while(length > 0)
  {
    if(gsmSendMessage(&sms))
      printf("send message: \'%s\' successful\n", msg);
    else
    {  
      printf("send message: \'%s\' failed\n", msg);
      break;
    }
  }

  return 0;
}

#if 0
int main(int argc, char* argv[])
{
  SM_PARAM sms;
  strcpy(sms.SCA, "8613800100500");
  strcpy(sms.TPA, "10086");
  sms.TP_PID = 0x00;
  sms.TP_DCS = GSM_UCS2;
  // strcpy(sms.TP_UD, "aaaaaa");
  char* test = "查询余额";

  start = message; // 初始化提取内容指针
  length = 0;     // 初始化内容长度

  length = gsmEncodeUcs2(test, message, strlen(test));

  while(length > 0)
  {
    if(gsmSendMessage(&sms))
      printf("send message: \'%s\' successful\n", test);
    else
    {  
      printf("send message: \'%s\' failed\n", test);
      break;
    }
  }

  return 0;
}
#endif
