Decimals, strings, and LoRa

I’m looking for some help regarding sending sensor data as a string over a LoRa network. I’ve been working on adapting a sketch I found on line that will allow me to send multiple sensors with one node over a LoRa network.

https://www.electroniclinic.com/multiple-sensors-monitoring-with-arduino-lora-nodes-sx1278-lora/

It’s a nice project and works fine with the three sensors used in the sketch. I’m trying to adapt it for use on my cottage’s well and tanks. I had no trouble adding an ultrasonic sensor. I also wanted to monitor the voltage of the battery powering the system. Again no trouble building that with a couple of resistors. The voltage sensor works fine at the sensor end. For instance giving me a reading of 8.63 volts. The problem comes when I send it through the LoRa to the master. The result at that end is a single digit reading, for instance, the previous reading is upscaled to 9 volts. Even changing from an int to a float, all I get is 9.00 volts. I think the problem is sending the processed data as a string).
I’ve thought about sending just the raw data (analogInput) through to the master and processing it there. But there must be a simpler way.
Any ideas?

I've stripped the other two sensors out of the original program to make it easier(I think ) to troubleshoot.

/*
  Lora Node 1

*/
#include <SPI.h>              // include libraries
#include <LoRa.h>
#include <OneWire.h>

int analogInput = A1;
float vout = 0.0;
float vin =0.0;
float R1 = 30000.0;
float R2 = 7500.0;
float PinFloat = A1 *5.0/1023.0;
int value = 0;

String outgoing;              // outgoing message

byte msgCount = 0;            // count of outgoing messages
byte MasterNode = 0xFF;     
byte Node1 = 0xBB;


float Sensor2 = 0; // Voltage Divider

String Mymessage = "";

void setup() {
  Serial.begin(9600);                   // initialize serial

pinMode(analogInput, INPUT); 

  if (!LoRa.begin(433E6)) {       
    Serial.println("LoRa init failed. Check your connections.");
    while (true);                       // if failed, do nothing
  }

 Serial.println("LoRa init succeeded.");
}

void loop() {
 {
value = analogRead(analogInput);
  vout = (value * 5.0)/ 1024.0;
  vin = vout / (R2/(R1+R2));
   Sensor2 = (vin); 
  Serial.println (vin);
   }
 
    Mymessage = Mymessage + Sensor2 +"," + Sensor2 ;  
    sendMessage(Mymessage,MasterNode,Node1);
    delay(100);
    Mymessage = "";
  Serial.println (Sensor2);
}

void sendMessage(String outgoing, byte MasterNode, byte otherNode) {
  LoRa.beginPacket();                   // start packet
  LoRa.write(MasterNode);              // add destination address
  LoRa.write(Node1);             // add sender address
  LoRa.write(msgCount);                 // add message ID
  LoRa.write(outgoing.length());        // add payload length
  LoRa.print(outgoing);                 // add payload
  LoRa.endPacket();                     // finish packet and send it
  msgCount++;                           // increment message ID
}

I know someone is going to ask why I have "Sensor2" twice in the line Mymessage= Mymessage + Sensor2 +"," + Sensor2 ; The reason is it won't work if I take one of the duplicates out. It probably has to do with the original program that had multiple sensors.

Here's the master (receiver)

/*
  Master Lora Node

*/
#include <SPI.h>              // include libraries
#include <LoRa.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
 
byte MasterNode = 0xFF;     
byte Node1 = 0xBB;

String SenderNode = "";
String outgoing;              // outgoing message

byte msgCount = 0;            // count of outgoing messages
String incoming = "";
 float Sensor2 = 0; // voltage Sensor


void setup() {
  Serial.begin(9600);                   // initialize serial
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  delay(500);
  display.clearDisplay();
  display.setTextColor(WHITE);

  if (!LoRa.begin(433E6)) {             // initialize ratio at 915 MHz
    Serial.println("LoRa init failed. Check your connections.");
    while (true);                       // if failed, do nothing
  }

 Serial.println("LoRa init succeeded.");
}

void loop() {

  // parse for a packet, and call onReceive with the result:
  onReceive(LoRa.parsePacket());
    
  }

