Eastron SDM230-Modbus RTU client time out

I am trying to get a RAK5005+RAK5802 using Arduino code to read the above Modbus interface. I keep getting error messages connection timed out.

Here is the output -

Here is the code -

/* -----------------------------------------
 * SDM230-Modbus Energy Meter to Arduino ModbusRTU Client
 * -----------------------------------------
 * 
 * https://gfinder.findernet.com/public/attachments/7E/EN/PRT_Modbus_7E_64_68_78_86EN.pdf
*/
 
#include <ArduinoRS485.h>
#include <ArduinoModbus.h>
#include <LoRaWan-RAK4630.h>

#define INPUT_REGISTERS 3

constexpr auto baudrate {9600};

// Calculate preDelay and postDelay in microseconds as per Modbus RTU Specification
// MODBUS over serial line specification and implementation guide V1.02
// Paragraph 2.5.1.1 MODBUS Message RTU Framing
// https://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf

constexpr auto bitduration { 1.f / baudrate };
constexpr auto preDelayBR { bitduration * 9.6f * 3.5f * 1e6 };
constexpr auto postDelayBR { bitduration * 9.6f * 3.5f * 1e6 };
// constexpr auto preDelayBR { bitduration * 10.0f * 3.5f * 1e6 };


unsigned long rate = 6000;                                              
unsigned long lastMillis = 0;
float voltage;
float current;
float power;
float frequency;
float exportEnergy;


void setup() 
{
  Serial.begin(9600);                                                     
  while (!Serial);
  Serial.println("\nModbus RTU Client\n");
  RS485.setDelays(preDelayBR, postDelayBR);
    
  if (!ModbusRTUClient.begin(baudrate, SERIAL_8N1))                      
    {
        Serial.println("Failed to start Modbus RTU Client!");
        while (1);
    }

}


void loop() 
{ 
  if (millis() - lastMillis > rate)                                      
   {
    lastMillis = millis();
  
    voltage = readVoltage();
    delay(100);
    current = readCurrent();
    delay(100);
    power = readPower();
    delay(100);
    frequency = readFreq();
    delay(100);
    exportEnergy = readExportEnergy();
  
    Serial.print("\nVoltage = " + String(voltage, 1) + " V   Current = " + String(current, 1) + " A   Power = " + String(power, 1) + " W   Freq = ");
    Serial.println(String(frequency, 1) + " Hz   Export Energy = " + String(exportEnergy, 1) + " kWh\n");
    delay(100);
  }   
}

 
float readVoltage()                                                                          
{
  float volt = 0.;
  
  if (!"connection timed out".
  
  Here ".requestFrom(01, INPUT_REGISTERS, 1, 2))                        
  
  {    
    Serial.print("Failed to read the voltage. ");                                             
    Serial.println(ModbusRTUClient.lastError()); 
  } 
  else 
  {
    // Response handler 
    Serial.println(ModbusRTUClient.available());
    uint16_t word1 = ModbusRTUClient.read();                                                    
    uint16_t word2 = ModbusRTUClient.read();                                                    
    uint32_t volt = word1 << 16 | word2;                                                        
  }

  return volt;
}



float readCurrent()                                                                             
{        
  float ampere = 0.;
     
  if (!ModbusRTUClient.requestFrom(0x01, INPUT_REGISTERS, 7, 2))                             
  {
      
    Serial.print("Failed to read the current. ");                                              
    Serial.println(ModbusRTUClient.lastError());         
  } 
  else 
  {        
    // Response handler 
    uint16_t word1 = ModbusRTUClient.read();                                                     
    uint16_t word2 = ModbusRTUClient.read();                                                    
    int32_t ampere = word1 << 16 | word2;                                                          
  }

  return ampere;
}


double readPower()                                                                              
{
  double watt = 0.;
  
  if (!ModbusRTUClient.requestFrom(0x01, INPUT_REGISTERS, 13, 2))                             
  {
      
    Serial.print("Failed to read power. ");                                                     
    Serial.println(ModbusRTUClient.lastError());
  } 
  else 
  {
    // Response handler 
    uint16_t word1 = ModbusRTUClient.read();                                                
    uint16_t word2 = ModbusRTUClient.read();                                                   from buffer                            
    int32_t watt = word1 << 16 | word2;                                                         
  }

  return watt;
}


float readFreq()                                                                                
{
  float freq = 0.;
  
  if (!ModbusRTUClient.requestFrom(0x01, INPUT_REGISTERS, 71, 2))                           
  {
      
    Serial.print("Failed to read frequency. ");                                                  
    Serial.println(ModbusRTUClient.lastError());
  } 
  else 
  {
    // Response handler 
    uint16_t word1 = ModbusRTUClient.read();                                                     
    uint16_t word2 = ModbusRTUClient.read();                                                   
    int32_t freq = word1 << 16 | word2;                                                          
  }
  return freq;
}


double readExportEnergy()                                                                             
{
  double kwh = 0.;
  
  if (!ModbusRTUClient.requestFrom(0x01, INPUT_REGISTERS, 75, 2))                         
  { 
    Serial.print("Failed to read energy. ");                                                     
    Serial.println(ModbusRTUClient.lastError());
  } 
  else 
  {
    // Response handler 
    uint16_t word1 = ModbusRTUClient.read();                                                     
    uint16_t word2 = ModbusRTUClient.read();                                                     
    int32_t dwh = word1 << 16 | word2;                                                           
    kwh = dwh/10000.0;                                                                           
  }
  return kwh;
}

Any help would be gratefully appreciated.

Check the AB outputs with an oscilloscope. Do you see any activity?

Is the power to the RS485 chip controlled by the libraries, or do you need to manually switch it on using the example in the documentation?

These libraries are specifically designed to be used on the MKR series of Arduinos together with the RS-485 MKR shield. I doubt that they work out of the box with your RAK board which isn't the most common board used on the Arduino platform. I suggest to use another Modbus library (p.e. ModbusMaster) where you can specifically tell the library which serial interface to use and how the enable lines are managed.

Post a link to the platform core you're using (I didn't find one with a simple search) as well as a wiring diagram!

Also post the correct code again, the one in your post isn't complete.

Corrected code -

/* -----------------------------------------
 * SDM230-Modbus Energy Meter to Arduino ModbusRTU Client
 * -----------------------------------------
 * 
 * https://gfinder.findernet.com/public/attachments/7E/EN/PRT_Modbus_7E_64_68_78_86EN.pdf
   https://docs.arduino.cc/arduino-cloud/tutorials/modbus-energy-meter
*/
 
#include <ArduinoRS485.h>
#include <ArduinoModbus.h>
#include <LoRaWan-RAK4630.h>

constexpr auto baudrate {9600};

// Calculate preDelay and postDelay in microseconds as per Modbus RTU Specification
// MODBUS over serial line specification and implementation guide V1.02
// Paragraph 2.5.1.1 MODBUS Message RTU Framing
// https://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf

constexpr auto bitduration { 1.f / baudrate };
constexpr auto preDelayBR { bitduration * 9.6f * 3.5f * 1e6 };
constexpr auto postDelayBR { bitduration * 9.6f * 3.5f * 1e6 };
// constexpr auto preDelayBR { bitduration * 10.0f * 3.5f * 1e6 };
 
unsigned long rate = 6000;                                                                       // Default refresh rate in ms
unsigned long lastMillis = 0;
float voltage;
float current;
float power;
float frequency;
float exportEnergy;


