RadioTest (Ping de Notificación)

La sencilla aplicación de prueba de RadioShuttle (“RadioTest”) está pensada para principiantes. Hay dos dispositivos que se comunican entre sí y comparten un mensaje simple. Un dispositivo se llama Nodo, el otro es una Estación. Con sólo pulsar un botón (botón “A”) se transmite un mensaje desde el nodo a la estación. Esto también funciona al revés, la estación también puede transmitir un mensaje al nodo.

Código de ejemplo de RadioShuttle RadioTest

Este ejemplo está incluido en el producto RadioShuttle, aquí está el código simplificado:

/*
 * The file is Licensed under the Apache License, Version 2.0
 * (c) 2017 Helmut Tschemernjak
 * 30826 Garbsen (Hannover) Germany
 */
  
#include "PinMap.h"
#include <arduino-mbed.h>
#include <sx1276-mbed-hal.h>
#include <RadioShuttle.h>
#include <RadioStatus.h>
#include <RadioSecurity.h>
#include <arduino-util.h>
#include <RTCZero.h>
// #define RADIO_SERVER 1
  
#ifdef RADIO_SERVER
bool server = true;
#else
bool server = false;
#endif
  
enum SensorsIDs { // Must be unique world wide.
  myTempSensorApp = 0x0001,
#ifdef RADIO_SERVER
  myDeviceID = 1,
  myCode = 0x20EE91D6, // station board id and code
  remoteDeviceID = 9,
#else
  myDeviceID = 9,
  myCode = 0x20EE91DE, // node board id and code
  remoteDeviceID = 1,
#endif
};

En la sección anterior se determina qué ID de aplicación y qué números de dispositivo se utilizan por estación y nodo.

/*
 * For details review: SX1276GenericLib/sx1276/sx1276.h
 * Supported spreading factors SF 7,8, 9, 10, 11, (12 does not work well)
 * Working frequencies using the 125000 bandwidth which leaves
 * sufficient distance to the neighbour channel
 * EU: 868.1, 868.3, 868.5 (Default LoRaWAN EU channels)
 * EU: 865.1, 865.3, 865.5, 865.7, 865.9 (additional channels)
 * EU: 866.1, 866.3, 866.5, 866.7, 866.9 (additional channels)
 * EU: 867.1, 867.3, 867.5, 867.7, 867.9 (additional channels)
 * Utilisation of these channels should not exceed 1% per hour per node
 * Bandwidth changes other than 125k requires different channels distances
 */
const RadioShuttle::RadioProfile myProfile[] = {
/*
 * Our default profile
 * frequency, bandwidth, TX power, spreading factor
 */
 { 868100000, 125000, 14, 7 },
 { 0, 0, 0, 0 },
};

Aquí se determinan la frecuencia, la anchura de banda del canal, la potencia de transmisión y el factor de dispersión LoRa. Estos datos deben ser idénticos entre todos los participantes, así que por favor, no cambie nada al realizar una primera prueba!

DigitalOut led(LED);
InterruptIn intr(SW0);
volatile int pressedCount = 0;
  
void SwitchInput(void) {
 dprintf("SwitchInput");
 led = !led;
 pressedCount++;
}
  
Radio *radio; // the LoRa radio modem
RadioShuttle *rs; // the RadioShuttle protocol
RadioStatusInterface *statusIntf; // the status add-on
RadioSecurityInterface *securityIntf; // the security 

Aquí se definen algunas variables globales para el LED y el botón “A”, además de una función llamada “SwitchInput” que se llama después de haber pulsado el botón. InterruptIn y DigitalOut son funciones compatibles con mbed. Por supuesto, las funciones de Arduino también pueden ser usadas.

void setup() {
  MYSERIAL.begin(230400);
  InitSerial(&amp;amp;amp;MYSERIAL, 3000); // wait 2000ms that the Serial Monitor opens, otherwise turn off USB.
  SPI.begin();
  rtc.begin();
  
  dprintf("USBStatus: %s", SerialUSB_active == true ? "SerialUSB_active" : "SerialUSB_disbaled");
  
  led = 1;
  intr.mode(PullUp);
  intr.fall(callback(&amp;amp;SwitchInput));
  dprintf("MyRadioShuttle");
  
  RSCode err;
  RadioStatusInterface *statusIntf = NULL;
  RadioSecurityInterface *securityIntf = NULL;
  
  // RFM95
  radio = new SX1276Generic(NULL, RFM95_SX1276,
        LORA_SPI_MOSI, LORA_SPI_MISO, LORA_SPI_SCLK, LORA_CS, LORA_RESET, LORA_DIO0, LORA_DIO1, LORA_DIO2, LORA_DIO3, LORA_DIO4, LORA_DIO5);
  
  statusIntf = new MyRadioStatus();
  securityIntf = new RadioSecurity();
  rs = new RadioShuttle("MyRadioShuttle");
  err = rs-&amp;gt;AddLicense(myDeviceID, myCode);
  if (err)
    return;
  
  rs->AddRadio(radio, MODEM_LORA, myProfile);
  rs->AddRadioStatus(statusIntf);
  rs->ampAddRadioSecurity(securityIntf);
  rs->RegisterApplication(myTempSensorApp, &amp;amp;amp;TempSensorRecvHandler)
  if (server) {
    err = rs->Startup(RadioShuttle::RS_Station_Basic);
    dprintf("Startup as a Server: Station_Basic ID=%d", myDeviceID);
  } else {
    err = rs->Startup(RadioShuttle::RS_Node_Online);
    dprintf("Startup as a Node: RS_Node_Online ID=%d", myDeviceID);
  }
}

La inicialización mostrada arriba activa el módulo de radio (RFM95) y el software de protocolo RadioShuttle con un indicador de estado “MyRadioStatus”, que hace que los LEDs de recepción y transmisión parpadeen durante la actividad. El nodo y el servidor se iniciarán con la función “Startup”.

void loop() {
  static int cnt = 0;
  
  if (cnt != pressedCount) {
    int flags = RadioShuttle::MF_NeedsConfirm; // optional
    if (server) {
      static char msg[] = "The server feels very good today";
      rs->SendMsg(myTempSensorApp, msg, sizeof(msg), flags, remoteDeviceID);
    } else {
      static char msg[] = "Hello, the temperature is 26 celsius";
      rs->SendMsg(myTempSensorApp, msg, sizeof(msg), flags, remoteDeviceID);
    }
    cnt = pressedCount;
  }
  led = 0;
  sleep() // timer and radio interrupts will wakeup us
  led = 1;
  rs->RunShuttle(); // process all pending events
}

El bucle principal comprueba si se ha pulsado el botón y envía un mensaje a la estación homóloga. La función dormir detiene la aplicación hasta que aparece un nuevo mensaje, es decir, cuando se pulsa el botón o se interrumpe el sueño mediante otra actividad. En caso de actividad, el LED se enciende, en caso de actividad elevada (p. ej. una ventana USB “Monitor Serie” abierta) el LED parpadea muy rápidamente (lo que casi parece una luz permanente). Con el enchufe USB desconectado y un reinicio (Reset), el LED debe apagarse y sólo parpadear cuando hay actividad.

El código de ejemplo de RadioShuttle incluye una variante de deepsleep(), que tiene el USB desactivado y por lo tanto ahorra una cantidad sustancial de energía.

void TempSensorRecvHandler(int AppID, RadioShuttle::devid_t stationID, int msgID, int status, void *buffer, int length)
{
  switch(status) {
  case RadioShuttle::MS_SentCompleted: // A SendMsg has been sent.
    dprintf("MSG_SentCompleted: id=%d %d bytes", msgID, length);
    break;
  case RadioShuttle::MS_SentCompletedConfirmed:// A SendMsg has been sent and confirmed
    dprintf("MSG_SentCompletedConfirmed: id=%d %d bytes", msgID, length);
    break;
  case RadioShuttle::MS_SentTimeout: // A timeout occurred, number of retires exceeded
    dprintf("MSG_SentTimeout ID: %d", msgID);
   break;
  case RadioShuttle::MS_RecvData: // a simple input message
    dprintf("MSG_RecvData ID: %d, len=%d", msgID, length);
    // dump("MSG_RecvData", buffer, length);
    break;
  case RadioShuttle::MS_RecvDataConfirmed: // received a confirmed message
    dprintf("MSG_RecvDataConfirmed ID: %d, len=%d", msgID, length);
    // dump("MSG_RecvDataConfirmed", buffer, length);
    break;
  case RadioShuttle::MS_NoStationFound:
    dprintf("MSG_NoStationFound");
    break;
  case RadioShuttle::MS_NoStationSupportsApp:
    dprintf("MSG_NoStationSupportsApp");
    break;
  case RadioShuttle::MS_AuthenicationRequired: // the password does not match.
    dprintf("MSG_AuthenicationRequired");
    break;
  case RadioShuttle::MS_StationConnected: // a confirmation that the connection was accepted
    dprintf("MSG_StationConnected");
    break;
  case RadioShuttle::MS_StationDisconnected: // a confirmation that the disconnect was accepted
    dprintf("MSG_StationDisconnected");
    break;
  default:
    break;
  }
}

Se llama al Receive Handler cada vez que se ha enviado un mensaje, o cuando llega un nuevo mensaje, o cuando se ha producido un error. Dependiendo de la aplicación, se pueden definir varios Receive Handlers, uno por cada ID de aplicación.

Recepción de mensajes

En general, se confirma la transmisión de un mensaje si el indicador RadioShuttle::MF_NeedsConfirm está activado en la función SendMsg. De esta manera, el nodo que envía el mensaje es notificado de la transmisión completada sólo cuando la estación homóloga ha recibido los datos. Esto evita que el mensaje se reciba sólo parcialmente o que no se reciba en absoluto.

Autorización por contraseña

Se puede almacenar una contraseña para cada ID de aplicación de RadioShuttle. Esto evita que la estación acepte mensajes si la contraseña entre el nodo y la estación no es idéntica. Se almacena una contraseña para el código de ejemplo original (por favor, cambie su propia contraseña).

bool usePassword = true; // password the can be used independently of AES
unsigned char samplePassword[] = { "RadioShuttleFly-PleaseChange" };

La contraseña garantiza que los dispositivos externos no puedan comunicarse con sus dispositivos. Se almacena en una base de identificación por aplicación, lo que permite usar una aplicación públicamente, mientras que otras aplicaciones pueden estar protegidas por contraseña. Las contraseñas no tienen una sobrecarga de protocolo porque sólo necesitan ser comprobadas en el momento del contacto inicial entre el nodo y la estación. La documentación de la Estrategia de Aplicaciones contiene información de fondo adicional relacionada con diferentes aplicaciones.

Las contraseñas no se transmiten a través de la red, por lo que no se pueden robar. En lugar de una contraseña, un gran número aleatorio, junto con la contraseña, se convierte en un código hash de 256 bits SHA. Sólo se transmite este código hash, que cambia con cada inicio de sesión, por ejemplo, al reiniciar un dispositivo.

Cifrado de mensajes AES de 128 bits

Una vez que se ha almacenado una contraseña, se puede activar el cifrado AES.

bool useAES = true; // AES needs the usePassword option on
int flags = RadioShuttle::MF_Encrypted;

AES cifra todos los datos de la aplicación mediante el uso de la contraseña, de modo que ningún participante de la red que registre la comunicación pueda ver el contenido del mensaje. Además, los mensajes falsos no funcionan con AES y se rechazan.

Para los propósitos de RadioShuttle AES128-bit es más que suficiente porque los intentos de 340282366920938463463374604607431768211456 (2128) consumen mucho tiempo y no son realistas. La contraseña de RadioShuttle y el cifrado AES están incluidos en el módulo “RadioSecurityInterface” separado y pueden ser ajustados o cambiados para otros requisitos.

A pesar de todos sus beneficios, el cifrado AES tiene una gran desventaja: AES128-bit tiene una longitud mínima de bloque de 16 bytes por mensaje que necesita ser transmitido. Con SF7 esto toma aproximadamente 100 milisegundos además, con SF11 es tanto como medio segundo.

El cifrado AES se puede definir por mensaje. La marca RadioShuttle::MF_Encrypted en el “SendMsg” debe estar configurado para permitir el cifrado AES del mensaje. El código de ejemplo de RadioTest ya viene configurado de esa manera.

Registro del paquete de red RadioShuttle

El ejemplo RadioTest activa el registro de transacciones de red y operaciones internas. Esto puede ser útil para verificar lo que está sucediendo actualmente. La desactivación de la función “EnablePacketTrace” desactiva el registro.

Función de registro por dprintf y dump

La función “dprintf” permite una salida compatible con “C printf”, con un cronomarcador por línea de salida así como la adición automática de marcas de final de línea.

“dump” permite datos de volcado hexadecimal incluyendo el nombre, la dirección y una longitud. En la aplicación RadioTest se incluyen ejemplos.

Documentación de la API de RadioShuttle

El archivo “RadioShuttle.h” contiene la documentación completa de la API del protocolo RadioShuttle. Además, el archivo “RadioShuttleProtocol.rtf” documenta los conceptos subyacentes del protocolo RadioShuttle.