void onReceive(int packetSize) {
  if (packetSize == 0) return;          // if there's no packet, return

  // read packet header bytes:
  int recipient = LoRa.read();          // recipient address
  byte sender = LoRa.read();            // sender address
  if( sender == 0XBB )
  SenderNode = "Node1:";
  byte incomingMsgId = LoRa.read();     // incoming msg ID
  byte incomingLength = LoRa.read();    // incoming msg length


  while (LoRa.available()) {
    incoming += (char)LoRa.read();
  }

  if (incomingLength != incoming.length()) {   // check length for error
    //Serial.println("error: message length does not match length");
    ;
    return;                             // skip rest of function
  }

  // if the recipient isn't this device or broadcast,
  if (recipient != Node1 && recipient != MasterNode) {
   // Serial.println("This message is not for me.");
    ;
    return;                             // skip rest of function
  }

 String r = getValue(incoming, ',', 1); // voltage Sensor
Sensor2 = r.toInt();
 incoming = "";

    //clear display
  display.clearDisplay();
    display.setTextSize(2);
  display.setCursor(0, 30);
  display.print("Volts:"+String(Sensor2));
  Serial.println ("Volts: " +String(Sensor2));

display.display(); 
}

String getValue(String data, char separator, int index)
{
    int found = 0;
    int strIndex[] = { 0, -1 };
    int maxIndex = data.length() - 1;
 
    for (int i = 0; i <= maxIndex && found <= index; i++) {
        if (data.charAt(i) == separator || i == maxIndex) {
            found++;
            strIndex[0] = strIndex[1] + 1;
            strIndex[1] = (i == maxIndex) ? i+1 : i;
        }
    }
    return found > index ? data.substring(strIndex[0], strIndex[1]) : "";
}
So any ideas on how to solve this problem would be appreciated.
Jeff

Why not pack all the sensor data into a single struct and send it all a once?

Actually all three sensors are packed into the string. I deleted the other two sensors from the sketch to make it easier to trouble shoot the decimal problem. I don't need decimals for the other two sensors so it's not a problem.

Why a string? That forces you to parse it at the receive side. A struct would make things much simpler.

getValue() returns say a string "9.3" which you then convert to an int making 9 which you then assign to Sensors2 making float value of 9.0

you also have a very convoluted way of extracting values from a string
if you have data="17.09,17.09"

    return data.substring(0,data.indexOf(','));

gives "17.09"

@gfvalvo suggests a struct would make things much simpler, i.e. saves converting your float data to Strings at the transmitter and then parsing string at the receiver to extract floats
e.g. this uses a Feather 32u4 to transmit a struct

// Lora transmit a structure

#include <SPI.h>
#include <LoRa.h>

struct Data {
  int x;
  float y;
  char z[10];
};

Data data={1 , 4.5, "hello"};

void setup() {
  Serial.begin(115200);
  while (!Serial);

  Serial.println("LoRa Sender");
  LoRa.setPins(8, 4, 7); // for Lora 32u4
  if (!LoRa.begin(866E6)) {
    Serial.println("Starting LoRa failed!");
    while (1);
  }
}

void loop() {
  Serial.print("\nSending packet: ");
  Serial.print("x = ");Serial.print(data.x);
  Serial.print(" y = ");Serial.print(data.y);
  Serial.print(" z = ");Serial.print(data.z);  
  // send packet
  LoRa.beginPacket();
  for (unsigned int i = 0; i < sizeof(Data);i++) {
    Serial.print(' ');
    LoRa.write(((byte *) &data)[i]);
    Serial.print(((byte *) &data)[i]);
  }
  LoRa.endPacket();
  data.x += 1;
  data.y += 1;
  data.z[0] += 1;

  delay(2000);
}

and this is the receiver

// Lora receive a structure

#include <SPI.h>
#include <LoRa.h>

struct Data {
  int x;
  float y;
  char z[10];
} data;


void setup() {
  Serial.begin(115200);
  while (!Serial);
  Serial.println("LoRa Receiver");
  LoRa.setPins(8, 4, 7); // for Lora 32u4
  if (!LoRa.begin(866E6)) {
    Serial.println("Starting LoRa failed!");
    while (1);
  }
}