void setup() 
{
  Serial.begin(9600);                                                                            // Initialize serial port at 9600 bauds and wait for it to open
  while (!Serial);
  Serial.println("\nModbus RTU Client\n");
  RS485.setDelays(preDelayBR, postDelayBR);
    
  if (!ModbusRTUClient.begin(baudrate, SERIAL_8N1))                                              // Start the Modbus RTU client
    {
        Serial.println("Failed to start Modbus RTU Client!");
        while (1);
    }

}


void loop() 
{ 
  if (millis() - lastMillis > rate)                                                              // Update energy meter data and show it via the Serial Monitor
   {
    lastMillis = millis();
  
    voltage = readVoltage();
    delay(100);
    current = readCurrent();
    delay(100);
    power = readPower();
    delay(100);
    frequency = readFreq();
    delay(100);
    exportEnergy = readExportEnergy();
  
    Serial.print("\nVoltage = " + String(voltage, 1) + " V   Current = " + String(current, 1) + " A   Power = " + String(power, 1) + " W   Freq = ");
    Serial.println(String(frequency, 1) + " Hz   Export Energy = " + String(exportEnergy, 1) + " kWh\n");
    delay(100);
  }   
}

 
float readVoltage()                                                                             // Function readVoltage(). Read voltage value from the Finder energy meter holding registers
{
  float volt = 0.;
  
  if (!ModbusRTUClient.requestFrom(0x01, INPUT_REGISTERS, 0, 2))                                // Send reading request over RS485
  {              
    Serial.print("Failed to read the voltage. ");                                               // Error handling
    Serial.println(ModbusRTUClient.lastError()); 
  } 
  else 
  {
    // Response handler 
    uint16_t word1 = ModbusRTUClient.read();                                                    // Read word1 from buffer
    uint16_t word2 = ModbusRTUClient.read();                                                    // Read word2 from buffer
    uint32_t volt = word1 << 16 | word2;                                                        // Join word1 and word2 to retrieve voltage value in millivolts
  }

  return volt;
}



float readCurrent()                                                                             // Function readCurrent(). Read current value from the Finder energy meter holding registers
{        
  float ampere = 0.;
     
  if (!ModbusRTUClient.requestFrom(0x01, INPUT_REGISTERS, 6, 2))                                // Send reading request over RS485   
  {
      
    Serial.print("Failed to read the current. ");                                               // Error handling 
    Serial.println(ModbusRTUClient.lastError());         
  } 
  else 
  {        
    // Response handler 
    uint16_t word1 = ModbusRTUClient.read();                                                    // Read word1 from buffer
    uint16_t word2 = ModbusRTUClient.read();                                                    // Read word2 from buffer
    int32_t ampere = word1 << 16 | word2;                                                       // Join word1 and word2 to retrieve current value in milliampere
  }

  return ampere;
}


double readPower()                                                                              // Function readPower(). read power value from the Finder energy meter holding registers
{
  double watt = 0.;
  
  if (!ModbusRTUClient.requestFrom(0x01, INPUT_REGISTERS, 12, 2))                               // Send reading request over RS485
  {
      
    Serial.print("Failed to read power. ");                                                     // Error handling 
    Serial.println(ModbusRTUClient.lastError());
  } 
  else 
  {
    // Response handler 
    uint16_t word1 = ModbusRTUClient.read();                                                    // Read word1 from buffer
    uint16_t word2 = ModbusRTUClient.read();                                                    // Read word2 from buffer                            
    int32_t watt = word1 << 16 | word2;                                                         // Join word1 and word2 to retrieve current value in milliampere 
  }

  return watt;
}


float readFreq()                                                                                // Function readFreq(). read frequency value from the Finder energy meter holding registers
{
  float freq = 0.;
  
  if (!ModbusRTUClient.requestFrom(0x01, INPUT_REGISTERS, 70, 2))                               // Send reading request over RS485
  {
      
    Serial.print("Failed to read frequency. ");                                                 // Error handling 
    Serial.println(ModbusRTUClient.lastError());
  } 
  else 
  {
    // Response handler 
    uint16_t word1 = ModbusRTUClient.read();                                                    // Read word1 from buffer
    uint16_t word2 = ModbusRTUClient.read();                                                    // Read word2 from buffer
    int32_t freq = word1 << 16 | word2;                                                         // Join word1 and word2 to retrieve energy value in dwh
  }
  return freq;
}


double readExportEnergy()                                                                       // Function readEnergy() read energy value from the Finder energy meter holding registers
{
  double kwh = 0.;
  
  if (!ModbusRTUClient.requestFrom(0x01, INPUT_REGISTERS, 74, 2))                               // Send reading request over RS485
  { 
    Serial.print("Failed to read energy. ");                                                    // Error handling   
    Serial.println(ModbusRTUClient.lastError());
  } 
  else 
  {
    // Response handler 
    uint16_t word1 = ModbusRTUClient.read();                                                    // Read word1 from buffer
    uint16_t word2 = ModbusRTUClient.read();                                                    // Read word2 from buffer
    int32_t dwh = word1 << 16 | word2;                                                          // Join word1 and word2 to retrieve energy value in dwh
    kwh = dwh/10000.0;                                                                          // Convert energy to kwh
  }
  return kwh;
}

Here are some links which might help.

I have done some more work and I will post that. I think it will shed some light on the idea that the Arduino Modbus Library is specifically or solely for Arduino products.

Thank you for your replies. I have gone right back to basics. Very simple code herewith -

// v4 - https://github.com/arduino-libraries/ArduinoModbus/blob/master/examples/RTU/ModbusRTUClientKitchenSink/ModbusRTUClientKitchenSink.ino
// v3 - https://docs.arduino.cc/learn/communication/modbus
// v2 - Reading Eastron SDM-230 Modbus from RAK5005-0_v1 + RAK5802_v2 Modbus
// v1 - Reading Modbus RTU From Arduino - original
//
// https://www.losant.com/blog/getting-started-with-arduino-modbus-rtu-and-mqtt
//

#include <ArduinoRS485.h>                                 // ArduinoModbus depends on the ArduinoRS485 library
#include <ArduinoModbus.h>
#include <LoRaWan-RAK4630.h>


void setup() 
{
  Serial.begin(9600);
  while (!Serial);
  Serial.println("Modbus RTU Client Kitchen Sink");
  // start the Modbus RTU client
  if (!ModbusRTUClient.begin(9600)) 
  {
    Serial.println("Failed to start Modbus RTU Client!");
    while (1);
  }
}


void loop() 
{
    readInputRegisterValues();
    delay(5000);
    Serial.println();  
}


    // Send reading request over RS485
    void readInputRegisterValues() 
    {
      Serial.print("Reading input register values ... ");
      if (!ModbusRTUClient.requestFrom(01, INPUT_REGISTERS, 0, 80))     // read 80 discrete input values from (slave) id 01,
      {
        Serial.print("failed! ");
        Serial.println(ModbusRTUClient.lastError());
      } 
      else 
      {
        Serial.println("success");
        while (ModbusRTUClient.available()) 
        {
          Serial.print(ModbusRTUClient.read());
          Serial.print(' ');
        }
        Serial.println();
      }
    }


https://stromzähler.eu/media/pdf/0e/ca/1d/SDM230-Modbus.pdf. See pages 14,15 to identify registers.

You can see from the screenshots that the sketch did indeed read the SDM230 energy meter. P 13/14 explain the zeros. The active registers are not contiguous.

I think tghis exercise shows that the RAK5005+RAK5802 will read the Eastron device using Arduino code.

