为了控制 Arduino长距离通信教程–LoRenz 开发板中构建的LoRenz开发板,我开发了LoRaLib——用于SX1278芯片的开源Arduino库。这个库从零开始设计,目的只有一个:制作易于使用的API,即使是初学者也可以实现LoRa通信。该库的目标是使远程通信与串行通信一样简单。

软件

  • Arduino IDE
  • LoRaLib Arduino 库 (可在 GitHub 上获得)

LoRaLib 库

SX1278有多种不同设置,允许用户完全自定义范围、数据速率和功耗,但是最重要的三个设置如下所示:

  • 带宽 SX1278允许的带宽设置为7.8 kHz至500 kHz。带宽值越高,数据传输越快。然而,这是以降低总灵敏度为代价的,因此降低了最大范围。

  • 扩频因子 在LoRa调制中,每个信息位由多个啁啾表示。扩频因子是指每位数据有多少啁啾。SX1278支持7种不同的设置,扩频因子越高,数据传输越慢,范围越大。

  • 编码速率 为了提高传输的稳定性,SX1278可以执行错误检查功能。此错误检查的度量称为编码速率,可以设定四个值。编码速率设为最低的4/5时,传输不太稳定,速度稍快。编码速率设为最高的4/8时,链路更可靠,但代价是数据传输速率较慢。

库的默认设置为:带宽为500 kHz、编码速率为4/5和扩频因子为12。这些设置是范围、稳定性和数据速率之间的合理平衡。当然,这些设置可以通过函数随时更改。

该库内置数据包类和寻址系统。地址长度为8字节,那么最大的寻址数量就是1.8千亿亿(1.8 × 10^19)。这个数值大的离谱。相比之下,NASA估计我们银河系中的恒星数量仅为“4亿”(4×10^11)。每个数据包由源地址、目标地址和最多240字节的有效负载组成。当然,该库还提供了几种读取和写入分组数据的方法。

让我们来看一下使用这个库是多么容易。假设我们有两个带有SX1278模块的LoRenz开发板。它们相距几百米,所以我们可以使用默认设置。首先,我们必须包含库头文件。然后,我们用默认设置创建 LoRa 类的一个实例,用目标地址和消息创建 packet 类的一个实例。源地址由库自动生成并写入Arduino EEPROM。要检查所有内容是否已正确保存,我们会读取数据包信息并将其打印到串行端口。接下来,我们只需调用 tx() 函数即可。一会儿之后……完成!只需一个命令,我们的数据包就传送成功了!
  1. // include the library
  2. #include <LoRaLib.h>
  3. // create instance of LoRa class with default settings
  4. LoRa lora;
  5. // create instance of packet class
  6. // destination: "20:05:55:FE:E1:92:8B:95"
  7. // data:        "Hello World !"
  8. packet pack("20:05:55:FE:E1:92:8B:95", "Hello World!");
  9. void setup() {
  10.   Serial.begin(9600);
  11.   // initialize the LoRa module with default settings
  12.   lora.init();
  13.   // create a string to store the packet information
  14.   char str[24];
  15.   // print the source of the packet
  16.   pack.getSourceStr(str);
  17.   Serial.println(str);
  18.   // print the destination of the packet
  19.   pack.getDestinationStr(str);
  20.   Serial.println(str);
  21.   // print the length of the packet
  22.   Serial.println(pack.length);
  23.   
  24.   // print the data of the packet
  25.   Serial.println(pack.data);
  26. }
  27. void loop() {
  28.   Serial.print("Sending packet ");
  29.   // start transmitting the packet
  30.   uint8_t state = lora.tx(pack);
  31.   
  32.   if(state == 0) {
  33.     // if the function returned 0, a packet was successfully transmitted
  34.     Serial.println(" success!");
  35.    
  36.   } else if(state == 1) {
  37.     // if the function returned 1, the packet was longer than 256 bytes
  38.     Serial.println(" too long!");
  39.    
  40.   }
  41.   // wait a second before transmitting again
  42.   delay(1000);
  43. }