void loop() {
  // try to parse packet
  int packetSize = LoRa.parsePacket();
  if (packetSize) {
    // received a packet
    Serial.print("\nReceived packet size ");
    Serial.print(packetSize);
    Serial.print(" data ");
    // read packet
    while (LoRa.available())
      for (int i = 0; i < packetSize; i++) {
        ((byte *) &data)[i] = LoRa.read();
        Serial.print(' ');
        Serial.print(((byte *) &data)[i]);
      }
    // print RSSI of packet
    Serial.print("' with RSSI ");
    Serial.println(LoRa.packetRssi());
    Serial.print("x = "); Serial.print(data.x);
    Serial.print(" y = "); Serial.print(data.y);
    Serial.print(" z = "); Serial.print(data.z);

  }
}

when run the serial monitors display

11:52:27.410 -> LoRa Sender
11:52:27.444 -> 
11:52:27.444 -> Sending packet: x = 1 y = 4.50 z = hello 1 0 0 0 144 64 104 101 108 108 111 0 0 0 0 0
11:52:29.472 -> Sending packet: x = 2 y = 5.50 z = iello 2 0 0 0 176 64 105 101 108 108 111 0 0 0 0 0
11:52:31.525 -> Sending packet: x = 3 y = 6.50 z = jello 3 0 0 0 208 64 106 101 108 108 111 0 0 0 0 0
11:52:33.589 -> Sending packet: x = 4 y = 7.50 z = kello 4 0 0 0 240 64 107 101 108 108 111 0 0 0 0 0
11:52:35.643 -> Sending packet: x = 5 y = 8.50 z = lello 5 0 0 0 8 65 108 101 108 108 111 0 0 0 0 0
11:52:37.694 -> Sending packet: x = 6 y = 9.50 z = mello 6 0 0 0 24 65 109 101 108 108 111 0 0 0 0 0
11:52:39.742 -> Sending packet: x = 7 y = 10.50 z = nello 7 0 0 0 40 65 110 101 108 108 111 0 0 0 0 0
11:52:41.772 -> Sending packet: x = 8 y = 11.50 z = oello 8 0 0 0 56 65 111 101 108 108 111 0 0 0 0 0
11:52:43.812 -> Sending packet: x = 9 y = 12.50 z = pello 9 0 0 0 72 65 112 101 108 108 111 0 0 0 0 0
11:52:45.860 -> Sending packet: x = 10 y = 13.50 z = qello 10 0 0 0 88 65 113 101 108 108 111 0 0 0 0 0
11:52:47.933 -> Sending packet: x = 11 y = 14.50 z = rello 11 0 0 0 104 65 114 101 108 108 111 0 0 0 0 0
11:52:49.979 -> Sending packet: x = 12 y = 15.50 z = sello 12 0 0 0 120 65 115 101 108 108 111 0 0 0 0 0
11:52:52.029 -> Sending packet: x = 13 y = 16.50 z = tello 13 0 0 0 132 65 116 101 108 108 111 0 0 0 0 0
11:52:54.077 -> Sending packet: x = 14 y = 17.50 z = uello 14 0 0 0 140 65 117 101 108 108 111 0 0 0 0 0