The sketch reads for approx 12 seconds, then times out. I can change the loop time (faster). The sketch will read the register more often, but the reading window does not exceed approx 12 seconds without timing out. So that appeqars the issue.

I have a second problem and that is decoding the 32 bit registers, but that another story for now.

If the circuit diagrams on the web site are correct then you need the
pinMode(WB_IO2, OUTPUT);
digitalWrite(WB_IO2, HIGH);
to turn on the 3V3_S supply which appears on pin 6 of the IO extension slot (and is incorrectly labelled 3V3).
Currently you are probably powering the transceiver via its signal pins.

image

image

Your wonderful. Why didnt I realise that!! Its working perfectly. Now I just have to decode the 2x 16-bit = 32-bit registers and get some sensible numbers. Again, many thanks for your help. I am very grateful.

Hello, again. I implemented your advice and got the Modbus-read code to work straight away. The next issue was to decode the content of the regiuster. The register is made up of 2x 16-bit registers, which when concatinated, gives a 32-bit int-format register result. You cant just call this float because it will be reforematted formatted into a floating point number when it is already a floating point number. The answer is "32-bit_floating_point_register_result = (float)&(registerValue);" So got that to work as well. Woohoo!

Now I'm trying to load that data into a LoRa payload, send it to my RAK7249 gateway, then subscribe to the topic on a RPi MQTT, decode it, then display it on a dashboard.

Please see the code below. The LoRa node issues a join request, the join is accepted by the RAK7249 gateway, the node continues to send packets/payloads to the gateway, but they are not seen on the gateway after that (nor on the MQTT).

Any advice would be greatly appreciated. Many thanks.

// 
// sketch_may13a_Node_13
//
// Device name = Node 13 SDM230-Modbus to LoRa
//
// Node No.13 WisBlock RAK4631 + RAK5802 RS485 Interface Module                                                                          (working)
// Read Eastron SDM230-Modbus input register data (Solar installation behaviour) and send it to the RAK7249 gateway in the barn.         (join requested, join is accepted, nothing sent after that)
// Then have the payload seen on the ACER Python MQTT.                                                                                   (the node/application is not seen on the MQTT)
//
// Based on: https://github.com/RAKWireless/WisBlock/blob/master/examples/RAK4630/communications/LoRa/LoRaWAN/LoRaWAN_OTAA_ABP/LoRaWAN_OTAA_ABP.ino
// 
// EASTRON SDM230-Modbus single phase energy meter
//
// (Keep - DeviceEUI[8] = {ac1f09fffe0507d0}; for another time)
//
// ApplicationID = 13 (RAK 5802 RS485 interface)
//
// DevEUI[8]   = {ac1f09fffe053d6e};                                                      This is node 13 RAK4630 resident in node 6 physical position in the garage. 
// AppEUI[8]   = {e0dad753fbd9bd81};
// AppKey[16]  = {7ab35168d92b51729772096d21876538};
//

#include <Arduino.h>
#include <ArduinoRS485.h>
#include <ArduinoModbus.h>                                                                  // http://librarymanager/All#ArduinoModbus
#include <LoRaWan-RAK4630.h>                                                                // http://librarymanager/All#SX126x
#include <SPI.h>

constexpr auto baudrate {9600};

// Calculate preDelay and postDelay in microseconds as per Modbus RTU Specification
// MODBUS over serial line specification and implementation guide V1.02
// Paragraph 2.5.1.1 MODBUS Message RTU Framing
// https://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf

constexpr auto bitduration { 1.f / baudrate };
constexpr auto preDelayBR  { bitduration * 9.6f * 3.5f * 1e6 };
constexpr auto postDelayBR { bitduration * 9.6f * 3.5f * 1e6 };
// constexpr auto preDelayBR { bitduration * 10.0f * 3.5f * 1e6 };

// The SDM230Modbus input registers are not contiguous.  id[i] contains the hex addresses of the 24 registers.  Each register is made up of two 16-bit registers, when concatinated represent a 32-bit floating point number
// uint16_t id[24] = {0x00, 0x06, 0x0c, 0x12, 0x18, 0x1e, 0x24, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x0102, 0x0108, 0x0156, 0x0158, 0x0180, 0x0182};   integer hex
// uint16_t id[24] = {1, 7, 13, 19, 25, 31, 37, 71, 73, 75, 77, 79, 85, 87, 89, 91, 93, 95, 259, 265, 343, 345, 385, 387};                                                           interger base 10
// 2x 16-bit registers = 4 bytes of data.  Total data = 4 x 24 = 96 bytes.

// see https://www.scribd.com/document/451662544/Eastron-SDM230-Modbus-user-manual-V1-4-2015-pdf pp 15-16

uint16_t id[24] = {1, 7, 13, 19, 25, 31, 37, 71, 73, 75, 77, 79, 85, 87, 89, 91, 93, 95, 259, 265, 343, 345, 385, 387};  //see Modbus User Manual v1.5
uint32_t input_register;
uint8_t j;
uint8_t k;

uint32_t eastron(uint16_t adr);                                                             // This function reads the Input_register values, one register per call

bool OTAA = true;                                                                           // OTAA is used by default.
#define SCHED_MAX_EVENT_DATA_SIZE APP_TIMER_SCHED_EVENT_DATA_SIZE                           // Maximum size of scheduler events.
#define SCHED_QUEUE_SIZE 60										                                              // Maximum number of events in the scheduler queue.
#define LORAWAN_DATERATE DR_0									                                              // LoRaMac datarates definition, from DR_0 to DR_5
#define LORAWAN_TX_POWER TX_POWER_5							                                            // LoRaMac tx power definition, from TX_POWER_0 to TX_POWER_15
#define JOINREQ_NBTRIALS 3										                                              // Number of trials for the join request.
DeviceClass_t g_CurrentClass = CLASS_A;					                                            // class definition
LoRaMacRegion_t g_CurrentRegion = LORAMAC_REGION_AU915;                                     // Region: AU915
lmh_confirm g_CurrentConfirm = LMH_UNCONFIRMED_MSG;				                                  // confirm/unconfirm packet definition
uint8_t gAppPort = LORAWAN_APP_PORT;							                                          // data port

// Structure containing LoRaWan parameters, needed for lmh_init()
static lmh_param_t g_lora_param_init = {LORAWAN_ADR_ON, LORAWAN_DATERATE, LORAWAN_PUBLIC_NETWORK, JOINREQ_NBTRIALS, LORAWAN_TX_POWER, LORAWAN_DUTYCYCLE_OFF};

//The LoRaWan application works with callbacks. So you do not need to poll the status from your loop(). Instead on different events these callbacks are are used to handle the events
// Foward declaration
static void lorawan_has_joined_handler(void);
static void lorawan_join_failed_handler(void);
static void lorawan_rx_handler(lmh_app_data_t *app_data);
static void lorawan_confirm_class_handler(DeviceClass_t Class);
static void send_lora_frame(void);

// Structure containing LoRaWan callback functions, needed for lmh_init()
static lmh_callback_t g_lora_callbacks = {BoardGetBatteryLevel, BoardGetUniqueId, BoardGetRandomSeed, lorawan_rx_handler, lorawan_has_joined_handler, lorawan_confirm_class_handler, lorawan_join_failed_handler};

//OTAA keys (KEYS ARE MSB)
uint8_t nodeDeviceEUI[8] = {0xac, 0x1f, 0x09, 0xff, 0xfe, 0x05, 0x3d, 0x6e};
uint8_t nodeAppEUI[8]    = {0xe0, 0xda, 0xd7, 0x53, 0xfb, 0xd9, 0xbd, 0x81};
uint8_t nodeAppKey[16]   = {0x7a, 0xb3, 0x51, 0x68, 0xd9, 0x2b, 0x51, 0x72, 0x97, 0x72, 0x09, 0x6d, 0x21, 0x87, 0x65, 0x38};

// Private defination
#define LORAWAN_APP_DATA_BUFF_SIZE 256                                                      // buffer size of the data to be transmitted.  (Only uses 120 bytes??)
#define LORAWAN_APP_INTERVAL 20000                                                          // Defines for user timer, the application data transmission interval. 20s, value in [ms].
static uint8_t m_lora_app_data_buffer[LORAWAN_APP_DATA_BUFF_SIZE];                          // Lora user application data buffer.
static lmh_app_data_t m_lora_app_data = {m_lora_app_data_buffer, 0, 0, 0, 0};               // Lora user application data structure.

TimerEvent_t appTimer;
static uint32_t timers_init(void);
static uint32_t count = 0;
static uint32_t count_fail = 0;


void setup()
{
  // RS485 Power On.  IO2 HIGH  3V3_S ON. See https://github.com/RAKWireless/WisBlock/blob/master/examples/RAK4630/IO/RAK5802_RS485/Receiver/Receiver.ino                                                    
  pinMode(WB_IO2, OUTPUT);
  digitalWrite(WB_IO2, HIGH);
  delay(500);

  // Initialize Serial for debug output
  time_t timeout = millis();
  Serial.begin(9600);
  while (!Serial)
  {
    if ((millis() - timeout) < 5000)
    {
      delay(100);
    }
    else
    {
      break;
    }
  }

  // Init Modbus
  RS485.begin(9600);
  RS485.receive();                                                                               // enable reception, can be disabled with: RS485.noReceive();
  Serial.println("\nModbus RTU Client\n");
  RS485.setDelays(preDelayBR, postDelayBR);
    
  if (!ModbusRTUClient.begin(baudrate, SERIAL_8N1))                                              // Start the Modbus RTU client
    {
      Serial.println("Failed to start Modbus RTU Client!");
      while (1);
    }

  // Initialize LoRa chip.
  lora_rak4630_init();

  Serial.println("=====================================");
  Serial.println("Welcome to RAK4630 LoRaWan");
  Serial.println("Type: OTAA");
  Serial.println("Region: AU915");
  Serial.println("=====================================");

  //creat a user timer to send data to server period
  uint32_t err_code;
  err_code = timers_init();
  if (err_code != 0)
  {
    Serial.printf("timers_init failed - %d\n", err_code);
    return;
  }

  // Setup the EUIs and AppKey
    lmh_setDevEui(nodeDeviceEUI);
    lmh_setAppEui(nodeAppEUI);
    lmh_setAppKey(nodeAppKey);

  // Initialize LoRaWan
  err_code = lmh_init(&g_lora_callbacks, g_lora_param_init, OTAA, g_CurrentClass, g_CurrentRegion);
  if (err_code != 0)
  {
    Serial.printf("lmh_init failed - %d\n", err_code);
    return;
  }

  // Start Join procedure
  lmh_join();
}


void loop()
{

}


// LoRa function for handling HasJoined event.
void lorawan_has_joined_handler(void)
{
  Serial.println("OTAA Mode, Network Joined");

  lmh_error_status ret = lmh_class_request(g_CurrentClass);
  if (ret == LMH_SUCCESS)
  {
    delay(1000);
    TimerSetValue(&appTimer, LORAWAN_APP_INTERVAL);
    TimerStart(&appTimer);
  }
}


//LoRa function for handling OTAA join failed
static void lorawan_join_failed_handler(void)
{
  Serial.println("OTAA join failed");
  Serial.println("Check your EUI's and AppKey");
  Serial.println("Check if a Gateway is in range");
}


// Function for handling LoRaWan received data from Gateway
// [in] app_data  Pointer to rx data
void lorawan_rx_handler(lmh_app_data_t *app_data)
{
  Serial.printf("LoRa Packet received on port %d, size:%d, rssi:%d, snr:%d, data:%s\n", app_data->port, app_data->buffsize, app_data->rssi, app_data->snr, app_data->buffer);
}


void lorawan_confirm_class_handler(DeviceClass_t Class)
{
  Serial.printf("switch to class %c done\n", "ABC"[Class]);

  // Informs the server that switch has occurred ASAP
  m_lora_app_data.buffsize = 0;
  m_lora_app_data.port = gAppPort;
  lmh_send(&m_lora_app_data, g_CurrentConfirm);
}


void send_lora_frame(void)
{
  if (lmh_join_status_get() != LMH_SET)
  {
    //Not joined, try again later
    return;
  }

  memset(m_lora_app_data.buffer, 0, LORAWAN_APP_DATA_BUFF_SIZE);
  m_lora_app_data.port = gAppPort;

  k = 0;
  for (j = 0; j < 24; j++)
  {
    input_register = eastron (id[j]);                                                          // Take the register address into the easteron function to read the 2x 16-bit register components
                                                                                               // the concatenated 32-bit register value is returned by the function
    m_lora_app_data.buffer[k++] = id[j];
    m_lora_app_data.buffer[k++] = (uint8_t) ((input_register & 0xff000000) >> 24 );            // convert int 32-bit "input_register" to 4-bytes for LoRa payload.  
    m_lora_app_data.buffer[k++] = (uint8_t) ((input_register & 0x00ff0000) >> 16 );            // Remember, this int 32-bit register actually contains a 32-bit floating point number toi be reassembled in the MQTT)
    m_lora_app_data.buffer[k++] = (uint8_t) ((input_register & 0x0000ff00) >>  8 );            // The leading byte ie register address, will key the register descriptor in the MQTT.
    m_lora_app_data.buffer[k++] = (uint8_t)  (input_register & 0x000000ff);
  }
  m_lora_app_data.buffsize = k;                                                                // there are 24 registers to be read, ie j = 0 to 23 ie total 24
                                                                                               // there are 5 bytes associated with the register to be placed in the payload.  Therefore buffsize = exit value of k = 24 x 5 = 120
  lmh_error_status error = lmh_send(&m_lora_app_data, g_CurrentConfirm);
  if (error == LMH_SUCCESS)
  {
    count++;
    Serial.printf("lmh_send ok count %d\n", count);
  }
  else
  {
    count_fail++;
    Serial.printf("lmh_send fail count %d\n", count_fail);
  }
}


// Function for handling user timerout event.
void tx_lora_periodic_handler(void)                                     // This function is called by a timer to start sending a message. In this example the timer is set to 10 seconds by the LORAWAN_APP_TX_DUTYCYCLE define
{
  TimerSetValue(&appTimer, LORAWAN_APP_INTERVAL);
  TimerStart(&appTimer);
  Serial.println("Sending frame now...\n");
  send_lora_frame();                                                          // This is the function that actually queues up a package to be sent over LoRaWan to the gateway.
}


// Function for the Timer initialization.
// Initializes the timer module. This creates and starts application timers.
uint32_t timers_init(void)
{
  TimerInit(&appTimer, tx_lora_periodic_handler);
  return 0;
}


