nanoFramework.Iot.Device.AtModem 1.0.216

前缀已保留
dotnet add package nanoFramework.Iot.Device.AtModem --version 1.0.216                
NuGet\Install-Package nanoFramework.Iot.Device.AtModem -Version 1.0.216                
此命令旨在在 Visual Studio 的包管理器控制台中使用,因为它使用了 NuGet 模块的 Install-Package 版本。
<PackageReference Include="nanoFramework.Iot.Device.AtModem" Version="1.0.216" />                
对于支持 PackageReference 的项目,将此 XML 节点复制到项目文件中以引用包。
paket add nanoFramework.Iot.Device.AtModem --version 1.0.216                
#r "nuget: nanoFramework.Iot.Device.AtModem, 1.0.216"                
#r 指令可用于 F# Interactive 和多语言笔记本。将此内容复制到交互式工具或脚本的源代码中以引用包。
// Install nanoFramework.Iot.Device.AtModem as a Cake Addin
#addin nuget:?package=nanoFramework.Iot.Device.AtModem&version=1.0.216

// Install nanoFramework.Iot.Device.AtModem as a Cake Tool
#tool nuget:?package=nanoFramework.Iot.Device.AtModem&version=1.0.216                

通用的 AT Modem SIM800 和 SIM7070,SIM7080,SIM7090 - 双模无线模块 CatM,LTE 调制解调器

此绑定是一个通用的 AT Modem 处理器,可以扩展以适应不同的使用。第一个实现是针对 Sim7080,它支持 CAT-MNB-IoT。它可以通过串行/UART 接口通过 AT 命令来控制。还有一个 SIM800 的部分实现。

注意:该模块已在 Sim7080 上进行了测试,但与 Sim7070 和 Sim7090 以及 SIM800H 兼容。

该项目由 SIMCom 正式支持。

SIMCom

文档

不同调制解调器支持的功能如下

功能 SIM800 SIM7080 SIM767XX 系列产品
SMS
呼叫
存储
HTTP 和 HTTPS
MQTT 和 MQTTS
IP 网络连接

大多数调制解调器支持其他功能,如MMS、带TCP/UDP的套接字、FTP、电子邮件、GNSS等。到目前为止,这些功能尚未实现。它们可以在以后添加。所有添加这些功能的机制都已经存在,并在下面的文档中特别说明了,尤其是在关于 频道属性 的描述中。

请注意,在 Eclo解决方案GitHub仓库 中提供了SIM800H的更完整的实现。

还应检查每个调制解调器的具体特性。例如,SIM7672支持文件夹,但仅限于SD卡。所有调制解调器在MQTT主题和消息的大小方面都有限制。这些可能成为您应用程序的限制。SIM767XX系列将正常运行。您必须对所有系列使用SIM7672类。

使用方法

重要:确保在创建SerialPort之前,尤其是对于ESP32,正确设置RT/RX引脚,确保安装了nanoFramework.Hardware.ESP32 nuget

SerialPort _serialPort;
OpenSerialPort("COM3");

_serialPort.NewLine = "\r\n";
AtChannel atChannel = AtChannel.Create(_serialPort);
Sim7080 modem = new(atChannel);

private static void OpenSerialPort(
    string port = "COM3",
    int baudRate = 115200,
    Parity parity = Parity.None,
    StopBits stopBits = StopBits.One,
    Handshake handshake = Handshake.None,
    int dataBits = 8,
    int readTimeout = Timeout.Infinite,
    int writeTimeout = Timeout.Infinite)
{
    // This section is specific to ESP32 targets
    // Configure GPIOs 16 and 17 to be used in UART2 (that's refered as COM3)
    Configuration.SetPinFunction(32, DeviceFunction.COM3_RX);
    Configuration.SetPinFunction(33, DeviceFunction.COM3_TX);

    _serialPort = new(port)
    {
        //Set parameters
        BaudRate = baudRate,
        Parity = parity,
        StopBits = stopBits,
        Handshake = handshake,
        DataBits = dataBits,
        ReadTimeout = readTimeout,
        WriteTimeout = writeTimeout
    };

    // Open the serial port
    _serialPort.Open();
}