11:52:21.734 -> LoRa Receiver
11:52:27.491 -> 
11:52:27.491 -> Received packet size 16 data  1 0 0 0 144 64 104 101 108 108 111 0 0 0 0 0' with RSSI -59
11:52:27.491 -> x = 1 y = 4.50 z = hello
11:52:29.537 -> Received packet size 16 data  2 0 0 0 176 64 105 101 108 108 111 0 0 0 0 0' with RSSI -59
11:52:29.537 -> x = 2 y = 5.50 z = iello
11:52:31.580 -> Received packet size 16 data  3 0 0 0 208 64 106 101 108 108 111 0 0 0 0 0' with RSSI -59
11:52:31.580 -> x = 3 y = 6.50 z = jello
11:52:33.625 -> Received packet size 16 data  4 0 0 0 240 64 107 101 108 108 111 0 0 0 0 0' with RSSI -59
11:52:33.625 -> x = 4 y = 7.50 z = kello
11:52:35.678 -> Received packet size 16 data  5 0 0 0 8 65 108 101 108 108 111 0 0 0 0 0' with RSSI -59
11:52:35.678 -> x = 5 y = 8.50 z = lello
11:52:37.727 -> Received packet size 16 data  6 0 0 0 24 65 109 101 108 108 111 0 0 0 0 0' with RSSI -59
11:52:37.727 -> x = 6 y = 9.50 z = mello
11:52:39.767 -> Received packet size 16 data  7 0 0 0 40 65 110 101 108 108 111 0 0 0 0 0' with RSSI -60
11:52:39.802 -> x = 7 y = 10.50 z = nello
11:52:41.843 -> Received packet size 16 data  8 0 0 0 56 65 111 101 108 108 111 0 0 0 0 0' with RSSI -59
11:52:41.843 -> x = 8 y = 11.50 z = oello
11:52:43.891 -> Received packet size 16 data  9 0 0 0 72 65 112 101 108 108 111 0 0 0 0 0' with RSSI -59
11:52:43.891 -> x = 9 y = 12.50 z = pello
11:52:45.938 -> Received packet size 16 data  10 0 0 0 88 65 113 101 108 108 111 0 0 0 0 0' with RSSI -59

2 Likes

Even easier:

LoRa.write(data, sizeof(data));

Hey guys, thanks for that. Let me try it out tomorrow.
Much appreciated.
Jeff

@gfvalvo Thanks! I had used that method in the past- certainly more efficient than using write(uint8_t byte); to write bytes in a loop
in the code in post #6 I used it in the a loop while printing the transmitted data to Serial to check what I was transmitting
interesting there is no equivelent read array method

LoRa inherits from Stream:

class LoRaClass : public Stream {

So, readBytes() should work. From Stream.h:

