Worldwide LoRa coverage with some clever tricks

I used the RYLR896 modules and RYC1001 MQTT IoT service from Reyax to create a device that can be controlled through LoRa from anywhere in the world.
Sep 10, 2024 — 10 mins read — Remote Control

Worldwide LoRa coverage with some clever tricks

LoRa is the de facto standard for controlling devices over long distances but no matter the modules used and the antennas, there is always a limit on how far a certain device can be controlled.

My friends from Reyax sent me a couple of their RYLR986 modules to test out and in combination with their RYC1001 MQTT IoT service, I created a setup where we can control a device through LoRa from anywhere in the world.



Principle of Operation

To achieve worldwide control for the end node, I created three devices, where two of them deal with LoRa, and the third one that we control is only connected through WiFi. The trick lies in the second device, which connects both to LoRa and to the RYC1001 MQTT cloud service so that it can act as a gateway.

This gateway listens for LoRa messages coming in from the first device that is the trigger and sends them over to the MQTT service. The end node is subscribed to the particular MQTT topic that the gateway sends the data at, and based on what is received it triggers the relay on or off.


Overview of the RYLR896 module

The RYLR896 module is a robust and highly efficient LoRa transceiver designed for long-range, low-power wireless communication in the 868/915 MHz frequency bands. Certified by both the NCC and FCC, this module boasts a range of features making it ideal for diverse IoT applications.

The RYLR896 module is suitable for various applications, including smart agriculture, industrial monitoring, and remote sensing. Its ability to provide long-range communication while maintaining low power consumption is particularly beneficial for battery-operated devices in remote locations.

By leveraging the capabilities of the RYLR896 module, developers can design IoT solutions that require reliable, long-range wireless communication with minimal infrastructure investment, making it an excellent choice for both new projects and upgrading existing systems.

Overview of the RYC1001 Service

The RYC1001 service is a powerful MQTT IoT cloud platform designed to facilitate easy and efficient connections for low-data-volume and power-saving devices. Built on a stable AWS infrastructure, this platform provides a reliable and scalable solution for managing IoT devices and data transmission. 

The RYC1001 service is tailored for IoT applications that require minimal data usage and efficient power management. By leveraging the MQTT protocol, it ensures efficient and reliable data transmission between devices and the cloud. The platform supports monitoring and control of end devices, making it easy to establish and manage your IoT connections.

Whether you are working on smart home systems, industrial automation, or remote sensing, the RYC1001 service offers a robust and flexible cloud solution. Its ability to work seamlessly across different platforms and its support for numerous IoT communication modules make it a versatile choice for developers.


Device Setup

The RYLR896 modules are connected to the NodeMCU microcontrollers on pins 14 and 12 so that they can be interacted with over UART. TX from the module connects to D5 on the NodeMCU and RX from the module is connected to pin D6.

For power, the modules are connected with VCC to 3.3V on the NodeMCU, and GND is connected to GND.

We don't have any additional components on the transmitter device as well as on the receiver/gateway device, but we will use the built-in Flash button on the NodeMCU as a trigger since it is connected to digital input 0.

The device that we will control remotely has a relay connected to it via a transistor (2N2222). The transistor acts as a switch so it can trigger the relay with 5V to reliably turn it on or off. The relay IN pin is connected to 5V via a 10k resistor and the transistor collector. The transistor base is connected to pin D2 via a 1k resistor and the transistor emitter is connected to GND.

With this setup, we are converting the 3.3V signal coming in from the NodeMCU to a 5V signal suitable for the 5V relay.

On the links below, you can find the mentioned components as well as some of the tools I use in my projects. If you decide to buy through them, you are supporting the channel without any additional cost to you!


Code

All of the devices are programmed with the Arduino IDE and the full code used in the example is available below.

Transmitter

#include <SoftwareSerial.h>

#define RXD2 14
#define TXD2 12

SoftwareSerial mySerial(RXD2, TXD2);

#define TARGET_ID 1

String Message = "Hello from Dev2";
String content = "";
int counter = 0;

void setup()
{
  Serial.begin(115200);
  mySerial.begin(115200);
  delay(1000);
  Serial.println("Starting transmitter module");
  pinMode(0, INPUT_PULLUP);
  pinMode(2, OUTPUT);
  digitalWrite(2, HIGH);

  writeToModule("AT+BAND=868500000");
  delay(500);
  writeToModule("AT+NETWORKID=6");
  delay(500);
  writeToModule("AT+ADDRESS=2");
  delay(500);
}

