emelianov / modbus-esp8266

Most complete Modbus library for Arduino. A library that allows your Arduino board to communicate via Modbus protocol, acting as a master, slave or both. Supports network transport (Modbus TCP) and Serial line/RS-485 (Modbus RTU). Supports Modbus TCP Security for ESP8266/ESP32.
Other
530 stars 189 forks source link

Need some help converting from Modbus-Master-Slave-for-Arduino #52

Closed dreamfalcon closed 4 years ago

dreamfalcon commented 4 years ago

Hi,

I'm very new at this low level stuff, but managed to read some values from this setup: esp8266<->TTL to RS485 Converter<->Energy Counter

The code was based on this links that are using a old library, "Modbus-Master-Slave-for-Arduino": https://github.com/edphackathon/EDPIoTHackathon2017/blob/master/src/examples/EDPComm_example/EDPComm_example.ino https://github.com/edphackathon/EDPIoTHackathon2017/blob/master/src/EDPComm-Hackathon-2017/EDPComm.cpp

Special this part:

void SendEBRequest(uint8_t u8id, uint8_t u8fct, uint16_t u16RegAdd, uint16_t u16CoilsNo, uint16_t *au16reg)
{
  /*
    u8id : slave address, in this case =1
    u8fct : function code (check documentation)
    u16RegAdd : start address in slave (check documentation)
    u16CoilsNo : number of elements (coils or registers) to read 
    au16reg : pointer to a memory array in the Arduino
  */
  telegram = {u8id, u8fct, u16RegAdd, u16CoilsNo, au16reg};
  Serial.println("**Sending Request to EB**");
  master.query(telegram); // send query (only once)
}
void loop()
{
  switch (u8state)
  {
  case 0:
    // wait state
    if (millis() > u32wait)
      u8state++;
    break;
  case 1:
    /* For FCT_READLOADPROFILE the u16RegAdd is 769 (00000011 00000001 in binary) 
         That means values 0x0003 and 0x0001" in hexadecimal:
         0x0003 for Measumerent ID indexes to retrieve (1st - Clock 2nd -AMR Status and 3rd - Active Energy Import (A+))
         0x0001 for quantity of entries (we only need the last)
         The u16CoilsNo is 0 as we don't need any more inputs.
         You should always use this configuration for FCT_READLOADPROFILE
      */
    SendEBRequest(1, FCT_READLOADPROFILE, 769, 0, au16data);
    u8state++;
    break;
  case 2:
    master.poll(); // check incoming messages
    if (master.getState() == COM_IDLE)
    {
      u32wait = millis() + 2000;
      edpComm.printRawData(au16data); //prints raw load profile data coming from the EB
      u8state =6; //goes to Sigfox sending option
    }
    break;
  case 3:
    // wait state. You should always use this between SendEBRequests()
    if (millis() > u32wait)
      u8state++;
    break;
  case 4:
    /* For FCT_READSINGLEREGISTER the u16RegAdd depends on which register you want to get.
       Check the documentation for more info.
       The u16CoilsNo is 1 as in one output.
      You should always use this configuration for FCT_READSINGLEREGISTER
    */
    SendEBRequest(1, FCT_READSINGLEREGISTER, 109, 1, au16data); // #109 register is Instantaneous Current
    u8state++;
    break;
  case 5:
    master.poll(); // check incoming messages
    if (master.getState() == COM_IDLE)
    {
      u32wait = millis() + 2000;
      double current = edpComm.getLiveInstantValues(au16data);
      Serial.println(current);          //Current has a scaler of -1
      edpComm.resetDataArray(au16data); //Always reset au16data
      u32wait = millis() + 2000;
      u8state = 0;
    }
    break;
  case 6:
    /*
        In this example, we will ONLY send the last data value from the Load Profile (3rd - Active Energy Import (A+))
        You should complete the case with the other data available coming in from the EBs and send it to Sigfox. 
        Be aware of the 12 bytes payload limit!
        */
    if (edpComm.checkLoadProfileData(au16data))
    {
      int loadProfileData = edpComm.getLoadProfileData(au16data);
     //send data ...
      edpComm.resetDataArray(au16data);
      u32wait = millis() + 2000;
      u8state = 3;
      break;
    }
    else
    {
      Serial.println("**Skipped. Trying again...**");
      edpComm.resetDataArray(au16data);
      u32wait = millis() + 2000;
      u8state = 0;
      break;
    }
  }
}

I was trying to convert to this library, but have some doubts: Where I have

SendEBRequest(1, FCT_READSINGLEREGISTER, 121, 2, au16data); // #121 Instantaneous Power

