You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用PDU模式UCS2编码发送波斯语SMS出现乱码的原因及解决

波斯语短信PDU模式发送乱码问题排查与解决

问题描述

使用GSM调制解调器通过PDU模式+UCS2编码发送波斯语短信(内容为“سلام”)至号码0777220XXX,接收方收到的是㌆䐆✆这类乱码字符。已执行AT+CMGF=0切换PDU模式、将消息转为UTF-16大端(UCS2)格式、号码转为半字节编码,仍未解决乱码问题。

原始代码

public void SendSms(string recipientNumber, string messageText)
{
    try
    {
        SerialPort port = new SerialPort();
        port.PortName = "COM9"; // Make sure this is correct
        port.BaudRate = 9600;
        port.Parity = Parity.None;
        port.DataBits = 8;
        port.StopBits = StopBits.One;
        port.Handshake = Handshake.None;
        port.ReadTimeout = 5000;
        port.WriteTimeout = 5000;

        port.Open();

        // Set to UCS2 character set
        port.WriteLine("AT+CSCS=\"UCS2\"\r");
        Thread.Sleep(1000);
        string response = port.ReadExisting();
        MessageBox.Show("Modem response UCS2: " + response, "Response", MessageBoxButtons.OK, MessageBoxIcon.Information);

        // Set SMS mode to PDU (0 = PDU mode)
        port.WriteLine("AT+CMGF=0\r");
        Thread.Sleep(1000);
        response = port.ReadExisting();
        MessageBox.Show("Modem response Text Mode: " + response, "Response", MessageBoxButtons.OK, MessageBoxIcon.Information);

        // Convert Persian message to PDU format
        string pduMessage = ConvertToPduFormat(recipientNumber, messageText);

        // Get PDU length (without SCA part)
        int pduLength = (pduMessage.Length / 2) - 1;

        // Send the PDU command
        port.WriteLine($"AT+CMGS={pduLength}\r");
        Thread.Sleep(1000);
        response = port.ReadExisting();
        MessageBox.Show("Modem response send pdu: " + response, "Response", MessageBoxButtons.OK, MessageBoxIcon.Information);

        // Send the actual PDU data
        port.Write(pduMessage + "\r");
        Thread.Sleep(1000);

        // Send Ctrl+Z (ASCII 26) to submit SMS
        port.Write(new byte[] { 26 }, 0, 1);
        Thread.Sleep(5000);

        // Read response
        response = port.ReadExisting();
        MessageBox.Show("Modem response send message: " + response, "Response", MessageBoxButtons.OK, MessageBoxIcon.Information);

        if (response.Contains("ERROR"))
        {
            MessageBox.Show("Send Failed!", "Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
        else
        {
            MessageBox.Show("Message Sent Successfully!", "Message", MessageBoxButtons.OK, MessageBoxIcon.Information);
            SaveMessageToDatabase(recipientNumber, messageText);
        }

        port.Close();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

public static string ConvertToPduFormat(string recipientNumber, string message)
{
    // Convert Persian text to UCS2 (UTF-16 Big Endian)
    byte[] ucs2Bytes = Encoding.BigEndianUnicode.GetBytes(message); // Use BigEndianUnicode for UCS2
    string hexMessage = BitConverter.ToString(ucs2Bytes).Replace("-", "");

    // Convert phone number to international format (remove leading 0)
    if (recipientNumber.StartsWith("0"))
    {
        recipientNumber = "+93" + recipientNumber.Substring(1); // Example for Afghanistan (+93)
    }

    // Convert phone number to semi-octet format
    string pduPhoneNumber = ConvertPhoneNumberToSemiOctets(recipientNumber);

    // PDU Header
    string pduHeader = "0001000B91" + pduPhoneNumber + "0008" + hexMessage;

    return pduHeader;
}

private static string ConvertPhoneNumberToSemiOctets(string phoneNumber)
{
    string semiOctets = "";
    if (phoneNumber.StartsWith("+"))
    {
        phoneNumber = phoneNumber.Substring(1);
    }

    for (int i = 0; i < phoneNumber.Length; i += 2)
    {
        if (i + 1 < phoneNumber.Length)
        {
            semiOctets += phoneNumber[i + 1].ToString() + phoneNumber[i].ToString();
        }
        else
        {
            semiOctets += "F" + phoneNumber[i].ToString(); // Add "F" padding if needed
        }
    }
    return semiOctets;
}

乱码原因分析

  1. PDU头部结构缺失关键字段:原始代码的PDU头缺少**TP-UDL(用户数据长度)**字段,接收方无法正确解析消息长度;同时TP-DA长度字段位置错误,导致地址解析异常。
  2. 冗余字符集设置干扰:PDU模式下modem直接接收十六进制编码数据,AT+CSCS="UCS2"是文本模式的字符集命令,会导致modem对数据进行额外编码处理。
  3. PDU长度计算错误:(pduMessage.Length / 2) - 1的计算方式不符合AT+CMGS参数要求,参数应为TPDU的总字节数(十六进制字符串长度/2)。
  4. PDU数据发送冗余换行:发送PDU数据时添加\r会破坏十六进制数据结构。

修正方案

1. 修复PDU头部结构

在ConvertToPduFormat方法中补充TP-UDL字段,并调整PDU头部结构:

public static string ConvertToPduFormat(string recipientNumber, string message)
{
    // 转换波斯语为UCS2字节数组
    byte[] ucs2Bytes = Encoding.BigEndianUnicode.GetBytes(message);
    string hexMessage = BitConverter.ToString(ucs2Bytes).Replace("-", "");
    // TP-UDL:UCS2消息的字节数,转为两位十六进制
    string udLength = ucs2Bytes.Length.ToString("X2");

    // 转换号码为国际格式
    if (recipientNumber.StartsWith("0"))
    {
        recipientNumber = "+93" + recipientNumber.Substring(1);
    }

    // 号码转半字节编码
    string pduPhoneNumber = ConvertPhoneNumberToSemiOctets(recipientNumber);

    // 正确PDU结构:TP-MTI(00) + TP-MR(01) + TP-DA长度(0B) + 地址类型(91) + 号码 + TP-PID(00) + TP-DCS(08) + TP-UDL + 消息
    string pduHeader = "00010B91" + pduPhoneNumber + "0008" + udLength + hexMessage;

    return pduHeader;
}

2. 调整SendSms方法

  • 移除冗余的AT+CSCS="UCS2"命令
  • 修正PDU长度计算逻辑
  • 移除PDU数据发送时的冗余换行
public void SendSms(string recipientNumber, string messageText)
{
    try
    {
        SerialPort port = new SerialPort();
        port.PortName = "COM9";
        port.BaudRate = 9600;
        port.Parity = Parity.None;
        port.DataBits = 8;
        port.StopBits = StopBits.One;
        port.Handshake = Handshake.None;
        port.ReadTimeout = 5000;
        port.WriteTimeout = 5000;

        port.Open();

        // 设置PDU模式
        port.WriteLine("AT+CMGF=0\r");
        Thread.Sleep(1000);
        string response = port.ReadExisting();
        MessageBox.Show("Modem response PDU Mode: " + response, "Response", MessageBoxButtons.OK, MessageBoxIcon.Information);

        // 生成PDU消息
        string pduMessage = ConvertToPduFormat(recipientNumber, messageText);

        // 计算TPDU总字节数(十六进制字符串长度/2)
        int pduLength = pduMessage.Length / 2;

        // 发送CMGS命令
        port.WriteLine($"AT+CMGS={pduLength}\r");
        Thread.Sleep(1000);
        response = port.ReadExisting();
        MessageBox.Show("Modem response send pdu: " + response, "Response", MessageBoxButtons.OK, MessageBoxIcon.Information);

        // 发送PDU数据(无冗余换行)
        port.Write(pduMessage);
        Thread.Sleep(1000);

        // 发送Ctrl+Z提交
        port.Write(new byte[] { 26 }, 0, 1);
        Thread.Sleep(5000);

        // 读取结果
        response = port.ReadExisting();
        MessageBox.Show("Modem response send message: " + response, "Response", MessageBoxButtons.OK, MessageBoxIcon.Information);

        if (response.Contains("ERROR"))
        {
            MessageBox.Show("Send Failed!", "Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
        else
        {
            MessageBox.Show("Message Sent Successfully!", "Message", MessageBoxButtons.OK, MessageBoxIcon.Information);
            SaveMessageToDatabase(recipientNumber, messageText);
        }

        port.Close();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

3. 额外验证项

  • 发送AT+CSCS=?查询modem支持的字符集,确认包含"UCS2"
  • 测试接收方手机的波斯语显示支持性,确认区域语言设置正确
  • 先用中文“测试”等已知UCS2字符验证编码逻辑是否正常

内容的提问来源于stack exchange,提问作者Mohammad Samim Safi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 13:45:56