首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

透過序列埠收發簡訊(下):PDU 完整編解碼與 AT 指令實作

     編輯:關於VC++

承接上篇《透過序列埠收發簡訊(上)》的 7-bit、8-bit、UCS2 編碼演算法,本文將完整呈現 PDU 完整字串的編解碼模組、GSM 07.05 AT 指令收發流程、序列埠通訊底層實作,以及實務常見問題解答。為確保程式碼可移植性,盡量使用 ANSI C 標準庫,避免 MFC 類別依賴。

核心常數與資料結構定義

// 使用者資料編碼方式
#define GSM_7BIT 0
#define GSM_8BIT 4
#define GSM_UCS2 8

// 簡訊參數結構(編碼/解碼共用)
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]; // 服務中心時間戳記 (接收用)
    char TP_UD[161];  // 原始使用者資料 (編碼前/解碼後)
    char index;       // 簡訊索引 (讀取用)
} SM_PARAM;

號碼與時間戳記的雙位元反轉工具函式

PDU 中號碼與時間採「兩兩顛倒」的 BCD 格式,奇數長度補 'F'。提供雙向轉換函式:

// 正常順序 → 兩兩顛倒(奇數補 F)
// "8613851872468" → "683158812764F8"
int gsmInvertNumbers(const char* pSrc, char* pDst, int nSrcLength);

// 兩兩顛倒 → 正常順序(移除尾部 F)
// "683158812764F8" → "8613851872468"
int gsmSerializeNumbers(const char* pSrc, char* pDst, int nSrcLength);

PDU 完整編碼模組(發送用)

int gsmEncodePdu(const SM_PARAM* pSrc, char* pDst) {
    unsigned char buf[256];
    int nDstLength = 0, nLength;

    // SMSC 地址資訊段
    nLength = strlen(pSrc->SCA);
    buf[0] = (char)(((nLength & 1) == 0 ? nLength : nLength + 1) / 2 + 1);
    buf[1] = 0x91; // 國際格式
    nDstLength += gsmBytes2String(buf, pDst, 2);
    nDstLength += gsmInvertNumbers(pSrc->SCA, &pDst[nDstLength], nLength);

    // TPDU 基本參數與目標地址
    nLength = strlen(pSrc->TPA);
    buf[0] = 0x11; // TP-MTI=01 (發送), TP-VPF=10 (相對格式)
    buf[1] = 0;    // TP-MR=0
    buf[2] = (char)nLength;
    buf[3] = 0x91; // 國際格式
    nDstLength += gsmBytes2String(buf, &pDst[nDstLength], 4);
    nDstLength += gsmInvertNumbers(pSrc->TPA, &pDst[nDstLength], nLength);

    // 協定識別碼、編碼方式、有效期、使用者資料
    nLength = strlen(pSrc->TP_UD);
    buf[0] = pSrc->TP_PID;
    buf[1] = pSrc->TP_DCS;
    buf[2] = 0; // 有效期 5 分鐘

    if (pSrc->TP_DCS == GSM_7BIT) {
        buf[3] = nLength; // 編碼前長度
        nLength = gsmEncode7bit(pSrc->TP_UD, &buf[4], nLength+1) + 4;
    } else if (pSrc->TP_DCS == GSM_UCS2) {
        buf[3] = gsmEncodeUcs2(pSrc->TP_UD, &buf[4], nLength);
        nLength = buf[3] + 4;
    } else {
        buf[3] = gsmEncode8bit(pSrc->TP_UD, &buf[4], nLength);
        nLength = buf[3] + 4;
    }
    nDstLength += gsmBytes2String(buf, &pDst[nDstLength], nLength);
    return nDstLength;
}

PDU 完整解碼模組(接收用)

int gsmDecodePdu(const char* pSrc, SM_PARAM* pDst) {
    unsigned char tmp, buf[256];
    // 解析 SMSC
    gsmString2Bytes(pSrc, &tmp, 2);
    tmp = (tmp - 1) * 2;
    pSrc += 4;
    gsmSerializeNumbers(pSrc, pDst->SCA, tmp);
    pSrc += tmp;

    // 解析 TPDU 基本參數與回覆地址 (TP-RA)
    gsmString2Bytes(pSrc, &tmp, 2);
    pSrc += 2;
    if (tmp & 0x80) { // 包含回覆地址
        gsmString2Bytes(pSrc, &tmp, 2);
        if (tmp & 1) tmp += 1;
        pSrc += 4;
        gsmSerializeNumbers(pSrc, pDst->TPA, tmp);
        pSrc += tmp;
    }

    // 解析 PID、DCS、時間戳記、使用者資料長度
    gsmString2Bytes(pSrc, (unsigned char*)&pDst->TP_PID, 2); pSrc += 2;
    gsmString2Bytes(pSrc, (unsigned char*)&pDst->TP_DCS, 2); pSrc += 2;
    gsmSerializeNumbers(pSrc, pDst->TP_SCTS, 14); pSrc += 14;
    gsmString2Bytes(pSrc, &tmp, 2); pSrc += 2; // TP-UDL

    // 依編碼方式解碼使用者資料
    if (pDst->TP_DCS == GSM_7BIT) {
        int nDstLength = gsmString2Bytes(pSrc, buf, tmp & 7 ? (int)tmp * 7 / 4 + 2 : (int)tmp * 7 / 4);
        gsmDecode7bit(buf, pDst->TP_UD, nDstLength);
        return tmp;
    } else if (pDst->TP_DCS == GSM_UCS2) {
        int nDstLength = gsmString2Bytes(pSrc, buf, tmp * 2);
        return gsmDecodeUcs2(buf, pDst->TP_UD, nDstLength);
    } else {
        int nDstLength = gsmString2Bytes(pSrc, buf, tmp * 2);
        return gsmDecode8bit(buf, pDst->TP_UD, nDstLength);
    }
}