  size_t readBytes( char *buffer, size_t length); // read chars from stream into buffer
  size_t readBytes( uint8_t *buffer, size_t length) { return readBytes((char *)buffer, length); }

appears towork OK!
updated Feather 32u4 Lora tranmit and receive code of post #6 to write and read byte array

// Lora transmit a structure

#include <SPI.h>
#include <LoRa.h>

struct Data {
  int x;
  float y;
  char z[10];
};

Data data={1 , 4.5, "hello"};

void setup() {
  Serial.begin(115200);
  while (!Serial);

  Serial.println("LoRa Sender");
  LoRa.setPins(8, 4, 7); // for Lora 32u4
  if (!LoRa.begin(866E6)) {
    Serial.println("Starting LoRa failed!");
    while (1);
  }
}

void loop() {
  Serial.print("\nSending packet: ");
  Serial.print("x = ");Serial.print(data.x);
  Serial.print(" y = ");Serial.print(data.y);
  Serial.print(" z = ");Serial.print(data.z);  
  // send packet
  LoRa.beginPacket();
  LoRa.write((byte *)&data, sizeof(data));
  for (unsigned int i = 0; i < sizeof(Data);i++) {
    Serial.print(' ');
    //LoRa.write(((byte *) &data)[i]);
    Serial.print(((byte *) &data)[i]);
  }
  LoRa.endPacket();
  data.x += 1;
  data.y += 1;
  data.z[0] += 1;

  delay(2000);
}

receiver

// Lora receive a structure

#include <SPI.h>
#include <LoRa.h>

struct Data {
  int x;
  float y;
  char z[10];
} data;


void setup() {
  Serial.begin(115200);
  while (!Serial);
  Serial.println("LoRa Receiver");
  LoRa.setPins(8, 4, 7); // for Lora 32u4
  if (!LoRa.begin(866E6)) {
    Serial.println("Starting LoRa failed!");
    while (1);
  }
}

void loop() {
  // try to parse packet
  int packetSize = LoRa.parsePacket();
  if (packetSize) {
    // received a packet
    Serial.print("\nReceived packet size ");
    Serial.print(packetSize);
    Serial.print(" data ");
    LoRa.readBytes((byte *)&data, packetSize);
    // read packet
    //while (LoRa.available())
      for (int i = 0; i < packetSize; i++) {
       // ((byte *) &data)[i] = LoRa.read();
        Serial.print(' ');
        Serial.print(((byte *) &data)[i]);
      }
    // print RSSI of packet
    Serial.print("' with RSSI ");
    Serial.println(LoRa.packetRssi());
    Serial.print("x = "); Serial.print(data.x);
    Serial.print(" y = "); Serial.print(data.y);
    Serial.print(" z = "); Serial.print(data.z);

  }
}

test

12:43:00.643 -> LoRa Sender
12:43:00.676 -> 
12:43:00.676 -> Sending packet: x = 1 y = 4.50 z = hello 1 0 0 0 144 64 104 101 108 108 111 0 0 0 0 0
12:43:02.702 -> Sending packet: x = 2 y = 5.50 z = iello 2 0 0 0 176 64 105 101 108 108 111 0 0 0 0 0
12:43:04.774 -> Sending packet: x = 3 y = 6.50 z = jello 3 0 0 0 208 64 106 101 108 108 111 0 0 0 0 0
12:43:06.820 -> Sending packet: x = 4 y = 7.50 z = kello 4 0 0 0 240 64 107 101 108 108 111 0 0 0 0 0
12:43:08.867 -> Sending packet: x = 5 y = 8.50 z = lello 5 0 0 0 8 65 108 101 108 108 111 0 0 0 0 0
12:43:10.912 -> Sending packet: x = 6 y = 9.50 z = mello 6 0 0 0 24 65 109 101 108 108 111 0 0 0 0 0
12:43:12.953 -> Sending packet: x = 7 y = 10.50 z = nello 7 0 0 0 40 65 110 101 108 108 111 0 0 0 0 0

12:42:34.125 -> LoRa Receiver
12:43:00.714 -> 
12:43:00.714 -> Received packet size 16 data  1 0 0 0 144 64 104 101 108 108 111 0 0 0 0 0' with RSSI -78
12:43:00.714 -> x = 1 y = 4.50 z = hello
12:43:02.760 -> Received packet size 16 data  2 0 0 0 176 64 105 101 108 108 111 0 0 0 0 0' with RSSI -78
12:43:02.760 -> x = 2 y = 5.50 z = iello
12:43:04.811 -> Received packet size 16 data  3 0 0 0 208 64 106 101 108 108 111 0 0 0 0 0' with RSSI -79
12:43:04.811 -> x = 3 y = 6.50 z = jello
12:43:06.858 -> Received packet size 16 data  4 0 0 0 240 64 107 101 108 108 111 0 0 0 0 0' with RSSI -80
12:43:06.858 -> x = 4 y = 7.50 z = kello
12:43:08.901 -> Received packet size 16 data  5 0 0 0 8 65 108 101 108 108 111 0 0 0 0 0' with RSSI -81
12:43:08.901 -> x = 5 y = 8.50 z = lello
12:43:10.971 -> Received packet size 16 data  6 0 0 0 24 65 109 101 108 108 111 0 0 0 0 0' with RSSI -83
12:43:10.971 -> x = 6 y = 9.50 z = mello

Okay, I've got the program you sent working. I had to tweak it a bit to work with the LoRa unit I have. Now that I have it working I sort of understand it. I take it that x, y, z represent the sensors (in my case and can be any of (int, float, char among others).
After I strip out the counter I can modify the program so x can be the ultrasonic, y the voltage, and z whatever.
Also I'll have to work in the display on the receiver.
Am I correct?

I just selected the three variables as a test - you can have combinations of basic types (float, byte, char, int, double, long int, etc) as well as arrays, structs, etc.
in general the structure in the sender and receiver should be the same
it is possible to transmit different sized structs, arrays, etc with the receiver working out what a particular packet represents but it is more complex

yes

Okay, I've already got the display working, now I'll see if I can get the voltage divider working. I'll post the final code here to help anyone else. Thanks again for the help.

what is the voltage divider for?

On the Transmitter side, I'm guessing I'll have to strip out all lines with [i] if I want to delete the counters. but leave in the (byte *) statements.

To read the voltage of the battery running the sensor. It's being fed by a solar panel/controller. Last summer the battery began to fail, and I didn't know for a while. So in addition to periodically sending the level in the water tank, I can no also see the battery voltage.

lines such as

