Cambiar la dirección de una pasarela que comunica por ModBus usando Arduino

Hola a todos, tengo una pasarela de entradas digitales y analógicas que se comunica por Modbus, lo que quiero es un código para cambiar la dirección que tiene (que se supone que es 1) para que no entre en conflicto con otros sensores que tengo, según el manual de la pasarela, esta es la trama que se debe enviar para cambiar la dirección (00 06 00 64 00 01 08 04) y la pasarela tiene que enviar el mismo para confirmar que me ha cambiado, mi problema es que no sé en que mi programa esta fallando, si al momento de enviar la trama o al momento de recibirlo soy bastante principiante en el tema de Modbus en Arduino por lo que agradecería su ayuda, muchas gracias.

#include <SoftwareSerial.h>
#include <Wire.h>

const byte msg[] = {0x00,0x06, 0x00, 0x64, 0x00, 0x0a, 0x08, 0x04};

SoftwareSerial mod(7,8);

byte values[11];

void setup() {
  Serial.begin(9600);
  mod.begin(9600);
  pinMode(2, OUTPUT);

}

void loop() {
  
  byte val1;
  
  
  val1 = r();
  delay(250);


  Serial.print("Respuesta: ");
  Serial.println(val1);


}

byte r(){  
  digitalWrite(2,HIGH);
  delay(10);
  for(byte i=0;i<7;i++){
    Serial.print(msg[i],HEX);
    mod.write(msg[i]);
  }
  Serial.println();
  if(mod.write(msg,sizeof(msg))==8){
    digitalWrite(2,LOW);
    for(byte i=0;i<7;i++){
    //Serial.println();
    //Serial.println("Vamos por aquí");
    //Serial.println();
    //Serial.print(mod.read(),HEX);
    values[i] = mod.read();
    Serial.print(values[i],HEX);
    }
    Serial.println();
  }
}

Dejo el link del manual de la pasarela por si ayuda en algo. Muchas gracias

Manual Pasarela

No estoy seguro si algo del código está mal, lo que seguro está mal es el CRC que tienes que calcularlo para el nuevo paquete que estás enviando.
0804 es para el paquete de ejemplo, tu cambias el dato de dirección entonces cambia el CRC.
Fíjate que en algún lugar de la hoja de datos debería explicar como calcularlo.

Saludos

hola, es cierto, me equivoqué al escribirlo, pero si, lo he calculado y también lo probé pero seguía sin funcionar, entonces por eso pensé que era problema del código.

Puede que esté equivocado, pero tengo la impresión de que estás mandando los datos de dos formas diferente, una detrás de otra. La primera es byte a byte:

  for(byte i=0;i<7;i++){
    Serial.print(msg[i],HEX);
    mod.write(msg[i]);
  }

Y la segunda mandando todos los bytes de una sola tacada:

