Modbus Configuration for SMA Sunny Island

Hi there,

I am currently in the early stages of a project to display battery % of an SMA Sunny Island system via an Arduino. SMA uses MODBUS and this is fairly new to me, I have a little experience programming with an Arduino and was hoping to get some further guidance.

My vision at the moment is reading in the data, converting it to the correct type and displaying it on a display of some kind. I am still in a theorising stage and as I don't actually have an Arduino on hand yet I wanted to talk about what I think I need to do and see if it sounds right.

So, the Sunny Island has pretty good documentation for MODBUS configuration and we are using modbus register 30845, the data will be given as an unsigned 32 bits over 2 registers. See here for further documentation: https://files.sma.de/downloads/SI-Modbus-BA-en-12.pdf

I have found a piece of code here: How to use Modbus RTU with Arduino to read Sensor Data

I want to comment on the parts I think I would need to change but there are bound to be things wrong regardless and would appreciate feedback on all of that.

#include <ModbusMaster.h>      
#include <SoftwareSerial.h>
 
// Create a SoftwareSerial object to communicate with the MAX485 module
SoftwareSerial mySerial(10, 11); // RX, TX
 
// Create a ModbusMaster object
ModbusMaster node;
 
void setup() {
  // Initialize serial communication for debugging
  Serial.begin(9600);
  // Initialize SoftwareSerial for Modbus communication
  mySerial.begin(9600);
 
  // Initialize Modbus communication with the Modbus slave ID 1
  node.begin(1, mySerial);
 
  // Allow some time for initialization
  delay(1000);
}
 
void loop() {
  uint8_t result;   // Variable to store the result of Modbus operations
  uint16_t data[2]; // Array to store the data read from the Modbus slave
 
  // Read 2 holding registers starting at address 0x0000
  // This function sends a Modbus request to the slave to read the registers
  result = node.readHoldingRegisters(0x0000, 2);
 
  // If the read is successful, process the data
  if (result == node.ku8MBSuccess) {
    // Get the response data from the response buffer
    data[0] = node.getResponseBuffer(0x00); // Humidity
    data[1] = node.getResponseBuffer(0x01); // Temperature
 
    // Calculate actual humidity and temperature values
    float humidity = data[0] / 10.0;      // Humidity is scaled by 10
    float temperature = data[1] / 10.0;   // Temperature is scaled by 10
 
    // Print the values to the Serial Monitor
    Serial.print("Humidity: ");
    Serial.print(humidity);
    Serial.println(" %RH");
 
    Serial.print("Temperature: ");
    Serial.print(temperature);
    Serial.println(" °C");
  } else {
    // Print an error message if the read fails
    Serial.print("Modbus read failed: ");
    Serial.println(result, HEX); // Print the error code in hexadecimal format
  }
 
  // Wait for 2 seconds before the next read
  delay(2000);
}

So, starting from setup the only change I can see would be changing the slave ID if my sunny island device doesn't read as device 1.

In the main loop I believe I would need to change the "result" to a 16 bit and the data to a 32 bit? I do believe the array length of 2 is correct in this case? My register I believe in Hex would be 0x787D ?

Obviously for my use the code after the successful read would be different but I just wanted to get some proper advice with where my head is at. Thank you anyone who helps and once I have a bit more clarity on what to do I will get myself an actual Arduino and start playing around.

Hi @bengfeld ,
Welcome to the forum..

Quick read of the manual for your product..
Looks like it's ModBus over IP not serial..
example code you posted is for ModBus over serial..

should also be able to find some libs/examples of using modbus over IP..

good luck.. ~q

1 Like

You're right on that one thank you. I've found a new piece of code and I'm just gonna do the same thing where I talk through my thoughts.

Ok so the code i've found is from https://www.industrialshields.com/blog/arduino-industrial-1/modbus-tcp-master-with-industrial-arduino-esp32-plcs-103:

/* 
   Copyright (c) 2018 Boot&Work Corp., S.L. All rights reserved

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <Ethernet.h>            // This is the client;
#include <ModbusTCPMaster.h>     // This is the master;

// Ethernet configuration values
uint8_t mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
uint8_t ip[] = { 10, 10, 10, 3 };
uint8_t slaveIp[] = { 10, 10, 10, 4 };
uint16_t slavePort = 502;

// Define the ModbusTCPMaster object
ModbusTCPMaster master;

// Ethernet client object used to connect to the slave
EthernetClient slave;

uint32_t lastSentTime = 0UL;

////////////////////////////////////////////////////////////////////////////////////////////////////
void setup() {
  Serial.begin(9600UL);

  // Begin Ethernet
  Ethernet.begin(mac, ip);
  Serial.println(Ethernet.localIP());

  // NOTE: it is not necessary to start the modbus master object
}

////////////////////////////////////////////////////////////////////////////////////////////////////
void loop() {
  // Connect to slave if not connected
  // The ethernet connection is managed by the application, not by the library
  // In this case the connection is opened once
  if (!slave.connected()) {
    slave.stop();

    slave.connect(slaveIp, slavePort);
  }

  // Send a request every 1000ms if connected to slave
  if (slave.connected()) {
    if (millis() - lastSentTime > 1000) {
      // Send a Read Input Registers request to the slave with address 31
      // It requests for 6 registers starting at address 0
      // IMPORTANT: all read and write functions start a Modbus transmission, but they are not
      // blocking, so you can continue the program while the Modbus functions work. To check for
      // available responses, call master.available() function often.
      if (!master.readInputRegisters(slave, 31, 0, 6)) {
        // Failure treatment
      }

      lastSentTime = millis();
    }

    // Check available responses often
    if (master.isWaitingResponse()) {
      ModbusResponse response = master.available();
      if (response) {
        if (response.hasError()) {
          // Response failure treatment. You can use response.getErrorCode()
          // to get the error code.
        } else {
          // Get the input registers values from the response
          Serial.print("Input registers values: ");
          for (int i = 0; i < 6; ++i) {
            Serial.print(response.getRegister(i));
            Serial.print(',');
          }
          Serial.println();
        }
      }
    }
  }
}

Ok, few questions about the initialisation: Can't find the documentation for "ModbusTCPMaster.h" does anyone know where to find that?
I am also a bit unsure as to what the name "mac" means (I've seen this in other examples and it isn't clear to me), more for my own understanding this one, also a bit unclear what is being referenced in this and what these values mean. I understand the rest of the initialisations.

I think I mostly understand the loop also, but line 64 which is:

if (!master.readInputRegisters(slave, 31, 0, 6))

I am a bit confused on this one, I understand slave as it's the device being requested, but then 31 I am unsure on, is this the master or slaves address or something else entirely? Then 0 for me I am assuming is where I set 30845 which will be the data I want, and then 6 I think I need 2?

I think I understand the rest, does anyone see why this would not work for my intended purpose? Reading in a value on one of the slaves registers.

31 is the slave address, 0 is first register to read and 6 is quantity of registers to read.

1 Like

Hi @bengfeld ,

Looks like it's Industrial-Shields/arduino-Modbus this one..
documentation is in the git, linked above..
mac short for "media access control"..
a unique identifier assigned to a network interface..
normally programmed by the manufacturer..
i would stay away from the commonly used DEAD BEEF FEED, it's cute but not so unique..

the exclamation point NOTs (reverses) the return value of readInputRegisters to demonstrate how to catch errors..
slave is the EthernetClient that has been connected to the remote machine..
I do believe the rest has been explained..
I would approach this scientifically, experimentation..

if (master.readInputRegisters(slave, 1, 844, 2)){
//success
}

be my first try..

good luck.. ~q

1 Like