请注意,每个调制解调器可以支持多个波特率,并具有自动波特率。您可以使用SetBaudRate函数在调制解调器唤醒后设置它。

一些调制解调器,如SIM800,具有用于设置高/低的外部引脚。此绑定不处理此行为。您应使用 GpioController 来调整这些引脚。如果情况需要,也建议要有适当的硬件复位模式。

每个调制解调器都源自ModemBase。它确实提供了通过属性访问不同功能的方法。例如,一旦调制解调器打开,通过FileStorage属性访问文件存储。如果您的调制解调器未实现此功能,则会抛出未实现异常。

所有调制解调器都应该实现ISmsProverICall,因为这些是标准。因此,使用它们应该是安全的。这些SmsProviderCall属性默认实现了一个标准类,它将与标准一起工作。每个调制解调器可以稍后继承并覆盖这些类以添加更多功能,例如。

频道属性

Channel属性,它是一个AtChannel类,允许您在尚未实现的情况下控制和编写自己的功能。该类实现了特定功能,允许发送和接收AT命令,还可以发送原始数据。有一个始终运行的线程不断读取调制解调器的输出,可以根据要求停止和启动。

大多数情况下,您将使用简单的命令,允许您仅发送命令并等待OK、Error或其他特定文本。这是通过以下函数完成的:

// This command is sending a simple command and waiting for OR or ERROR.
// Internally it uses a default timeout. A specific one can be passed as well.
AtResponse response = Channel.SendCommand($"AT+IPR={baudRate}");
if (response.Success)
{
    Console.WriteLine("The command returned OK");
    // You can proceed and do something else
}
else
{
    Console.WriteLine("The command returned ERROR!");
    // You may retry or do anything else
}

您还可以等待获取具有一个或多个行的特定部分响应

AtResponse response = Channel.SendCommandReadSingleLine($"AT+CSCS=?", "+CSCS:");
if (response.Success)
{
    // This means we have read 1 line stating with "+CSCS:"
    // We have ignore all the rest up to a OK
}
else
{
    // In this case, there is ERROR at the end or it does not contains "+CSCS:" or a timeout
}

这是一个读取多个行的示例

AtResponse response = Channel.SendCommandReadMultiline("ATI", null);
// Here, we don't wait for a specific answer, any will be taken up to OK or ERROR or a timeout
if (response.Success)
{
    // This means that there is a OK at the end
    StringBuilder builder = new StringBuilder();
    foreach (string line in response.Intermediates)
    {
        builder.AppendLine(line);
    }

    Console.WriteLine(builder.ToString());
}

作为一个更高级的示例,您可以停止主接收线程,添加自己的逻辑,然后再重新启动它,您可以这样做:

Modem.Channel.Stop();
Modem.Channel.SendBytesWithoutAck(Encoding.UTF8.GetBytes($"AT+SHREAD=0,{_httpActionResult.DataLenght}\r\n"));

// Do a lot of things here like reading manually
var chunk = Modem.Channel.ReadRawBytes(42);

// Then clean the output and restart the receiving thread
Modem.Channel.Clear();
Modem.Channel.Start();

内部存储

每个调制解调器可能包含内部存储。有些只提供基本文件支持,有些可能还包括目录以及或多个驱动器/存储区域。

实现应通过IFileStorage接口完成,并可以通过FileStorage属性访问。某些设备将支持文件夹,这将在HasDirectorySupport属性中指示。

您可以创建、读取文件、删除和重命名它们,并获取文件和存储的大小

const string FileName = "test.txt";
const string Content = "Hello from nanoFramework";

// Available storage
var size = modem.FileStorage.GetAvailableStorage();
Console.WriteLine($"Available storage: {size}");

// Create a file
var respCreate = modem.FileStorage.WriteFile(FileName, Content);
Console.WriteLine($"Create file: {(respCreate ? "success" : "failure")}");

// Get file size
Console.WriteLine($"File size: {modem.FileStorage.GetFileSize(FileName)}");