    Serial.print(((byte *) &data)[i]);

were for displaying the packet contents during testing - not required in final application

I thought I'd start out adapting the program slowly. I got the display working okay, then decided I'd try and get the ultrasonic sensor working. It's very simple - especially when using the ultrasonic library - just a few lines of code. What could go wrong. I guess using the library function "ultrasonic.Ranging(CM)" instead of a simple integer or float is the problem.
I get an error:

"could not convert '{ultrasonic.Ultrasonic::Ranging(1), 4.5e+0, "hello"}' from '' to 'Data'

I could always go back to the old fashioned way of calculating the distance, but I'm curious, for future reference, how to get a library result to work in a struct.

// Lora transmit a structure

#include <SPI.h>
#include <LoRa.h>
#include "Ultrasonic.h"
Ultrasonic ultrasonic(8,7); //Ultrasonic ultrasonic(Trig,Echo);

struct Data {
 // int x;
  float y;
  char z[10];
   float x = ultrasonic.Ranging(CM);
};

Data data={ultrasonic.Ranging(CM) , 4.5, "hello"};

void setup() {
  Serial.begin(9600);                   // initialize serial
 // display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  delay(500);
 // display.clearDisplay();
//  display.setTextColor(WHITE);

  if (!LoRa.begin(433E6)) {             // initialize ratio at 915 MHz
    Serial.println("LoRa init failed. Check your connections.");
    while (true);                       // if failed, do nothing
  }
}

void loop() {
  Serial.print("\nSending packet: ");
  Serial.print("x = ");Serial.print(data.x);
  Serial.print(" y = ");Serial.print(data.y);
  Serial.print(" z = ");Serial.print(data.z);  
  // send packet
  LoRa.beginPacket();
  //for (unsigned int i = 0; i < sizeof(Data);i++) {
 //   Serial.print(' ');
  //  LoRa.write(((byte *) &data)[i]);
   // Serial.print(((byte *) &data)[i]);
  }
 // LoRa.endPacket();
 // data.x += 1;
//  data.y += 1;
 // data.z[0] += 1;

  delay(2000);
}

there are a number of ultrasonic libraries with different function names etc
I use ErickSimoes/Ultrasonic
made a few modifications to your code

#include <SPI.h>
#include <LoRa.h>
#include "Ultrasonic.h"
Ultrasonic ultrasonic(8,7); //Ultrasonic ultrasonic(Trig,Echo);

struct Data {
   float x;// = ultrasonic.Ranging(CM);
  float y;       // do you require y or z??
  char z[10];
};

Data data;//={ultrasonic.Ranging(CM) , 4.5, "hello"};

void setup() {
  Serial.begin(9600);                   // initialize serial
 // display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  delay(500);
 // display.clearDisplay();
//  display.setTextColor(WHITE);

  if (!LoRa.begin(433E6)) {             // initialize ratio at 915 MHz
    Serial.println("LoRa init failed. Check your connections.");
    while (true);                       // if failed, do nothing
  }
}

void loop() {
  data.x=ultrasonic.read();   //ultrasonic.ranging(CM);  <<< changed function name
  Serial.print("\nSending packet: ");
  Serial.print("x = ");Serial.print(data.x);
  Serial.print(" y = ");Serial.print(data.y);
  Serial.print(" z = ");Serial.print(data.z);  
  // send packet
  LoRa.beginPacket();
  for (unsigned int i = 0; i < sizeof(Data);i++) {
    Serial.print(' ');
    LoRa.write(((byte *) &data)[i]);
    Serial.print(((byte *) &data)[i]);
  }
  LoRa.endPacket();
 // data.x += 1;
//  data.y += 1;
 // data.z[0] += 1;

  delay(2000);
}

it now compiles OK