当然,我们需要第二套配有LoRenz 开发板的Arduino来接收该数据包。 系统设置不变,只是这次我们调用 rx() 函数,然后打印接收到的数据包。此函数将等待数据包,如果数据没有在某个时间内到达,该函数将超时,以便您的代码不会完全挂起。该库甚至还会检查传输的数据包是否已损坏,如果是,则将其丢弃。
  1. // include the library
  2. #include <LoRaLib.h>
  3. // create instances of LoRa and packet classes with default settings
  4. LoRa lora;
  5. packet pack;
  6. void setup() {
  7.   Serial.begin(9600);
  8.   // initialize the LoRa module with default settings
  9.   lora.init();
  10. }
  11. void loop() {
  12.   Serial.print("Waiting for incoming transmission ... ");
  13.   
  14.   // start receiving single packet
  15.   uint8_t state = lora.rx(pack);
  16.   if(state == 0) {
  17.     // if the function returned 0, a packet was successfully received
  18.     Serial.println("success!");
  19.     // create a string to store the packet information
  20.     char str[24];
  21.     // print the source of the packet
  22.     pack.getSourceStr(str);
  23.     Serial.println(str);
  24.     // print the destination of the packet
  25.     pack.getDestinationStr(str);
  26.     Serial.println(str);
  27.     // print the length of the packet
  28.     Serial.println(pack.length);
  29.     // print the data of the packet
  30.     Serial.println(pack.data);
  31.    
  32.   } else if(state == 1) {
  33.     // if the function returned 1, no packet was received before timeout
  34.     Serial.println("timeout!");
  35.    
  36.   } else if(state == 2) {
  37.     // if the function returned 2, a packet was received, but is malformed
  38.     Serial.println("CRC error!");
  39.    
  40.   }
  41.   
  42. }
当然,这只是最基本的例子。库本身可以做更多事情,而且我还在继续开发更多的功能。有关该库和所有其他功能的更深入信息,请参阅我的 GitHub 以及那里托管的文档。


Arduino 加密

本文结束之前,我还想讨论一下Arduino的加密。我在上一篇文章中提到了这个问题。现在,我们发送的所有数据都是未加密的。这意味着拥有相同配置、使用相同模块和相同设置的任何人都能拦截和阅读我们的消息。攻击者甚至可以发送自己的消息,而我们却无法分辨。显然,这并不安全。

最简单的解决方案就是使用某种加密。具体地,我决定使用 Rijndael 密码。没听说过吧?这是因为这个名字是荷兰语,因此不好记忆和发音。密码本身实际上非常普遍,但名称更加引人注目:AES。它是一种对称密码,可在加密速度和安全性之间提供出色的平衡。此外,Arduino还提供了几个AES库!本项目使用的库是Davy Landman开发的AESLib(可从 GitHub 上获得)。

如上所述,AES是一种对称密码 – 这意味着它使用相同的密钥来加密和解密消息。现在,我们只有两个设备,因此将密钥硬编码到Arduino中非常容易。当然,如果我们想要动态添加更多设备并创建某种无线网络,我们必须以某种方式实现安全密钥交换,例如使用Diffie-Hellman交换。但是我们现在不会深入这个领域,我们只需将密钥硬编码到我们的Arduino程序中即可。