// Read file
var respRead = modem.FileStorage.ReadFile(FileName);
if (respRead != null && respRead == Content)
{
    Console.WriteLine($"Read file: success");
}
else
{
    if (respRead != null)
    {
        Console.WriteLine($"Read file: failure, content is {respRead}");
    }
    else
    {
        Console.WriteLine($"Read file: failure");
    }
}

bool respDelete = false;

// Rename file
var respRename = modem.FileStorage.RenameFile(FileName, "test2.txt");
if (respRename)
{
    Console.WriteLine($"Rename file: success");
    // Delete file
    respDelete = modem.FileStorage.DeleteFile("test2.txt");
}
else
{
    Console.WriteLine($"Rename file: failure");
    // Delete file
    respDelete = modem.FileStorage.DeleteFile(FileName);
}

Console.WriteLine($"Delete file: {(respDelete ? "success" : "failure")}");

假设我们有目录支持,您可以创建、列出和删除目录

const string DirectoryName = "Test";
const string Content = " nanoFramework!";
// Create a directory
var respCreateDir = modem.FileStorage.CreateDirectory(DirectoryName);
Console.WriteLine($"Create directory: {(respCreateDir ? "success" : "failure")}");

// You can create file in it
res = modem.FileStorage.WriteFile($"{DirectoryName}\\test.txt", Content);
if (!res)
{
    Console.WriteLine($"Create file: failure");
}
else
{
    Console.WriteLine($"Create file: success");
}

// List the directory
var respListDir = modem.FileStorage.ListDirectory(DirectoryName);
if (respListDir != null)
{
    Console.WriteLine($"List directory: success");
    foreach (var file in respListDir)
    {
        Console.WriteLine($"  {file}");
    }
}
else
{
    Console.WriteLine($"List directory: failure");
}

// Delete the directory
var respDeleteDir = modem.FileStorage.DeleteDirectory(DirectoryName);
Console.WriteLine($"Delete directory: {(respDeleteDir ? "success" : "failure")}, failure is normal if the directory is not empty.");

SMS

短信功能几乎完全支持。该实现目前尚不支持UCS2。其余功能均支持。SmsProvider 属性允许访问短信功能。

重要:在发送或接收短信之前,请确保您有合适的网络连接。请参阅网络部分

即使连接可用,您可能也无法使用短信引擎,请使用 IsSmsReady 属性进行检查。

while (true)
{
    if (modem.SmsProvider.IsSmsReady)
    {
        Console.WriteLine($"SMS is ready!");
        break;
    }

    Console.WriteLine($"Waiting for SMS to be ready...");
    Thread.Sleep(1000);
}

您可以通过以下方式列出短信:

var resplistSms = modem.SmsProvider.ListSmss(SmsStatus.ALL);
if (resplistSms.IsSuccess)
{
    Console.WriteLine($"SMS list:");
    foreach (SmsWithIndex sms in (ArrayList)resplistSms.Result)
    {
        Console.WriteLine($"  Sender: {sms.Sender}");
        Console.WriteLine($"  Date: {sms.ReceiveTime}");
        Console.WriteLine($"  Message: {sms.Message}");
        Console.WriteLine($"  Status: {sms.Status}");
        Console.WriteLine($"  Index: {sms.Index}");
    }
}

您可以根据索引读取特定的短信。

// Assuming in this example that the SMS index is 1
var respSmsRead = modem.SmsProvider.ReadSms(1, SmsTextFormat.PDU);
if (respSmsRead.IsSuccess)
{
    Sms sms = (Sms)respSmsRead.Result;
    Console.WriteLine($"SMS read successfully:");
    Console.WriteLine($"  Sender: {sms.Sender}");
    Console.WriteLine($"  Date: {sms.ReceiveTime}");
    Console.WriteLine($"  Message: {sms.Message}");
    Console.WriteLine($"  Status: {sms.Status}");
}
else
{
    Console.WriteLine($"SMS read failed: {respSmsRead.ErrorMessage}");
}

您可以使用文本或PDU格式发送短信。PDU将允许您指定特定的编码(注意:UCS2目前尚不支持)。

ModemResponse respSmsSend;
respSmsSend = modem.SmsProvider.SendSmsInTextFormat(new PhoneNumber("+33664404676"), "Hello from nanoFramework text");
if (respSmsSend.IsSuccess)
{
    Console.WriteLine($"SMS sent successfully: {respSmsSend.Result}");
}
else
{
    Console.WriteLine($"SMS sent failed: {respSmsSend.ErrorMessage}");
}

respSmsSend = modem.SmsProvider.SendSmsInPduFormat(new PhoneNumber("+33664404676"), "Hello from nanoFramework pdu", IoT.Device.AtModem.CodingSchemes.CodingScheme.Gsm7, true);
if (respSmsSend.IsSuccess)
{
    Console.WriteLine($"SMS sent successfully: {respSmsSend.Result}");
}
else
{
    Console.WriteLine($"SMS sent failed: {respSmsSend.ErrorMessage}");
}

网络

您的SIM卡可能带有PIN码。您可以使用 GetSimStatus 函数进行检测。

var pinStatus = modem.GetSimStatus();
if (pinStatus.IsSuccess)
{
    Console.WriteLine($"SIM status: {(SimStatus)pinStatus.Result}");
    // Do we need a pin code?
    if ((SimStatus)pinStatus.Result == SimStatus.PinRequired)
    {
        // Provide the pin code
        var pinRes = modem.EnterSimPin(new PersonalIdentificationNumber("1234"));
        if (pinRes.IsSuccess)
        {
            Console.WriteLine("PIN entered successfully");
        }
        else
        {
            Console.WriteLine("PIN entered failed");
        }
    }
}

根据您的提供商,您可能需要提供APN详情并手动连接到网络。您可以这样做:

var network = modem.Network;
// If you have a pin code, you should pass it
// var connectRes = network.Connect(new PersonalIdentificationNumber("1234"), new AccessPointConfiguration("free"));
var connectRes = network.Connect(apn: new AccessPointConfiguration("orange"));
if (connectRes)
{
    Console.WriteLine($"Connected to network.");
}
else
{
    Console.WriteLine($"Connected to network failed! Trying to reconnect...");
    connectRes = network.Reconnect();
    if (connectRes)
    {
        Console.WriteLine($"Reconnected to network.");
    }
    else
    {
        Console.WriteLine($"Reconnected to network failed!");
    }
}

NetworkInformation networkInformation = network.NetworkInformation;
Console.WriteLine($"Network information:");
Console.WriteLine($"  Operator: {networkInformation.NetworkOperator}");
Console.WriteLine($"  Connextion status: {networkInformation.ConnectionStatus}");
Console.WriteLine($"  IP Address: {networkInformation.IPAddress}");
Console.WriteLine($"  Signal quality RSSI: {networkInformation.SignalQuality.Rssi}");
Console.WriteLine($"  Signal quality BER: {networkInformation.SignalQuality.Ber}");

还有一个高级功能可以等待连接发生。您应该测试与您提供商的工作方式。

// Wait for network registration for 2 minutes max, if not connected, then something is most likely very wrong
var isConnected = modem.WaitForNetworkRegistration(new CancellationTokenSource(120_000).Token);

您还有了解您如何连接到网络的函数。

var networkReg = GetNetworkRegistration();
if (networkReg.IsSuccess)
{
    if ((NetworkRegistration)networkReg.Result == NetworkRegistration.RegisteredHomeNetwork || (NetworkRegistration)networkReg.Result == NetworkRegistration.RegisteredRoaming)
    {
        // We are connected
    }
}

一旦连接到网络,您将有事件提供日期和时间更新,这很方便设置设备的日期和时间。

var network = modem.Network;
network.DateTimeChanged += NetworkDateTimeChanged;

void NetworkDateTimeChanged(object sender, DateTimeEventArgs e)
{
    // Set the native date time
    Rtc.SetSystemTime(e.DateTime);
    Console.WriteLine($"Date and time received, it is now {DateTime.UtcNow}");
}

其他事件可用于相关的连接状态。网络连接是指与运营商的连接,而应用程序网络通常是IP连接。

modem.NetworkConnectionChanged += ModemNetworkConnectionChanged;
// This will set the modem to reconnect when disconnected
modem.Network.AutoReconnect = true;
modem.Network.ApplicationNetworkEvent += NetworkApplicationNetworkEvent;

void ModemNetworkConnectionChanged(object sender, NetworkConnectionEventArgs e)
{
    Console.WriteLine($"Network connection changed to: {e.NetworkRegistration}");
}

void NetworkApplicationNetworkEvent(object sender, ApplicationNetworkEventArgs e)
{
    Console.WriteLine($"Application network event received, connection is: {e.IsConnected}");
}

请注意,即使大多数工作已在调制解调器和代码中完成,您可能仍需处理重新连接机制。

您也可以列出运营商。就如同在手机上这样做一样,这可能会花费几分钟!

var network = modem.Network;

Console.WriteLine("Getting the list of operators, this may take a while, up to 5 minutes...");
// Get the operators
var operators = network.GetOperators();
if (operators != null)
{
    foreach (var op in operators)
    {
        Console.WriteLine($"Operator:");
        Console.WriteLine($"  Name: {op.Name}");
        Console.WriteLine($"  Long name: {op.ShortName}");
        Console.WriteLine($"  Format: {op.Format}");
        Console.WriteLine($"  System Mode: {op.SystemMode}");
    }
}

您还有执行USSD代码、获取电量、信号强度、获取用户号码等功能。

呼叫

您可以使用任何调制解调器管理通话。通过 Call 属性提供了一个通用实现。您可以通过事件通知通话状态并在其上进行操作。

var call = modem.Call;
call.IncomingCall += CallIncomingCall;
call.CallStarted += CallCallStarted;
call.CallEnded += CallCallEnded;
call.MissedCall += CallMissedCall;

// Let's do a call now and being anonymous ;-)
call.Dial(new PhoneNumber("+33123456789"), true);

void CallCallStarted(object sender, CallStartedEventArgs e)
{
    Console.WriteLine("A call has started");
}

void CallMissedCall(object sender, MissedCallEventArgs e)
{
    Console.WriteLine($"Missed call from {e.PhoneNumber} at {e.Time}");
}

void CallIncomingCall(object sender, IncomingCallEventArgs e)
{
    Console.WriteLine($"Incoming!");
    // We have an incoming call, we answer it
    modem.Call.AnswerIncomingCall();
}

void CallCallEnded(object sender, CallEndedEventArgs e)
{
    Console.WriteLine($"Call ended!");
}

注意:为了使用这些功能,您可能需要添加麦克风和扬声器。这些功能可能因您所获得的板而不同,您可能无法在硬件中看到它们。

MQTT和MQTT安全

您可以使用实现 IMqttClient 接口的MQTT客户端。

重要:每个调制解调器可能不支持所有功能,或对主题和负载长度有所限制。请确保您检查限制,特别是在您希望使用Azure或AWS IoT的情况下。

由于接口的原因,您可以使用 MqttClient 客户端属性提供的MQTT客户端,就像您可以与现有的nanoFramework MQTT客户端一样使用它。

例如,您可以使用 Azure IoT完全管理的nuget 并传递MqttClient属性和证书。

// This specific modem only support binary DER certificate format (not PEM):
DeviceClient azureIoT = new DeviceClient(modem.MqttClient, IotBrokerAddress, DeviceID, SasKey, azureCert: Resource.GetBytes(Resource.BinaryResources.DigiCertGlobalRootG2));

由于 MqttClient 使用了此 IMqttClient 接口,您可以在不同实现之间重用几乎相同的代码,尽管它们的连接方式不同,无论是WiFi、以太网还是AT调制解调器。在 Samples 存储库中提供了一个完整的Azure示例。

MQTTS证书验证

仅在您提供证书时才会进行安全MQTT连接的证书验证。如果传递null服务器证书,则将忽略服务器证书验证。

在SIM7070/7080/7090中,支持的唯一证书格式是DER,意味着证书的二进制表示(通常带有.crt扩展名),而不是PEM格式(这是一个base64编码文本表示)。

重要:为了方便,提供了全管理的 X509Certificate 代码实现。但它提供任何获取证书有效期等属性的功能。这只是一个用于简化代码重用的包装器。它既不解析证书,也不转换它。如果您传递一个 PEM 证书,获取到的数据将始终是 PEM 证书,它不会将其转换为 DER 格式,反之亦然。