uint32_t eastron(uint16_t adr)
{
  delay (100);

  // Read SDM230-Modbus energy meter Input Registers.

  uint16_t word1 = 0;
  uint16_t word2 = 0; 
  uint32_t registerValue = 0; 

  if (!ModbusRTUClient.requestFrom(0x01, INPUT_REGISTERS, adr, 2))
    {
      Serial.print("failed to read registers. ");
      Serial.println(ModbusRTUClient.lastError());
    }
  else
    {
    // If the request succeeds, the sensor reads the values that are stored in the input registers. 
    // The read() method is used to get the register values.
    word1 = ModbusRTUClient.read();
    word2 = ModbusRTUClient.read();
    registerValue = word1 << 16 | word2;                                     // concatinate int 16-bit word1 and int 16-bit word2 to create int 32-bit registerValue Whisc is actually a 32-bit floating point number
    }                                                                        // Note: use "floating_point_register_result = *(float*)&(registerValue);" in MQTT to decode the register value
  return registerValue;
}
type or paste code here

Hi everyone. I read on a forum somewhere that its better to devote a few hours researching a solution to your problem rather than racing into the forum to solve it. I can tell you that Ihave been working on the issues covered here for months befor asking the forum. And I do appreciate the forum's assistance. I have made a lot of progress. It strikes me that I wont be the only one in the world trying to read an Eastron SDM230-modbus energy meter using RAK hardware and associated code via Areduino IDE. So if Admin is ok with this, I would like to post my latest code which works perfectly in the hope that it helps others who may be struggling.

I have unbundled the project into three priorities -

Priority 1 - read the Eastron SDM230 INPUT_REGISTERs and present the register values to the Serial.print() screen and confirm that the data is correct. Working beautifully.

Priority 2 - put that data into a LoRa (RAK4630) payload and send it to the RAK7249 gateway. Prove that as working. Working perfectly.

Priority 3 - establish an MQTT client using an Arduino Mega + Ethernet2 shield to subscribe to the SMD230 topic, recover the data from the payload into float format and publish on a dashboard. I am particularly interested in the level of export solar power that the SDM230 is monitoring.

Im having a problem with this one and I would apprecite any help, please. Regards.

Hello, again. Here's Priority 1. I had two issues. The first was not having any power going to the RAK5802 RS485 interface hardware. Solution, thank you forum.

The second issue was getting the data structure clear. It consistes of two 16-bit interger words read from the SDM230 INPUT_REGISTER. These have to be concatinated to form a 32-bit integer format word. Then those 32-bits, once concatinated, are actually the INPUT_REGISTER value as a floating point number. You can just call it float. It will be translated into a float which will be rubbish. The solution is -

float y = (float)&x;

&x give the address of x. This is a pointer to x, and because x is a uint32_t, the type of the pointer is "pointer to uint32_t". So, cast that pointer to a pointer to float, and de-reference that pointer to fetch the value there as a float, and assign that value to a float variable y.

Here is the code. It is well commented.


/*
 * sketch_may20a_formatting.ino
 *
 * Priority 1.  FINAL
 *
 * --------------------------------------------------------------
 * Eastron SDM230-Modbus Energy Meter to Arduino ModbusRTU Client
 * -------------------------------=====================----------
 *
 * Uses RAK4630 + RAK5802 

 * This sketch reads the 24 registers associated with the SDM230-modbus energy meter
 * and outputs them to Serial.print().  This code is working properly.

 * Priority 1 - The purpose of this sketch is to demonstrate that the modbus client interface working properly (tick)                                              (working)
 * Priority 2 - put the regisater values into a LoRa payload and send to the RAK7249 gateway (in the barn)                                                         (working)
 * Priority 3 - Using an MQTT subscribe to the LoRa payload/message (tick), deserialise the data (json) (tick), decode the Base64 data and present in a dashboard. (not working, decoding issue)
 * 
 * https://gfinder.findernet.com/public/attachments/7E/EN/PRT_Modbus_7E_64_68_78_86EN.pdf
   https://docs.arduino.cc/arduino-cloud/tutorials/modbus-energy-meter
*/
 
#include <ArduinoRS485.h>
#include <ArduinoModbus.h>
#include <LoRaWan-RAK4630.h>

constexpr auto baudrate {9600};

// Calculate preDelay and postDelay in microseconds as per Modbus RTU Specification
// MODBUS over serial line specification and implementation guide V1.02
// Paragraph 2.5.1.1 MODBUS Message RTU Framing
// https://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf

constexpr auto bitduration { 1.f / baudrate };
constexpr auto preDelayBR { bitduration * 9.6f * 3.5f * 1e6 };
constexpr auto postDelayBR { bitduration * 9.6f * 3.5f * 1e6 };
// constexpr auto preDelayBR { bitduration * 10.0f * 3.5f * 1e6 };
 
unsigned long rate = 5000;                                                                       // Refresh rate of register(s) read in ms
unsigned long lastMillis = 0;

// The SDM230-Modbus INPUT_REGISTERS are not contiguous.  id[i] contains the hex addresses of the 24 registers.  Each register is made up of two 16-bit registers, when concatinated represent a 32-bit floating point number
// uint16_t id[24] = {0x00, 0x06, 0x0c, 0x12, 0x18, 0x1e, 0x24, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x0102, 0x0108, 0x0156, 0x0158, 0x0180, 0x0182};   integer hex
// uint16_t id[24] = {1, 7, 13, 19, 25, 31, 37, 71, 73, 75, 77, 79, 85, 87, 89, 91, 93, 95, 259, 265, 343, 345, 385, 387};                                                           interger base 10
// 2x 16-bit registers = 4 bytes of data.  Total data = 4 x 24 = 96 bytes.

// see https://www.scribd.com/document/451662544/Eastron-SDM230-Modbus-user-manual-V1-4-2015-pdf pp 15-16

uint16_t id[24] = {0x00, 0x06, 0x0c, 0x12, 0x18, 0x1e, 0x24, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x0102, 0x0108, 0x0156, 0x0158, 0x0180, 0x0182};
uint8_t  i;
uint16_t word1 = 0;
uint16_t word2 = 0; 
uint32_t word3 = 0;  
float    rregister;

String regName[24]    = {"Line to neutral", "Current", "Active power", "Apparent power", "Reactive power", "Power factor", "Phase angle", "Frequency", "Import active energy", "Export active energy", "Import reactive energy", "Export reactive energy", "Total system power demand", "Maximum total system power demand", "Current system positive power demand", "Maximum system positive power demand", "Current system reverse power demand", "Maximum system reverse power demand", "Maximum current demand", "Total active energy", "Total reactive energy", "Current resettable total active energy", "Current demand", "Current resettable total reactive energy"};
String regUnits[24]   = {"Volts", "Amps", "Watts", "VoltAmps", "VAr", "    ", "Degree", "Hz", "kwh", "kwh", "kvarh", "kvarh", "Watts", "Watts", "Watts", "Watts", "Watts", "Watts", "Amps", "Amps", "kwh", "kvarh", "kwh", "kvarh"};
String Formatting[24] = {"                          ","                                  ","                             ","                           ","                           ","                             ","                              ","                                ","                     ","                     ","                   ","                   ","                ","        ","     ","     ","      ","      ","                   ","                      ","                    ","   ","                           "," "};

void setup() 
{
  pinMode(WB_IO2, OUTPUT);                                                                       // IO2 HIGH  3V3_S ON
  digitalWrite(WB_IO2, HIGH);                                                                    // See https://github.com/RAKWireless/WisBlock/blob/master/examples/RAK4630/IO/RAK5802_RS485/Receiver/Receiver.ino  (Line 33)
  delay(500);

  Serial.begin(9600);                                                                            // Initialize serial port at 9600 bauds and wait for it to open
  while (!Serial);
  Serial.println("\nSDM230-Modbus RTU Client started\n");
  RS485.setDelays(preDelayBR, postDelayBR);
    
  if (!ModbusRTUClient.begin(baudrate, SERIAL_8N1))                                              // Start the Eastron SDM230-Modbus RTU client
    {
        Serial.println("Failed to start Modbus RTU Client!");
        while (1);
    }
}