那么我们应该如何修改上一章的代码呢?修改并不多,说实话,我们只需添加密钥以及一个加密或解密数据包中的数据。这是发射机部分,加密通过 aes128_enc_single() 函数完成。
  1. // include the libraries
  2. #include <LoRaLib.h>
  3. #include <AESLib.h>
  4. // create instance of LoRa class with default settings
  5. LoRa lora;
  6. // create instance of packet class
  7. // destination: "20:05:55:FE:E1:92:8B:95"
  8. // data:        "Hello World !"
  9. packet pack("20:05:55:FE:E1:92:8B:95", "Hello World!   ");
  10. // our secret 16-byte long key
  11. uint8_t key[] = {0x2C, 0x66, 0x54, 0x94, 0xE3, 0xAE, 0xC7, 0x32,
  12.                  0xC4, 0x66, 0xC8, 0xBE, 0xF3, 0x71, 0x22, 0x36};
  13. void setup() {
  14.   Serial.begin(9600);
  15.   // initialize the LoRa module with default settings
  16.   lora.init();
  17.   // create strings to store the packet information
  18.   char src[24];
  19.   char dest[24];
  20.   
  21.   // print the source of the packet
  22.   pack.getSourceStr(src);
  23.   Serial.print("Source:\t\t\t");
  24.   Serial.println(src);
  25.   // print the destination of the packet
  26.   pack.getDestinationStr(dest);
  27.   Serial.print("Destination:\t\t");
  28.   Serial.println(dest);
  29.   // print the length of the packet
  30.   Serial.print("Total # of bytes:\t");
  31.   Serial.println(pack.length);
  32.   // print the contents of unencrypted packet
  33.   Serial.println("-------- Plain text ---------");
  34.   Serial.println(pack.data);
  35.   // encrypt the data
  36.   aes128_enc_single(key, pack.data);
  37.   // print the contents of encrypted packet
  38.   Serial.println("--- Encrypted with AES128 ---");
  39.   Serial.println(pack.data);
  40. }
  41. void loop() {
  42.   Serial.print("Sending packet ");
  43.   // start transmitting the packet
  44.   uint8_t state = lora.tx(pack);
  45.   
  46.   if(state == 0) {
  47.     // if the function returned 0, a packet was successfully transmitted
  48.     Serial.println(" success!");
  49.    
  50.   } else if(state == 1) {
  51.     // if the function returned 1, the packet was longer than 256 bytes
  52.     Serial.println(" too long!");
  53.    
  54.   }
  55.   // wait a second before transmitting again
  56.   delay(1000);
  57. }
接收机部分如下所示,解密通过相同密钥和函数 aes128_dec_single() 完成。
  1. // include the libraries
  2. #include <LoRaLib.h>
  3. #include <AESLib.h>
  4. // create instances of LoRa and packet classes with default settings
  5. LoRa lora;
  6. packet pack;
  7. // our secret 16-byte long key
  8. uint8_t key[] = {0x2C, 0x66, 0x54, 0x94, 0xE3, 0xAE, 0xC7, 0x32,
  9.                  0xC4, 0x66, 0xC8, 0xBE, 0xF3, 0x71, 0x22, 0x36};
  10. void setup() {
  11.   Serial.begin(9600);
  12.   // initialize the LoRa module with default settings
  13.   lora.init();
  14. }
  15. void loop() {
  16.   Serial.print("Waiting for incoming transmission ... ");
  17.   
  18.   // start receiving single packet
  19.   uint8_t state = lora.rx(pack);
  20.   if(state == 0) {
  21.     // if the function returned 0, a packet was successfully received
  22.     Serial.println("success!");
  23.     // create strings to store the packet information
  24.     char src[24];
  25.     char dest[24];
  26.    
  27.     // print the source of the packet
  28.     pack.getSourceStr(src);
  29.     Serial.print("Source:\t\t\t");
  30.     Serial.println(src);
  31.   
  32.     // print the destination of the packet
  33.     pack.getDestinationStr(dest);
  34.     Serial.print("Destination:\t\t");
  35.     Serial.println(dest);
  36.   
  37.     // print the length of the packet
  38.     Serial.print("Total # of bytes:\t");
  39.     Serial.println(pack.length);
  40.     // print the contents of encrypted packet
  41.     Serial.print("Encrypted (AES128):\t");
  42.     Serial.println(pack.data);
  43.   
  44.     // decrypt the data
  45.     aes128_dec_single(key, pack.data);
  46.   
  47.     // print the contents of unencrypted packet
  48.     Serial.print("Plain text:\t\t");
  49.     Serial.println(pack.data);
  50.    
  51.   } else if(state == 1) {
  52.     // if the function returned 1, no packet was received before timeout
  53.     Serial.println("timeout!");
  54.    
  55.   } else if(state == 2) {
  56.     // if the function returned 2, a packet was received, but is malformed
  57.     Serial.println("CRC error!");
  58.    
  59.   }
  60.   
  61. }
使用了密钥之后,我们的消息现在是安全的。如果有人偷听我们的谈话,他无法看到除地址之外的任何内容,每个数据包中都是240字节的乱码。同样,如果攻击者试图传输他自己的消息,我们会立即知道,因为他传输的消息不会加密。

来源:techclass.rohm