HTTP 和 HTTPS

纳米框架 System.Net 中提供与 HttpClient 兼容的实现,通过 HttpClient 属性提供。这允许您通过无线网络、以太网和 AT 调制解调器跨不同实现重用代码。

以下是一些获取和发送请求的示例。

var httpClient = modem.HttpClient;
HttpResponseMessage resp;
resp = httpClient.Get("http://www.ellerbach.net/DateHeure/");
Console.WriteLine($"Status should be OK 200: {resp.StatusCode}");
Console.WriteLine($"HTTP GET: {resp.Content?.ReadAsString()}");
Console.WriteLine();

resp = httpClient.Get("http://www.ellerbach.net/DateHeure");
Console.WriteLine($"Status should be MovedPermanently 301: {resp.StatusCode}");
Console.WriteLine($"HTTP GET: {resp.Content?.ReadAsString()}");
Console.WriteLine();

resp = httpClient.Get("http://www.ellerbach.net/DateTime");
Console.WriteLine($"Status should be NotFound 404: {resp.StatusCode}");
Console.WriteLine($"HTTP GET: {resp.Content?.ReadAsString()}");
Console.WriteLine();

resp = httpClient.Get("https://www.ellerbach.net/DateHeure/");
Console.WriteLine($"Status should be OK 200: {resp.StatusCode}");
Console.WriteLine($"HTTP GET: {resp.Content?.ReadAsString()}");
Console.WriteLine();

resp = httpClient.Post("https://httpbin.org/post", new StringContent("{\"title\":\"nano\",\"body\":\"Framework\",\"userId\":101}", System.Text.Encoding.UTF8, "application/json"));
Console.WriteLine($"Status should be OK 200: {resp.StatusCode}");
Console.WriteLine($"HTTP POST: {resp.Content?.ReadAsString()}");
Console.WriteLine();

HTTPS 证书验证

只有提供证书时才会对 HTTPS 安全连接进行证书验证。如果传递空的服务器证书,将忽略服务器证书验证。

在SIM7070/7080/7090中,支持的唯一证书格式是DER,意味着证书的二进制表示(通常带有.crt扩展名),而不是PEM格式(这是一个base64编码文本表示)。

重要:为了方便,提供了全管理的 X509Certificate 代码实现。但它提供任何获取证书有效期等属性的功能。这只是一个用于简化代码重用的包装器。它既不解析证书,也不转换它。如果您传递一个 PEM 证书,获取到的数据将始终是 PEM 证书,它不会将其转换为 DER 格式,反之亦然。

文章

此代码部分是 ATLib 的改编和移植。

产品 兼容和额外的计算目标框架版本。
.NET 框架 net 是兼容的。
兼容的目标框架
包含的目标框架(在包中)
了解更多关于 目标框架.NET 标准的 信息。

NuGet 包

此包没有被任何 NuGet 包使用。

GitHub 仓库 (1)

显示最受欢迎的 1 个依赖 nanoFramework.Iot.Device.AtModem 的 GitHub 仓库

仓库 星标
nanoframework/Samples
🍬 来自纳米框架团队在测试、概念验证和其他探索性尝试中使用的代码示例
版本 下载 最后更新
1.0.216 40 8/9/2024
1.0.206 47 7/31/2024
1.0.204 57 7/26/2024
1.0.193 94 7/17/2024
1.0.176 110 6/19/2024
1.0.173 88 6/14/2024
1.0.151 132 5/15/2024
1.0.145 113 5/1/2024
1.0.139 102 4/15/2024
1.0.117 145 3/22/2024
1.0.97 129 2/28/2024
1.0.95 99 2/27/2024
1.0.86 117 1/31/2024
1.0.82 85 1/27/2024
1.0.80 91 1/26/2024
1.0.77 84 1/24/2024
1.0.65 164 1/5/2024
1.0.61 129 12/20/2023
1.0.39 159 11/10/2023
1.0.19 90 11/8/2023
1.0.10 140 10/11/2023
1.0.6 119 10/6/2023
1.0.1 149 10/2/2023