GSM 07.05 AT 指令收發實作

發送簡訊:AT+CMGS

BOOL gsmSendMessage(const SM_PARAM* pSrc) {
    char pdu[512], cmd[16], ans[128];
    int nPduLength = gsmEncodePdu(pSrc, pdu);
    strcat(pdu, "\x1A"); // Ctrl-Z 結束

    unsigned char nSmscLength;
    gsmString2Bytes(pdu, &nSmscLength, 2);
    nSmscLength++; // 含長度位元組本身
    sprintf(cmd, "AT+CMGS=%d\r", nPduLength / 2 - nSmscLength);

    WriteComm(cmd, strlen(cmd));
    int nLength = ReadComm(ans, 128);
    if (nLength == 4 && strncmp(ans, "\r\n> ", 4) == 0) {
        WriteComm(pdu, strlen(pdu));
        nLength = ReadComm(ans, 128);
        if (nLength > 0 && strncmp(ans, "+CMS ERROR", 10) != 0)
            return TRUE;
    }
    return FALSE;
}

讀取簡訊:AT+CMGL(一次讀取全部)

int gsmReadMessage(SM_PARAM* pMsg) {
    char ans[1024], cmd[16] = "AT+CMGL\r";
    WriteComm(cmd, strlen(cmd));
    int nLength = ReadComm(ans, 1024);
    if (nLength > 0 && strncmp(ans, "+CMS ERROR", 10) != 0) {
        int nMsg = 0;
        char* ptr = ans;
        while ((ptr = strstr(ptr, "+CMGL:")) != NULL) {
            ptr += 6;
            sscanf(ptr, "%d", &pMsg->index);
            ptr = strstr(ptr, "\r\n") + 2;
            gsmDecodePdu(ptr, pMsg);
            pMsg++; nMsg++;
        }
        return nMsg;
    }
    return 0;
}

刪除簡訊:AT+CMGD

BOOL gsmDeleteMessage(int index) {
    char cmd[16], ans[128];
    sprintf(cmd, "AT+CMGD=%d\r", index);
    WriteComm(cmd, strlen(cmd));
    int nLength = ReadComm(ans, 128);
    return (nLength > 0 && strncmp(ans, "+CMS ERROR", 10) != 0);
}

Windows 序列埠 API 實作(同步阻塞模式)

HANDLE hComm;

BOOL OpenComm(const char* pPort, int nBaudRate, int nParity, int nByteSize, int nStopBits) {
    DCB dcb;
    COMMTIMEOUTS timeouts = {100, 3, 2, 3, 2}; // 讀寫超時參數
    hComm = CreateFile(pPort, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
    if (hComm == INVALID_HANDLE_VALUE) return FALSE;
    GetCommState(hComm, &dcb);
    dcb.BaudRate = nBaudRate; dcb.StopBits = nStopBits;
    dcb.Parity = nParity; dcb.ByteSize = nByteSize;
    SetCommState(hComm, &dcb);
    SetCommTimeouts(hComm, &timeouts);
    return TRUE;
}

// WriteFile / ReadFile / CloseHandle 發送、接收、關閉

實務常見問題 Q&A

  • AT 指令失敗重試:手機可能處於來電/通話狀態回傳 +CMS ERROR,應檢測回應失敗後重發。建議將收發封裝於工作執行緒,避免阻塞 UI。
  • 廠商相容性:GSM 07.05 1998 年定案,基本 PDU 模式 AT 指令各廠商應支援,但可能有細微差異。
  • EMS 標準:Motorola、Ericsson 等制定 EMS 統一鈴聲/圖片格式,Nokia 未參與,格式不一。EMS 仍在 GSM 07.05 框架下,僅 TP-UD 資料格式不同。
  • 自訂資料傳輸:TP-UD 可傳輸任何自訂資料(GPS、加密資訊等),編碼方式仍需遵守規範。建議在資料前加識別碼(如 "FFFF")區分一般簡訊。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved