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
<PackageReference Include="nanoFramework.Iot.Device.AtModem" Version="1.0.216" />
paket add nanoFramework.Iot.Device.AtModem --version 1.0.216
#r "nuget: nanoFramework.Iot.Device.AtModem, 1.0.216"
// 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-M
和 NB-IoT
。它可以通过串行/UART 接口通过 AT 命令来控制。还有一个 SIM800
的部分实现。
注意:该模块已在
Sim7080
上进行了测试,但与 Sim7070 和 Sim7090 以及SIM800H
兼容。
该项目由 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
属性访问文件存储。如果您的调制解调器未实现此功能,则会抛出未实现异常。
所有调制解调器都应该实现ISmsProver
和ICall
,因为这些是标准。因此,使用它们应该是安全的。这些SmsProvider
和Call
属性默认实现了一个标准类,它将与标准一起工作。每个调制解调器可以稍后继承并覆盖这些类以添加更多功能,例如。
频道属性
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 的改编和移植。
-
- nanoFramework.CoreLibrary (>= 1.15.5)
- nanoFramework.M2Mqtt.Core (>= 5.1.138)
- nanoFramework.Runtime.Native (>= 1.6.12)
- nanoFramework.System.Collections (>= 1.5.31)
- nanoFramework.System.IO.Ports (>= 1.1.86)
- nanoFramework.System.Math (>= 1.5.43)
- nanoFramework.System.Threading (>= 1.1.32)
- UnitsNet.nanoFramework.ElectricPotential (>= 5.56.0)
- UnitsNet.nanoFramework.Ratio (>= 5.56.0)
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 |