if(mod.write(msg,sizeof(msg))==8){

Lo que me temo es que en el primer caso lo estás mandando mal, porque sólo estás mandando 7 bytes en lugar de 8. Puede que eso sea lo que te está dando problemas. Para mandar 8 bytes la condición del for ha de ser i<8 o i<=7 cualquiera de las dos comparaciones te sirve. Pero tal como lo has puesto el bucle sólo envía los bytes del 0 al 6.

Muy bien @IgnoranteAbsoluto , siempre atento, estás en lo cierto con el for().

Lo del if() todavía no termino de entender que es lo que pretende hacer...
Se me ocurre que debería ser

if(mod.available() == 8 ) {

porque imagino espera la respuesta según indica la tabla que ha adjuntado.
Pero necesita darle tiempo a que el dispositivo reciba los datos y llegue la respuesta de confirmación.
Al menos un odioso delay(20) haría falta antes del if() porque se necesitan casi 9 ms para enviar los datos desde el arduino y otro tanto para recibir la respuesta del dispositivo.
Sino, como alternativa al delay() y el if() juntos, (otro odioso)

while(mod.available() < 8 );

pero si por alguna razón no llegase la respuesta, adiós, ahí se quedaría...

Modbus requiere de un timming especial que si no se tiene en cuenta las tramas van a fallar siempre.
No reinventes la rueda. La Biblioteca de libreríastiene muchas opciones Modbus, toma una y ajusta tu código a dicha librería. Hay muchos tutoriales ademas fortaleciendo cada una de esas librerías.
ModbusMaster es una de las preferidas.

Modbus Master IndustrialShields

Muchas gracias a todos por vuestra ayuda, sin duda me pondré a probar en cuanto pueda. Además quería mencionar que la parte

if(mod.write(msg,sizeof(msg))==8){

Yo pensaba que lo que hacía era comprobar que la trama que se enviaba tenía la longitud correcta y no se le había escapado ningún byte, pero claro también pensé que si era eso lo que hacía, entonces que parte del programa mandaba la trama como tal, por eso puse el for para que enviara la trama byte por byte. Básicamente esa parte que se supone que comunica la copié de otro código que era de un sensor que se comunicaba por modbus, solo le quité la parte del display y las otras dos tramas que enviaba, dejo aquí el código por si le queréis echar un vistazo.

#include <Wire.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
#define OLED_RESET -1       // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
 
#define RE 8
#define DE 7
 
//const byte code[]= {0x01, 0x03, 0x00, 0x1e, 0x00, 0x03, 0x65, 0xCD};
const byte nitro[] = {0x01,0x03, 0x00, 0x1e, 0x00, 0x01, 0xe4, 0x0c};
const byte phos[] = {0x01,0x03, 0x00, 0x1f, 0x00, 0x01, 0xb5, 0xcc};
const byte pota[] = {0x01,0x03, 0x00, 0x20, 0x00, 0x01, 0x85, 0xc0};
 
byte values[11];
SoftwareSerial mod(2,3);
 
void setup() {
  Serial.begin(9600);
  mod.begin(9600);
  pinMode(RE, OUTPUT);
  pinMode(DE, OUTPUT);
  
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C); //initialize with the I2C addr 0x3C (128x64)
  delay(500);
  display.clearDisplay();
  display.setCursor(25, 15);
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.println(" NPK Sensor");
  display.setCursor(25, 35);
  display.setTextSize(1);
  display.print("Initializing");
  display.display();
  delay(3000);
}
 
void loop() {
  byte val1,val2,val3;
  val1 = nitrogen();
  delay(250);
  val2 = phosphorous();
  delay(250);
  val3 = potassium();
  delay(250);
  
  
  Serial.print("Nitrogen: ");
  Serial.print(val1);
  Serial.println(" mg/kg");
  Serial.print("Phosphorous: ");
  Serial.print(val2);
  Serial.println(" mg/kg");
  Serial.print("Potassium: ");
  Serial.print(val3);
  Serial.println(" mg/kg");
  delay(2000);
 
  display.clearDisplay();
  
 
  display.setTextSize(2);
  display.setCursor(0, 5);
  display.print("N: ");
  display.print(val1);
  display.setTextSize(1);
  display.print(" mg/kg");
 
  display.setTextSize(2);
  display.setCursor(0, 25);
  display.print("P: ");
  display.print(val2);
  display.setTextSize(1);
  display.print(" mg/kg");
 
  display.setTextSize(2);
  display.setCursor(0, 45);
  display.print("K: ");
  display.print(val3);
  display.setTextSize(1);
  display.print(" mg/kg");
 
  display.display();
}
 
byte nitrogen(){
  digitalWrite(DE,HIGH);
  digitalWrite(RE,HIGH);
  delay(10);
  if(mod.write(nitro,sizeof(nitro))==8){
    digitalWrite(DE,LOW);
    digitalWrite(RE,LOW);
    for(byte i=0;i<7;i++){
    //Serial.print(mod.read(),HEX);
    values[i] = mod.read();
    Serial.print(values[i],HEX);
    }
    Serial.println();
  }
  return values[4];
}
 
byte phosphorous(){
  digitalWrite(DE,HIGH);
  digitalWrite(RE,HIGH);
  delay(10);
  if(mod.write(phos,sizeof(phos))==8){
    digitalWrite(DE,LOW);
    digitalWrite(RE,LOW);
    for(byte i=0;i<7;i++){
    //Serial.print(mod.read(),HEX);
    values[i] = mod.read();
    Serial.print(values[i],HEX);
    }
    Serial.println();
  }
  return values[4];
}
 
byte potassium(){
  digitalWrite(DE,HIGH);
  digitalWrite(RE,HIGH);
  delay(10);
  if(mod.write(pota,sizeof(pota))==8){
    digitalWrite(DE,LOW);
    digitalWrite(RE,LOW);
    for(byte i=0;i<7;i++){
    //Serial.print(mod.read(),HEX);
    values[i] = mod.read();
    Serial.print(values[i],HEX);
    }
    Serial.println();
  }
  return values[4];
}

Bueno sin enrollarme más, probaré lo del delay, también quitar el for o la otra forma que había puesto para enviar la trama, y a las muy malas como ha mencionado @Surbyte probaré cambiar el código con la librería ModbusMaster, pero el profesor de mi proyecto me había dicho que no lo hiciera mediante biblioteca sino que mandara la trama digamos de forma "manual", entonces intentaré todos los medios posibles para que el código funcione con los consejos que me habéis dado y sino hay mas remedio utilizaré la biblioteca.

Bueno, no es fácil hacerlo, te recomiendo esto que no es una librería pero esta cerca:

protocolo ModBus RTU de Scada Acimut para Arduino
Hace poco trabajé con esto para un cliente. No me gustó el Scada pero el sketch de Modbus funciona bien desde la PC directamente. Tal vez te sirva.

@kinetmc sigues repitiendo los mismos errores.

if(mod.write(pota,sizeof(pota))==8){

No hace lo que crees, y aunque lo haga, si envias 8 bytes, la respuesta será 8, no te sirve como control.
En todo caso usa

if(mod.available() == 8) {

porque tu necesitas saber si llegó la respuesta desde el dispositivo.

Sigues sin dar tiempo a que los datos salgan del arduino e inmediatamente te pones a leer la respuesta (que no está lista todavía, obviamente).
Piensa que tu código lleva apenas unos microsegundos en ejecutarse pero cada byte que envías tarda 1.04 milisegundos a 9600 bps. Tu estas intentando leer la respuesta cuando probablemente no se llegó a terminar enviar 1 bit del primer byte enviado.

for(byte i=0;i<7;i++){

Sigues contando mal la cantidad de bytes recibidos.
El if() termina cuando i es igual a 7, o sea que cuanta de 0 a 6 y sale, y de 0 a 6 hay 7 pasos.

for(byte i=0;i<8;i++){

es lo correcto.

Muchas gracias por tu ayuda @gatul, esta semana no he podido modificar el código ni probarlo, porque estuve de viaje pero este fin de semana ya probaré y os diré si ha funcionado.

Ahora probaré las dos librerías que me aconsejó @Surbyte y a ver si alguna funciona.

Muy buenas a todos, ya he podido modificar el código con lo que me habéis dicho, aquí dejo como lo he dejado para probarlo.

En primer lugar, la trama que he enviado es 00 06 00 64 00 0A 49 C3, los 5 primeros bytes son así según indica el manual de la pasarela, el 0A es para cambiar la dirección 1 a la 10, y los dos últimos son el CRC que calculé previamente.

El for esta hasta 8 para que envíe y reciba los 8 bytes completos, el primero lo tengo para enviar la trama y que me imprima lo que está enviando, y el segundo es para leer lo que me devuelve, también le he añadido delay prácticamente a todo XD, para evitar que eso me pueda dar problema en algún lado.

Me sigue sin funcionar, pero en principio creo que la trama si se está enviando bien, porque las luces de la pasarela parpadean cada vez que la trama se envía, asi que por eso creo que si se está enviando bien, ahora el problema es que no se si está mal el método que tengo para leerlo o que directamente la pasarela no me devuelve ninguna trama de respuesta.

#include <SoftwareSerial.h>
#include <Wire.h>

const byte msg[] = {0x00,0x06, 0x00, 0x64, 0x00, 0x0a, 0x49, 0xc3};

SoftwareSerial mod(7,8);

byte values[11];

void setup() {
  Serial.begin(9600);
  mod.begin(9600);
  pinMode(2, OUTPUT);

}

void loop() {
  
  byte val1;
  
  
  val1 = r();
  delay(250);


  Serial.print("Respuesta: ");
  Serial.println(val1);


}

byte r(){  
  digitalWrite(2,HIGH);
  delay(2000);
  for(byte i=0;i<8;i++){
    mod.write(msg[i]);
    Serial.print(msg[i],HEX);
  }
  delay(5000);
  Serial.println();
  digitalWrite(2,LOW);
  delay(5000);
  for(byte i=0;i<8;i++){
    Serial.print(mod.read(),HEX);
    //values[i] = mod.read();
    //Serial.print(values[i],HEX);
  }
  delay(2000);
  //if(mod.write(msg,sizeof(msg))==8){
    //digitalWrite(2,LOW);
    //for(byte i=0;i<8;i++){
    //Serial.println();
    //Serial.println("Vamos por aquí");
    //Serial.println();
    //Serial.print(mod.read(),HEX);
    //values[i] = mod.read();
    //Serial.print(values[i],HEX);
    //}
    Serial.println();
}

Muy buenas a todos, me complace decir que he solucionado el problema en parte, probé con todo lo que me dijiste, cambié bastante cosas del código, y volví a cablear el Arduino con la pasarela y al final acabó funcionando XD. El problema es que me di cuenta de que funcionó gracias al piloto que tiene consigo, ya que cuando consigue comunicar este se pone en verde y parpadea cada vez que se le envía una trama, además para confirmarlo probé enviar una trama para que activara una salida digital con la nueva dirección que le había puesto a la pasarela, y funcionó. Entonces no me di cuenta que funcionaba gracias a la trama que se supone que me envía como respuesta, sino al piloto, pero claro, yo necesito poder leer las tramas que me envíe como respuesta, porque al ser una pasarela de entradas analógicas y digital, si no me envía los datos de los sensores que le ponga no me sirve de nada. Ahora dejaré como tengo el código, por si hay alguna cosa que se me haya pasado por alto.