Should be something like this?

if (!mb.slave()) {
      mb.readHreg(1, 121, coils, FCT_READSINGLEREGISTER, cbWrite);
    } 

Thx

emelianov commented 4 years ago

Hello Daniel, Sothing like:

mb.readIreg(1, 121, au16data, 2);

Inddeed It looks like sligtly more complex because of FCT_READLOADPROFILE is not standard Modbus function. As all Modbus magic is hidden inside the library code implementation of this processing function should be added to Modbus.h masterPDU().

dreamfalcon commented 4 years ago

This gives timeout.

void loop() {
  //OTA
  ArduinoOTA.handle();
  //webSocket
  webSocket.loop();
  //LED
  handleLedBlink();

  //MQTT
  if (!client.connected()) {
    checkMqttConnection();
  }

  //MODBUS
  //handleState();
  if (!mb.slave()) {
    mb.readIreg(1, 108, au16data, 2, cbWrite);
  }
  mb.task();
  //yield();
}

FCT_READLOADPROFILE appears to be "READ_INPUT_REGISTER": / For FCT_READSINGLEREGISTER the u16RegAdd depends on which register you want to get. Check the documentation for more info. The u16CoilsNo is 1 as in one output. You should always use this configuration for FCT_READSINGLEREGISTER / / For FCT_READLOADPROFILE the u16RegAdd is 769 (00000011 00000001 in binary) That means values 0x0003 and 0x0001" in hexadecimal: 0x0003 for Measumerent ID indexes to retrieve (1st - Clock 2nd -AMR Status and 3rd - Active Energy Import (A+)) 0x0001 for quantity of entries (we only need the last) The u16CoilsNo is 0 as we don't need any more inputs. You should always use this configuration for FCT_READLOADPROFILE /

ModbusRtu.h:

/**
 * @enum MB_FC
 * @brief
 * Modbus function codes summary.
 * These are the implement function codes either for Master or for Slave.
 *
 * @see also fctsupported
 * @see also modbus_t
 */
enum MB_FC
{
    MB_FC_NONE                     = 0,   /*!< null operator */
    MB_FC_READ_COILS               = 1, /*!< FCT=1 -> read coils or digital outputs */
    MB_FC_READ_DISCRETE_INPUT      = 2, /*!< FCT=2 -> read digital inputs */
    MB_FC_READ_REGISTERS           = 3, /*!< FCT=3 -> read registers or analog outputs */
    MB_FC_READ_INPUT_REGISTER      = 4, /*!< FCT=4 -> read analog inputs */
    MB_FC_WRITE_COIL               = 5, /*!< FCT=5 -> write single coil or output */
    MB_FC_WRITE_REGISTER           = 6, /*!< FCT=6 -> write single register */
    MB_FC_WRITE_MULTIPLE_COILS     = 15,    /*!< FCT=15 -> write multiple coils or outputs */
    MB_FC_WRITE_MULTIPLE_REGISTERS = 16 /*!< FCT=16 -> write multiple registers */
};
emelianov commented 4 years ago

Timeout may be result of hardware/wiring problems of long delay in checkMqttConnection() if connection is permanently unsuccesseful.

Anyway it's not possible to use FCT_READLOADPROFILE function without the library code modification. Easiest way i see that should work is to redefine (in Modbus.h)

FC_READ_REGS        = 68, // Redefine Read Holding Registers

and drop verifications (in ModbusRTU.h):

uint16_t ModbusRTU::readHreg(uint8_t slaveId, uint16_t offset, uint16_t* value, uint16_t numregs, cbTransaction cb) {
//  if (numregs < 0x0001 || numregs > maxRegs) return false;
    readSlave(offset, numregs, FC_READ_REGS);
    return send(slaveId, HREG(offset), cb, value);
}

and (again in Modbus.h):

    switch (fcode) {
        case FC_READ_REGS:
        case FC_READ_INPUT_REGS:
            //field2 = numregs, frame[1] = data lenght, header len = 2
//            if (frame[1] != 2 * field2) { //Check if data size matches
//                _reply = EX_DATA_MISMACH;
//                break;
//            }
            if (output) {
                frame += 2;
                while(field2) {
                    *((uint16_t*)output) = __bswap_16(*((uint16_t*)frame));
                    frame += 2;
                    output += 2;
                    field2--;
                }
            } else {
                setMultipleWords((uint16_t*)(frame + 2), startreg, field2);
            }
        break;

Afrer that you can call

mb.readHreg(1, 769, &buffer, 0, cb);

as FCT_READLOADPROFILE (but for normal usage readHreg will be broken).