void loop() 
{ 
  if (millis() - lastMillis > rate)                                                              // Update energy meter data and show it via the Serial Monitor
   {
    lastMillis = millis();
   }     
  
  for (i = 0; i < 24; i++)
  {
    if (!ModbusRTUClient.requestFrom(0x01, INPUT_REGISTERS, id[i], 2))                           // Send reading request over RS485.  Readd 2x 16-bit words starting adr id[i]
    {              
      Serial.print("Failed to read the voltage. ");                                              // Error handling
      Serial.println(ModbusRTUClient.lastError()); 
    } 
    else 
    {
      word1 = ModbusRTUClient.read();                                                            // Read 16-bit integer word1 from buffer
      word2 = ModbusRTUClient.read();                                                            // Read 16-bit integer word2 from buffer
      word3 = word1 << 16 | word2;                                                               // concatinate word1 and word2 into 32-bit integer word3
      rregister = *(float*)&(word3);                                                             // word3 is the envelope for the float value of the register being read
                                                                                                 // float rregister is read as the "float" interpretation of the unit32_t word3
      Serial.print ("\n");
      Serial.print (Formatting[i]);
      Serial.print (regName[i]);
      Serial.print ("  ");    
      Serial.print (rregister, 2);
      Serial.print ("  ");
      Serial.print (regUnits[i]);
      
    }
  }  
       Serial.print ("\n");
       delay(rate);  
}

I have attached a jpg of the Serial.print() screen of the output. The voltage is correct and the frequency is correct so I'm pretty sure the rest of the data is correct. Regards.

Screenshot (77)
Here's the output screen.

Hi there. Here is priority 2. RAK4261 running LoRa. Payload is the data as shown on the previous out put screen. I had some problems getting this to work. I was pretty confident that I was putting 5-bytes x 24 registers into the payload, but I was seeing no payload on the RAK7249 gateway. I did post this issue on the forum. I ended up approaching Bernd Giesecke from RAK and he pointed out the need to change DR0 to DR3 to cope with approx 128 byte payload in accordance with the LoRa spec. I changed it to DR5 as follows -

#define LORAWAN_DATARATE DR_5

The node took off like a rocket. The second thing I'd like to mention is regarding the first of the 5-bytes mentioned above. It contained the address of the register being read id[i] and put into the payload as a pointer to the formatting label, eg volts, amps, freq, etc. I read somewhere do not put any data into the payload that essentially is redundant. I dont need to send an index of the formatting labels because the data will arrive in the same sequence as the formatting labels are stored in any. That reduced the payload by 25% and therefore the DR3 setting.

Here is the code -

// 
// Node_14_FINAL 21 May 2023.ino
//
// Priority_2 Place the register data from priority_1 into a LoRa payload and send to the RAK7249 gateway.
//
// Device name = Node 14 SDM230-Modbus to LoRa
// Node_14_FINAL.ino  (look in "Priority_2" folder)
// Node No.14 WisBlock RAK4631 + RAK5802 RS485 Interface Module                                                                                                               (working)
// Read Eastron SDM230-Modbus input register data (Solar installation behaviour) (Priority_1) and send it to the RAK7249 gateway in the barn using LoRa (Priority_2).         (working)
// Then have the payload seen on the ACER Python MQTT (Priority_3).                                                                                   
//
// Based on: https://github.com/RAKWireless/WisBlock/blob/master/examples/RAK4630/communications/LoRa/LoRaWAN/LoRaWAN_OTAA_ABP/LoRaWAN_OTAA_ABP.ino
// 
// EASTRON SDM230-Modbus single phase energy meter
//
// (Keep - DeviceEUI[8] = {ac1f09fffe0507d0}; for another time)
//
// ApplicationID = 14 (RAK 5802 RS485 interface)
//
// DevEUI[8]   = {ac1f09fffe053d6e};                                                        // This is node 14 RAK4630 resident in node 6 physical position in the garage. 
// AppEUI[8]   = {e0dad753fbd9bd81};
// AppKey[16]  = {7ab35168d92b51729772096d21876538};
//

#include <Arduino.h>
#include <ArduinoRS485.h>
#include <ArduinoModbus.h>                                                                  // http://librarymanager/All#ArduinoModbus
#include <LoRaWan-RAK4630.h>                                                                // http://librarymanager/All#SX126x
#include <SPI.h>

constexpr auto baudrate {9600};

// Calculate preDelay and postDelay in microseconds as per Modbus RTU Specification
// MODBUS over serial line specification and implementation guide V1.02
// Paragraph 2.5.1.1 MODBUS Message RTU Framing
// https://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf

constexpr auto bitduration { 1.f / baudrate };
constexpr auto preDelayBR  { bitduration * 9.6f * 3.5f * 1e6 };
constexpr auto postDelayBR { bitduration * 9.6f * 3.5f * 1e6 };
// constexpr auto preDelayBR { bitduration * 10.0f * 3.5f * 1e6 };

// The SDM230Modbus input registers are not contiguous.  id[i] contains the hex addresses of the 24 registers.  Each register is made up of two 16-bit registers, when concatinated represent a 32-bit floating point number
// uint16_t id[24] = {0x00, 0x06, 0x0c, 0x12, 0x18, 0x1e, 0x24, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x0102, 0x0108, 0x0156, 0x0158, 0x0180, 0x0182};   integer hex
// uint16_t id[24] = {1, 7, 13, 19, 25, 31, 37, 71, 73, 75, 77, 79, 85, 87, 89, 91, 93, 95, 259, 265, 343, 345, 385, 387};                                                           interger base 10
// 2x 16-bit registers = 4 bytes of data.  Total data = 4 x 24 = 96 bytes.

// see https://www.scribd.com/document/451662544/Eastron-SDM230-Modbus-user-manual-V1-4-2015-pdf pp 15-16

uint16_t id[24] = {0x00, 0x06, 0x0c, 0x12, 0x18, 0x1e, 0x24, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x0102, 0x0108, 0x0156, 0x0158, 0x0180, 0x0182};
uint8_t j;
uint8_t k;
uint16_t word1 = 0;
uint16_t word2 = 0;                                                             // This function reads the Input_register values, one register per call

bool OTAA = true;                                                                           // OTAA is used by default.
#define SCHED_MAX_EVENT_DATA_SIZE APP_TIMER_SCHED_EVENT_DATA_SIZE                           // Maximum size of scheduler events.
#define SCHED_QUEUE_SIZE 60										                                              // Maximum number of events in the scheduler queue.
#define LORAWAN_DATARATE DR_5									                                              // LoRaMac datarates definition, from DR_0 to DR_5
#define LORAWAN_TX_POWER TX_POWER_5							                                            // LoRaMac tx power definition, from TX_POWER_0 to TX_POWER_15
#define JOINREQ_NBTRIALS 3										                                              // Number of trials for the join request.
DeviceClass_t g_CurrentClass = CLASS_A;					                                            // class definition
LoRaMacRegion_t g_CurrentRegion = LORAMAC_REGION_AU915;                                     // Region: AU915
lmh_confirm g_CurrentConfirm = LMH_UNCONFIRMED_MSG;				                                  // confirm/unconfirm packet definition
uint8_t gAppPort = LORAWAN_APP_PORT;							                                          // data port