void loop() {
  if (Serial.available()){
    Serial.println("Writing");
    content = Serial.readString();
    content.trim();
    Serial.println();
    content = content + "\r\n";
    char* bufc = (char*) malloc(sizeof(char) * content.length() + 1);
    content.toCharArray(bufc, content.length() + 1);
    mySerial.write(bufc);
    free(bufc);
  }

  if (mySerial.available()) {
    String incomming = mySerial.readString();
    if (incomming.length() <= 10)
      Serial.println(incomming);
    else {
      String channel_ID = incomming.substring(incomming.indexOf('=') + 1, incomming.indexOf(','));
      Serial.println("Channel ID : " + channel_ID);

      String str = incomming.substring(incomming.indexOf(',') + 1);

      String msgLength = str.substring(0, str.indexOf(','));
      Serial.println("Message Length : " + msgLength);

      String str2 = str.substring(str.indexOf(',') + 1);

      String message = str2.substring(0, str2.indexOf(','));
      Serial.println("Message : " + message);
      
      digitalWrite(2, LOW);
      delay(2000);
      digitalWrite(2, HIGH);
    }
  }

  // When the button is pressed send the message to other module
  if (digitalRead(0) == LOW) {
    delay(1000);
    String data = Message + " - Count: " + counter;
    sendLoraData(data, TARGET_ID);
    //increase counter on each send
    counter++;
  }
}

void sendLoraData(String data, int address) {
  String myString = "AT+SEND=" + String(address) + "," + String(data.length()) + "," + data + "\r\n";
  char* buf = (char*) malloc(sizeof(char) * myString.length() + 1);
  Serial.println(myString);
  myString.toCharArray(buf, myString.length() + 1);
  mySerial.write(buf);
  free(buf);
}

void writeToModule(String content) {
  Serial.println("Writing");
  content.trim();
  content = content + "\r\n";
  char* bufc = (char*) malloc(sizeof(char) * content.length() + 1);
  content.toCharArray(bufc, content.length() + 1);
  mySerial.write(bufc);
  free(bufc);
}


Receiver/Gateway

#include <SoftwareSerial.h>
#include <PubSubClient.h>
#include <ESP8266WiFi.h>
#include "secrets.h"


#define RXD2 14
#define TXD2 12

SoftwareSerial mySerial(RXD2, TXD2);

WiFiClient wifiClient;

const char broker[] = "iot.reyax.com";
int        port     = 1883;
const char topic[]  = "lora_to_mqtt_test";

PubSubClient MQTTclient(broker, port, wifiClient);

String Message = "Hello from Dev1";
String content = "";
int counter = 0;
bool toogle_value = false;

void setup()
{
  Serial.begin(115200);
  mySerial.begin(115200);
  delay(1000);
  Serial.println("Hello");
  pinMode(0, INPUT_PULLUP);
  pinMode(2, OUTPUT);
  digitalWrite(2, HIGH);

  WiFi.begin(SECRET_SSID, SECRET_PASS);             // Connect to the network
  Serial.print("Connecting to ");
  Serial.print(SECRET_SSID); Serial.println(" ...");

  int i = 0;
  while (WiFi.status() != WL_CONNECTED) { // Wait for the Wi-Fi to connect
    delay(1000);
    Serial.print(++i); Serial.print(' ');
  }

  Serial.println('\n');
  Serial.println("Connection established!");  
  Serial.print("IP address:\t");
  Serial.println(WiFi.localIP()); 

  Serial.println("Trying to connect to MQTT");
  while ( !MQTTclient.connected() )
  {
    MQTTclient.connect( MQTT_DEVICE_ID, MQTT_USERNAME, MQTT_PASSWORD );
    Serial.print(".");
    delay(500);
  }

  Serial.println("MQTT Connected");

  writeToModule("AT+BAND=868500000");
  delay(500);
  checkSerial();
  writeToModule("AT+NETWORKID=6");
  delay(500);
  checkSerial();
  writeToModule("AT+ADDRESS=1");
  delay(500);
  checkSerial();

}

void loop() {
  if (Serial.available()){  
    writeToModule(Serial.readString());
  }

  checkSerial();

  // When the button is pressed send the message to other module
  if (digitalRead(0) == LOW) {
    delay(1000);
    String data = Message + " - Count: " + counter;
    sendLoraData(data, 2);
    //increase counter on each send
    counter++;
  }
  MQTTclient.loop();
}

void checkSerial() {
  if (mySerial.available()) {
    String incomming = mySerial.readString();
    if (incomming.length() <= 10)
      Serial.println(incomming);
    else {
      String channel_ID = incomming.substring(incomming.indexOf('=') + 1, incomming.indexOf(','));
      Serial.println("Channel ID : " + channel_ID);

      String str = incomming.substring(incomming.indexOf(',') + 1);

      String msgLength = str.substring(0, str.indexOf(','));
      Serial.println("Message Length : " + msgLength);

      String str2 = str.substring(str.indexOf(',') + 1);

      String message = str2.substring(0, str2.indexOf(','));
      Serial.println("Message : " + message);

      toogle_value = !toogle_value;
      Serial.print("Sending data to MQTT: ");
      Serial.println(toogle_value ? "1" : "0");
      MQTTclient.publish(topic, toogle_value ? "1" : "0");

      //send confirmation message
      content = "Message received: " + message;
      sendLoraData(content, channel_ID.toInt());
    }
  }
}

void sendLoraData(String data, int address) {
  String myString = "AT+SEND=" + String(address) + "," + String(data.length()) + "," + data + "\r\n";
  char* buf = (char*) malloc(sizeof(char) * myString.length() + 1);
  Serial.println(myString);
  myString.toCharArray(buf, myString.length() + 1);
  mySerial.write(buf);
  free(buf);
}

void writeToModule(String content) {
  Serial.println("Writing");
  content.trim();
  content = content + "\r\n";
  char* bufc = (char*) malloc(sizeof(char) * content.length() + 1);
  content.toCharArray(bufc, content.length() + 1);
  mySerial.write(bufc);
  free(bufc);
}


Relay

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include "secrets.h"

// Update these with values suitable for your network.

const char* ssid = WIFI_SSID;
const char* password = WIFI_PASS;
const char* mqtt_server = "iot.reyax.com";

WiFiClient espClient;
PubSubClient client(espClient);

#define RELAY_PIN D2

void setup_wifi() {

  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  randomSeed(micros());

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();

  Serial.print("Payload: ");
  Serial.println((char)payload[0]);
  if ((char)payload[0] == '0') {
    Serial.println("Setting relay pin low");
    digitalWrite(RELAY_PIN, LOW);
  } else {
    Serial.println("Setting relay pin high");
    digitalWrite(RELAY_PIN, HIGH);
  }
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect(MQTT_DEVICE_ID, MQTT_USERNAME, MQTT_PASSWORD)) {
      // ... and subscribe
      Serial.println("Connected to MQTT");
      client.subscribe("lora_to_mqtt_test");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void setup() {
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, LOW);
  Serial.begin(115200);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
}

void loop() {

  if (!client.connected()) {
    reconnect();
  }
  client.loop();
}

The secrets.h file on all examples contains the credentials for either WiFi or the MQTT service and it can be created with your particular defined values in the following format:

#define SECRET_SSID "YouWiFiName"
#define SECRET_PASS "your-password"
...


Are we cheating?

Now, I'm sure that someone will state that we are cheating and that we do not really have worldwide LoRa coverage directly for the devices and that is true. However, in a case if we have two gateways, one at the transmitting end and another on the receiving, we will have the trigger signal being initially transmitted via LoRa, handled by the MQTT gateways in between, and finally sent via LoRa once again to the end node. In this case, the gateway are just mediators so that we can extend the physical boundaries that LoRa or any other communication technology has.

Additionally, some can say that we can achieve the same with LoRaWAN alone, without using the cloud MQTT service and while that is possible, it will be significantly more expensive to provide the LoraWAN gateway if they are not present in the area. Our gateways are orders of magnitude cheaper compared to LoRaWAN so even if there is no coverage we can easily deploy them and establish it.


Next steps

I hope that with this example I managed to intrigue you with what is possible and to expand your view on how you can combine technologies and services to achieve your goal. I've seen it way too often that people get stuck with using just one technology, not even considering mixing them up to get the best of both worlds.

I’d love to hear your thoughts on this project. If you have any questions or suggestions, please leave them in the comments below or in the video comments on YouTube. Your feedback helps in refining and advancing these projects further.

Finally, don’t forget to like the video, subscribe for more exciting and informative projects, and stay tuned for our future projects.

LoRa MQTT esp8266 nodemcu
Read next

KRUPS Coffee Machine Repair with No Power Issue

A friend gave me this machine to check out, but it had a no-power issue after being held in storage for an extended period. Since the issue...

You might also enojy this

MQ7 Carbon Monoxide Sensor done the right way!

There are a ton of tutorials on how to use the MQ7 Carbon Monoxide sensor with Arduino, Raspberry Pis, and other microcontrollers but they a...