// Structure containing LoRaWan parameters, needed for lmh_init()
static lmh_param_t g_lora_param_init = {LORAWAN_ADR_ON, LORAWAN_DATARATE, LORAWAN_PUBLIC_NETWORK, JOINREQ_NBTRIALS, LORAWAN_TX_POWER, LORAWAN_DUTYCYCLE_OFF};

//The LoRaWan application works with callbacks. So you do not need to poll the status from your loop(). Instead on different events these callbacks are are used to handle the events
// Foward declaration
static void lorawan_has_joined_handler(void);
static void lorawan_join_failed_handler(void);
static void lorawan_rx_handler(lmh_app_data_t *app_data);
static void lorawan_confirm_class_handler(DeviceClass_t Class);
static void send_lora_frame(void);

// Structure containing LoRaWan callback functions, needed for lmh_init()
static lmh_callback_t g_lora_callbacks = {BoardGetBatteryLevel, BoardGetUniqueId, BoardGetRandomSeed, lorawan_rx_handler, lorawan_has_joined_handler, lorawan_confirm_class_handler, lorawan_join_failed_handler};

//OTAA keys (KEYS ARE MSB)
uint8_t nodeDeviceEUI[8] = {0xac, 0x1f, 0x09, 0xff, 0xfe, 0x05, 0x3d, 0x6e};
uint8_t nodeAppEUI[8]    = {0xe0, 0xda, 0xd7, 0x53, 0xfb, 0xd9, 0xbd, 0x81};
uint8_t nodeAppKey[16]   = {0x7a, 0xb3, 0x51, 0x68, 0xd9, 0x2b, 0x51, 0x72, 0x97, 0x72, 0x09, 0x6d, 0x21, 0x87, 0x65, 0x38};

// Private defination
#define LORAWAN_APP_DATA_BUFF_SIZE 96                                                       // buffer size of the data to be transmitted.
#define LORAWAN_APP_INTERVAL 20000                                                          // Defines for user timer, the application data transmission interval. 20s, value in [ms].
static uint8_t m_lora_app_data_buffer[LORAWAN_APP_DATA_BUFF_SIZE];                          // Lora user application data buffer.
static lmh_app_data_t m_lora_app_data = {m_lora_app_data_buffer, 0, 0, 0, 0};               // Lora user application data structure.

TimerEvent_t appTimer;
static uint32_t timers_init(void);
static uint32_t count = 0;
static uint32_t count_fail = 0;


void setup()
{
  // RS485 Power On.  IO2 HIGH  3V3_S ON. See https://github.com/RAKWireless/WisBlock/blob/master/examples/RAK4630/IO/RAK5802_RS485/Receiver/Receiver.ino                                                    
  pinMode(WB_IO2, OUTPUT);
  digitalWrite(WB_IO2, HIGH);
  delay(500);

  // Initialize Serial for debug output
  time_t timeout = millis();
  Serial.begin(9600);
  while (!Serial)
  {
    if ((millis() - timeout) < 5000)
    {
      delay(100);
    }
    else
    {
      break;
    }
  }

  // Init Modbus
  RS485.begin(9600);
  RS485.receive();                                                                               // enable reception, can be disabled with: RS485.noReceive();
  Serial.println("\nModbus RTU Client started\n");
  RS485.setDelays(preDelayBR, postDelayBR);
    
  if (!ModbusRTUClient.begin(baudrate, SERIAL_8N1))                                              // Start the Modbus RTU client
    {
      Serial.println("Failed to start Eastron SDM230-Modbus RTU Client!");
      while (1);
    }

  // Initialize LoRa chip.
  lora_rak4630_init();

  Serial.println("=====================================");
  Serial.println("Welcome to RAK4630 LoRaWan");
  Serial.println("Type: OTAA");
  Serial.println("Region: AU915");
  Serial.println("=====================================");

  //creat a user timer to send data to server period
  uint32_t err_code;
  err_code = timers_init();
  if (err_code != 0)
  {
    Serial.printf("timers_init failed - %d\n", err_code);
    return;
  }

  // Setup the EUIs and AppKey
    lmh_setDevEui(nodeDeviceEUI);
    lmh_setAppEui(nodeAppEUI);
    lmh_setAppKey(nodeAppKey);

  // Initialize LoRaWan
  err_code = lmh_init(&g_lora_callbacks, g_lora_param_init, OTAA, g_CurrentClass, g_CurrentRegion);
  if (err_code != 0)
  {
    Serial.printf("lmh_init failed - %d\n", err_code);
    return;
  }

  // Start Join procedure
  lmh_join();
}


void loop()
{

}


// LoRa function for handling HasJoined event.
void lorawan_has_joined_handler(void)
{
  Serial.println("OTAA Mode, Network Joined");

  lmh_error_status ret = lmh_class_request(g_CurrentClass);
  if (ret == LMH_SUCCESS)
  {
    delay(1000);
    TimerSetValue(&appTimer, LORAWAN_APP_INTERVAL);
    TimerStart(&appTimer);
  }
}


//LoRa function for handling OTAA join failed
static void lorawan_join_failed_handler(void)
{
  Serial.println("OTAA join failed");
  Serial.println("Check your EUI's and AppKey");
  Serial.println("Check if a Gateway is in range");
}


// Function for handling LoRaWan received data from Gateway
// [in] app_data  Pointer to rx data
void lorawan_rx_handler(lmh_app_data_t *app_data)
{
  Serial.printf("LoRa Packet received on port %d, size:%d, rssi:%d, snr:%d, data:%s\n", app_data->port, app_data->buffsize, app_data->rssi, app_data->snr, app_data->buffer);
}


void lorawan_confirm_class_handler(DeviceClass_t Class)
{
  Serial.printf("switch to class %c done\n", "ABC"[Class]);

  // Informs the server that switch has occurred ASAP
  m_lora_app_data.buffsize = 0;
  m_lora_app_data.port = gAppPort;
  lmh_send(&m_lora_app_data, g_CurrentConfirm);
}


void send_lora_frame(void)
{
  if (lmh_join_status_get() != LMH_SET)
  {
    //Not joined, try again later
    return;
  }

  memset(m_lora_app_data.buffer, 0, LORAWAN_APP_DATA_BUFF_SIZE);
  m_lora_app_data.port = gAppPort;

  k = -1;
  for (j = 0; j < 24; j++)
  {
     delay (100);

    // Read Eastron SDM230-Modbus energy meter Input Registers.
 
    if (!ModbusRTUClient.requestFrom(0x01, INPUT_REGISTERS, (uint16_t) id[j], 2))
    {
      Serial.print("failed to read registers. ");
      Serial.println(ModbusRTUClient.lastError());
    }
    else
    {
      word1 = ModbusRTUClient.read();
      word2 = ModbusRTUClient.read();
    
      m_lora_app_data.buffer[k++] = highByte(word1);           
      m_lora_app_data.buffer[k++] = lowByte(word1);            
      m_lora_app_data.buffer[k++] = highByte(word2);            
      m_lora_app_data.buffer[k++] = lowByte(word2);
    }
  }
  m_lora_app_data.buffsize = k+1;                                                              // there are 24 registers to be read, ie j = 0 to 23 ie total 24
                                                                                               // there are 4 bytes associated with the register to be placed in the payload.  Therefore buffsize = exit value of k plus 1, ie 96
  lmh_error_status error = lmh_send(&m_lora_app_data, g_CurrentConfirm);
  if (error == LMH_SUCCESS)
  {
    count++;
    Serial.printf("lmh_send ok count %d\n", count);
  }
  else
  {
    count_fail++;
    Serial.printf("lmh_send fail count %d\n", count_fail);
  }
}


// Function for handling user timerout event.
void tx_lora_periodic_handler(void)                                     // This function is called by a timer to start sending a message. In this example the timer is set to 10 seconds by the LORAWAN_APP_TX_DUTYCYCLE define
{
  TimerSetValue(&appTimer, LORAWAN_APP_INTERVAL);
  TimerStart(&appTimer);
  Serial.println("Sending frame now...\n");
  send_lora_frame();                                                          // This is the function that actually queues up a package to be sent over LoRaWan to the gateway.
}


// Function for the Timer initialization.
// Initializes the timer module. This creates and starts application timers.
uint32_t timers_init(void)
{
  TimerInit(&appTimer, tx_lora_periodic_handler);
  return 0;
}

The RAK7249 gateway server shows the payload on its screen. I wont show it here. It is more appropriate to Priority_3 and the problem Im having there. Regards

Hi everyone. Here's Priority_3. The MQTT coding is straight forward which you will see below -

//
// Eastron_decode.ino
//
// Grid failure/UPS Project/Eastron SDM230-Modbus energy meter on Node 14
//
// Arduino-Mega-Ethnertet2 MQTT Client
//
// mqttClient.subscribe ("application/14/device/ac1f09fffe053d6e/rx"), ie Node 14
//
// Works perfectly (??) with Node 14 on RAK7249 (Barn)
//      
// Link: https://github.com/mcci-catena/arduino-lmic/blob/master/examples/ttn-otaa-feather-us915-dht22/ttn-otaa-feather-us915-dht22.ino
//
// float y = (float)&x;
// 
// &x give the address of x.  This is a pointer to x, and because x is a uint32_t, the type of the pointer is "pointer to uint32_t".
// So, cast that pointer to a pointer to float, and de-reference that pointer to fetch the value there as a float, and assign that value to a float variable y.
//

#include <Ethernet.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <base64.hpp>                                                         // https://github.com/Densaugeo/base64_arduino

StaticJsonDocument <512> doc;
boolean TimeToParse = false;

float rregister;
char devEUI[17] = "";
uint8_t base64_message[97] = {""};
uint8_t decoded_message[97] = {""};
int i;                                                                            
int j;

 byte mac[] = {0xA8, 0x61, 0x0A, 0xAE, 0x64, 0x3a};                           // MAC address Arduino Ethernet Shield MQTT client
 IPAddress ip(192, 168, 1, 52);                                               // associated static IP address Arduino MQTT client
 IPAddress server(192, 168, 1, 177);                                          // Static IP address RAK7249 built-in LoRa server Barn post Starlink passthrough = off

void callback(char *topic, byte *payload, unsigned int length)                // ISR
{ 
  Serial.print("\n\nMessage arrived\nTopic\n  [");
  Serial.print(topic);
  Serial.print("]\n");
  Serial.print("Payload\n  ");
  for (int i = 0; i < length; i++) 
  {
    Serial.print((char)payload[i]);
  } 
  Serial.print("\n");
  
  deserializeJson(doc, payload, length);                                      // Deserialize the JSON document    
  TimeToParse = true;                                                         // set flag for loop to parse payload
}

EthernetClient ethClient;
PubSubClient mqttClient(ethClient);
String clientID = String(mac[4]) + String(mac[5]) ;                           // use mac address to create clientID
  
void reconnect() 
{
 while (!mqttClient.connected())                                              // Loop until reconnected  
  {
    if (mqttClient.connect(clientID.c_str()))                                 // Attempt to connect
    {
      mqttClient.subscribe("application/14/device/ac1f09fffe053d6e/rx");
    }
     else 
    {
      delay(5000);                                                            // Wait 5 seconds before retrying
    }
  }
}

// Formatting of printout
String regName[24]    = {"Line to neutral", "Current", "Active power", "Apparent power", "Reactive power", "Power factor", "Phase angle", "Frequency", "Import active energy", "Export active energy", "Import reactive energy", "Export reactive energy", "Total system power demand", "Maximum total system power demand", "Current system positive power demand", "Maximum system positive power demand", "Current system reverse power demand", "Maximum system reverse power demand", "Maximum current demand", "Total active energy", "Total reactive energy", "Current resettable total active energy", "Current demand", "Current resettable total reactive energy"};
String regUnits[24]   = {"Volts", "Amps", "Watts", "VoltAmps", "VAr", "    ", "Degree", "Hz", "kwh", "kwh", "kvarh", "kvarh", "Watts", "Watts", "Watts", "Watts", "Watts", "Watts", "Amps", "Amps", "kwh", "kvarh", "kwh", "kvarh"};
String Formatting[24] = {"                          ","                                  ","                             ","                           ","                           ","                             ","                              ","                                ","                     ","                     ","                   ","                   ","                ","        ","     ","     ","      ","      ","                   ","                      ","                    ","   ","                           "," "};


void setup() 
{
  Serial.begin(9600);
  Ethernet.begin(mac, ip);
  delay(5000);                                                                // Allow the hardware to sort itself out
  mqttClient.setServer(server, 1883);
  mqttClient.setCallback(callback);
}


void loop() 
{
  if (!mqttClient.connected()) 
  {
    reconnect();
  }
  mqttClient.loop();

  if (TimeToParse)                                                            // if true, then there's data to be parsed from the ISR
  {                                                                        
    const char* base64_message = doc["data"];  

    Serial.print ("Base64_message\n  ");
    Serial.print (base64_message);

    int decoded_length = decode_base64(base64_message, decoded_message); 

    Serial.print ("\ndecoded_length\n  ");
    Serial.print (decoded_length);
    
    Serial.print ("\ndecoded_message\n  ");
    for (i = 0; i < decoded_length; i++)
    {
      Serial.print (decoded_message[i]);
    }
    Serial.print ("\n");   

    // Serial.print() float content of each concatinated 4 contiguous bytes as the Eastron register value
    j = 0;
    for (i = 0; i < 24; i++)
    {
     rregister = *(float*)&(decoded_message[j]); 

      Serial.print ("\n");
      Serial.print (Formatting[i]);
      Serial.print (regName[i]);
      Serial.print (" = ");
      Serial.print (rregister, 2);
      Serial.print (" ");
      Serial.print (regUnits[i]);
      j = j + 4;
    }

    TimeToParse = false;                                                // clear the parsing flag       
  }
}

I have a BASE64 decoding issue. I would appreciate any help or advice as to what the prblem is.

Here is Serial.print() scren shot of the output from the above code.

I have printed the message in the payload, 7th line down. This is identical to the payload shown on the RAK7249 gateway server. Here is that output screen shot.

You can see the payload about halfway down. (I dont know how to annotate the screen shot! Sorry.) Clearly the two message/data printouts are the same so that tells me that the MQTT is receiving the correct data from the gateway.

You van also see in both printouts that the fCnt 6846 is the same in both printouts so we are looking at the same packet.

The second printout shows the decoded base64 message at the top of the screen shot. This data is correct

The first printout shows the decoded base64 data in the MQTT and it bears no resemblence to the correctly decoded data from the gateway server. Thats my problem! Please help.

This code MQTT is using the Arduino base64 library v 1.3.0. See base64 - Arduino Libraries. There are at least 2 other base64 libraries out there. Thanks in anticipation. Regards

2 posts were split to a new topic: Connecting my Arduino with Win cc tia portal using RTU protocol

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.