Modbus en NTP server

Is het mogelijk dit tegelijk te laten werken?

ik wil vanuit een plc via modbus de datum/tijd naar een arduino mega te sturen (dat werkt trouwens al)
Deze Datum/tijd wil ik via een Time Server naar andere hardware in me netwerk zenden…

maar gaat dat lukken?

ik heb op dit moment dit

#include <SPI.h>
#include <Ethernet.h>
#include "MgsModbus.h"
#include <EthernetUdp.h>   // UDP library from: bjoern@cs.stanford.edu 12/30/2008

#include <UTFTGLUE.h>              //use GLUE class and constructor
UTFTGLUE myGLCD(0, A2, A1, A3, A4, A0); //all dummy args

// Time Server Port
#define NTP_PORT 123

MgsModbus Mb;

// Ethernet settings (depending on MAC and Local network)
byte mac[] = {0x90, 0xA2, 0xDA, 0x0E, 0x94, 0xB5 };
IPAddress ip(192, 168, 3, 250);
IPAddress gateway(192, 168, 3, 100);
IPAddress subnet(255, 255, 255, 0);

static const int NTP_PACKET_SIZE = 48;

// buffers for receiving and sending data
byte packetBuffer[NTP_PACKET_SIZE];


// An Ethernet UDP instance
EthernetUDP Udp;

int year;
byte month, day, hour, minute, second, hundredths;
unsigned long date, time, age;
uint32_t timestamp, tempval;

void setup()
{
  // serial setup
  Serial.begin(9600);
  Serial.println("Serial interface started");

  // initialize the ethernet device
  Ethernet.begin(mac, ip, gateway, subnet);   // start etehrnet interface
  Serial.println("Ethernet interface started");

  // print your local IP address:
  Serial.print("My IP address: ");
  for (byte thisByte = 0; thisByte < 4; thisByte++) {
    // print the value of each byte of the IP address:
    Serial.print(Ethernet.localIP()[thisByte], DEC);
    Serial.print(".");
  }

  myGLCD.InitLCD();

  // Clear the screen
  myGLCD.clrScr();
  //myGLCD.setFont(WideFont);
  myGLCD.setColor(100, 255, 100);
  myGLCD.setBackColor(0, 0, 0);
  myGLCD.fillScr(0, 0, 0);

  myGLCD.setTextSize(8);
}

void loop()
{
  Mb.MbsRun();
  myGLCD.printNumI( Mb.MbData[2], 0, 100);
  myGLCD.printNumI( Mb.MbData[1], 110, 100);
  myGLCD.printNumI( Mb.MbData[0], 220, 100);

  myGLCD.printNumI( Mb.MbData[3], 0, 200);
  myGLCD.printNumI( Mb.MbData[4], 110, 200);
  myGLCD.printNumI( Mb.MbData[5], 220, 200);

  Serial.print(Mb.MbData[2]);
  Serial.print(":");
  Serial.print(Mb.MbData[1]);
  Serial.print(":");
  Serial.print(Mb.MbData[0]);
  Serial.print(" - ");
  Serial.print(Mb.MbData[3]);
  Serial.print("/");
  Serial.print(Mb.MbData[4]);
  Serial.print("/");
  Serial.println(Mb.MbData[5]);

  year = Mb.MbData[5];
  month = Mb.MbData[4];
  day = Mb.MbData[3];
  hour = Mb.MbData[2];
  minute = Mb.MbData[1];
  second = Mb.MbData[0];

  //processNTP();

}


void processNTP() {

  // if there's data available, read a packet
  int packetSize = Udp.parsePacket();
  if (packetSize)
  {
    Udp.read(packetBuffer, NTP_PACKET_SIZE);
    IPAddress Remote = Udp.remoteIP();
    int PortNum = Udp.remotePort();

#if debug
    Serial.println();
    Serial.print("Received UDP packet size ");
    Serial.println(packetSize);
    Serial.print("From ");

    for (int i = 0; i < 4; i++)
    {
      Serial.print(Remote[i], DEC);
      if (i < 3)
      {
        Serial.print(".");
      }
    }
    Serial.print(", port ");
    Serial.print(PortNum);

    byte LIVNMODE = packetBuffer[0];
    Serial.print("  LI, Vers, Mode :");
    Serial.print(packetBuffer[0], HEX);

    byte STRATUM = packetBuffer[1];
    Serial.print("  Stratum :");
    Serial.print(packetBuffer[1], HEX);

    byte POLLING = packetBuffer[2];
    Serial.print("  Polling :");
    Serial.print(packetBuffer[2], HEX);

    byte PRECISION = packetBuffer[3];
    Serial.print("  Precision :");
    Serial.println(packetBuffer[3], HEX);

    for (int z = 0; z < NTP_PACKET_SIZE; z++) {
      Serial.print(packetBuffer[z], HEX);
      if (((z + 1) % 4) == 0) {
        Serial.println();
      }
    }
    Serial.println();

#endif


    packetBuffer[0] = 0b00100100;   // LI, Version, Mode
    packetBuffer[1] = 1 ;   // stratum
    packetBuffer[2] = 6 ;   // polling minimum
    packetBuffer[3] = 0xFA; // precision

    packetBuffer[7] = 0; // root delay
    packetBuffer[8] = 0;
    packetBuffer[9] = 8;
    packetBuffer[10] = 0;

    packetBuffer[11] = 0; // root dispersion
    packetBuffer[12] = 0;
    packetBuffer[13] = 0xC;
    packetBuffer[14] = 0;

    timestamp = numberOfSecondsSince1900Epoch(year, month, day, hour, minute, second);

    Serial.println(timestamp);

    tempval = timestamp;

    packetBuffer[12] = 71; //"G";
    packetBuffer[13] = 80; //"P";
    packetBuffer[14] = 83; //"S";
    packetBuffer[15] = 0; //"0";

    // reference timestamp
    packetBuffer[16] = (tempval >> 24) & 0XFF;
    tempval = timestamp;
    packetBuffer[17] = (tempval >> 16) & 0xFF;
    tempval = timestamp;
    packetBuffer[18] = (tempval >> 8) & 0xFF;
    tempval = timestamp;
    packetBuffer[19] = (tempval) & 0xFF;

    packetBuffer[20] = 0;
    packetBuffer[21] = 0;
    packetBuffer[22] = 0;
    packetBuffer[23] = 0;


    //copy originate timestamp from incoming UDP transmit timestamp
    packetBuffer[24] = packetBuffer[40];
    packetBuffer[25] = packetBuffer[41];
    packetBuffer[26] = packetBuffer[42];
    packetBuffer[27] = packetBuffer[43];
    packetBuffer[28] = packetBuffer[44];
    packetBuffer[29] = packetBuffer[45];
    packetBuffer[30] = packetBuffer[46];
    packetBuffer[31] = packetBuffer[47];

    //receive timestamp
    packetBuffer[32] = (tempval >> 24) & 0XFF;
    tempval = timestamp;
    packetBuffer[33] = (tempval >> 16) & 0xFF;
    tempval = timestamp;
    packetBuffer[34] = (tempval >> 8) & 0xFF;
    tempval = timestamp;
    packetBuffer[35] = (tempval) & 0xFF;

    packetBuffer[36] = 0;
    packetBuffer[37] = 0;
    packetBuffer[38] = 0;
    packetBuffer[39] = 0;

    //transmitt timestamp
    packetBuffer[40] = (tempval >> 24) & 0XFF;
    tempval = timestamp;
    packetBuffer[41] = (tempval >> 16) & 0xFF;
    tempval = timestamp;
    packetBuffer[42] = (tempval >> 8) & 0xFF;
    tempval = timestamp;
    packetBuffer[43] = (tempval) & 0xFF;

    packetBuffer[44] = 0;
    packetBuffer[45] = 0;
    packetBuffer[46] = 0;
    packetBuffer[47] = 0;


    // Reply to the IP address and port that sent the NTP request

    Udp.beginPacket(Remote, PortNum);
    Udp.write(packetBuffer, NTP_PACKET_SIZE);
    Udp.endPacket();
  }
}


////////////////////////////////////////

const uint8_t daysInMonth [] PROGMEM = {
  31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
}; //const or compiler complains

const unsigned long seventyYears = 2208988800UL; // to convert unix time to epoch

// NTP since 1900/01/01
static unsigned long int numberOfSecondsSince1900Epoch(uint16_t y, uint8_t m, uint8_t d, uint8_t h, uint8_t mm, uint8_t s) {
  if (y >= 1970)
    y -= 1970;
  uint16_t days = d;
  for (uint8_t i = 1; i < m; ++i)
    days += pgm_read_byte(daysInMonth + i - 1);
  if (m > 2 && y % 4 == 0)
    ++days;
  days += 365 * y + (y + 3) / 4 - 1;
  return days * 24L * 3600L + h * 3600L + mm * 60L + s + seventyYears;
}

oja,zodra ik //processNTP(); aan zet, stop de modbus er mee?

Ik heb nu de onderstaande code…
en nu loopt de modbus en NTP server wel samen, dus dat is opgelost…

maar helemaal werken doet het nog niet…

Ik heb nu een latop aangesloten, en probeer de tijd te synchroniseren met de time server in de arduino…

maar hij komt dus niet op de zelfde tijd te staan…

wel zie ik in de seriele monitor het volgende langs komen:

14:41:29.800 -> Received UDP packet size 48
14:41:29.800 -> From 192.168.3.233, port 123  LI, Vers, Mode :DB  Stratum :0  Polling :11  Precision :E9
14:41:29.892 -> DB011E9
14:41:29.938 -> 0000
14:41:29.938 -> 0100
14:41:29.938 -> 0000
14:41:29.938 -> E42193C6
14:41:29.938 -> 5BB1D77E
14:41:29.983 -> 0000
14:41:29.983 -> 0000
14:41:29.983 -> 0000
14:41:29.983 -> 0000
14:41:29.983 -> E433F28C
14:41:29.983 -> C3A180EE
14:41:29.983 -> 
14:41:29.983 -> 3828609677

en ik zie in windows Laatste geslaagde synchronisatie :28-4-2021 16:41:17
Tijdserver:192.168.3.250

maar in de arduino was de tijd 14:41:17

maar ik begrijp ook niet goed hoe die tijd nu in dat NTP verhaal gezet moet worden…
En hoe ik kan controleren of het daar wel goed in staat?

begrijp die timestamp niet zo?

#include <SPI.h>
#include <Ethernet.h>
#include "MgsModbus.h"
#include <EthernetUdp.h>   // UDP library from: bjoern@cs.stanford.edu 12/30/2008

#include <UTFTGLUE.h>              //use GLUE class and constructor
UTFTGLUE myGLCD(0, A2, A1, A3, A4, A0); //all dummy args

#define debug true


// Time Server Port
#define NTP_PORT 123

MgsModbus Mb;

// Ethernet settings (depending on MAC and Local network)
byte mac[] = {0x90, 0xA2, 0xDA, 0x0E, 0x94, 0xB5 };
IPAddress ip(192, 168, 3, 250);
IPAddress gateway(192, 168, 3, 100);
IPAddress subnet(255, 255, 255, 0);

static const int NTP_PACKET_SIZE = 48;

// buffers for receiving and sending data
byte packetBuffer[NTP_PACKET_SIZE];


// An Ethernet UDP instance
EthernetUDP Udp;

int year;
byte month, day, hour, minute, second, hundredths;
unsigned long date, time, age;
uint32_t timestamp, tempval;

void setup()
{
  // serial setup
  Serial.begin(9600);
  Serial.println("Serial interface started");

  // initialize the ethernet device
  Ethernet.begin(mac, ip, gateway, subnet);   // start etehrnet interface
  Serial.println("Ethernet interface started");

    Udp.begin(NTP_PORT);

  // print your local IP address:
  Serial.print("My IP address: ");
  for (byte thisByte = 0; thisByte < 4; thisByte++) {
    // print the value of each byte of the IP address:
    Serial.print(Ethernet.localIP()[thisByte], DEC);
    Serial.print(".");
  }

  myGLCD.InitLCD();

  // Clear the screen
  myGLCD.clrScr();
  //myGLCD.setFont(WideFont);
  myGLCD.setColor(100, 255, 100);
  myGLCD.setBackColor(0, 0, 0);
  myGLCD.fillScr(0, 0, 0);

  myGLCD.setTextSize(8);
}

void loop()
{
  Mb.MbsRun();
  myGLCD.printNumI( Mb.MbData[2], 0, 100);
  myGLCD.printNumI( Mb.MbData[1], 110, 100);
  myGLCD.printNumI( Mb.MbData[0], 220, 100);

  myGLCD.printNumI( Mb.MbData[3], 0, 200);
  myGLCD.printNumI( Mb.MbData[4], 110, 200);
  myGLCD.printNumI( Mb.MbData[5], 220, 200);

//  Serial.print(Mb.MbData[2]);
//  Serial.print(":");
//  Serial.print(Mb.MbData[1]);
//  Serial.print(":");
//  Serial.print(Mb.MbData[0]);
//  Serial.print(" - ");
//  Serial.print(Mb.MbData[3]);
//  Serial.print("/");
//  Serial.print(Mb.MbData[4]);
//  Serial.print("/");
//  Serial.println(Mb.MbData[5]);

  year = Mb.MbData[5];
  month = Mb.MbData[4];
  day = Mb.MbData[3];
  hour = Mb.MbData[2];
  minute = Mb.MbData[1];
  second = Mb.MbData[0];

  processNTP();

}


void processNTP() {

  // if there's data available, read a packet
  int packetSize = Udp.parsePacket();
  if (packetSize)
  {
    Udp.read(packetBuffer, NTP_PACKET_SIZE);
    IPAddress Remote = Udp.remoteIP();
    int PortNum = Udp.remotePort();

#if debug
    Serial.println();
    Serial.print("Received UDP packet size ");
    Serial.println(packetSize);
    Serial.print("From ");

    for (int i = 0; i < 4; i++)
    {
      Serial.print(Remote[i], DEC);
      if (i < 3)
      {
        Serial.print(".");
      }
    }
    Serial.print(", port ");
    Serial.print(PortNum);

    byte LIVNMODE = packetBuffer[0];
    Serial.print("  LI, Vers, Mode :");
    Serial.print(packetBuffer[0], HEX);

    byte STRATUM = packetBuffer[1];
    Serial.print("  Stratum :");
    Serial.print(packetBuffer[1], HEX);

    byte POLLING = packetBuffer[2];
    Serial.print("  Polling :");
    Serial.print(packetBuffer[2], HEX);

    byte PRECISION = packetBuffer[3];
    Serial.print("  Precision :");
    Serial.println(packetBuffer[3], HEX);

    for (int z = 0; z < NTP_PACKET_SIZE; z++) {
      Serial.print(packetBuffer[z], HEX);
      if (((z + 1) % 4) == 0) {
        Serial.println();
      }
    }
    Serial.println();

#endif


    packetBuffer[0] = 0b00100100;   // LI, Version, Mode
    packetBuffer[1] = 1 ;   // stratum
    packetBuffer[2] = 6 ;   // polling minimum
    packetBuffer[3] = 0xFA; // precision

    packetBuffer[7] = 0; // root delay
    packetBuffer[8] = 0;
    packetBuffer[9] = 8;
    packetBuffer[10] = 0;

    packetBuffer[11] = 0; // root dispersion
    packetBuffer[12] = 0;
    packetBuffer[13] = 0xC;
    packetBuffer[14] = 0;

    timestamp = numberOfSecondsSince1900Epoch(year, month, day, hour, minute, second);

    Serial.println(timestamp);

    tempval = timestamp;

    packetBuffer[12] = 71; //"G";
    packetBuffer[13] = 80; //"P";
    packetBuffer[14] = 83; //"S";
    packetBuffer[15] = 0; //"0";

    // reference timestamp
    packetBuffer[16] = (tempval >> 24) & 0XFF;
    tempval = timestamp;
    packetBuffer[17] = (tempval >> 16) & 0xFF;
    tempval = timestamp;
    packetBuffer[18] = (tempval >> 8) & 0xFF;
    tempval = timestamp;
    packetBuffer[19] = (tempval) & 0xFF;

    packetBuffer[20] = 0;
    packetBuffer[21] = 0;
    packetBuffer[22] = 0;
    packetBuffer[23] = 0;


    //copy originate timestamp from incoming UDP transmit timestamp
    packetBuffer[24] = packetBuffer[40];
    packetBuffer[25] = packetBuffer[41];
    packetBuffer[26] = packetBuffer[42];
    packetBuffer[27] = packetBuffer[43];
    packetBuffer[28] = packetBuffer[44];
    packetBuffer[29] = packetBuffer[45];
    packetBuffer[30] = packetBuffer[46];
    packetBuffer[31] = packetBuffer[47];

    //receive timestamp
    packetBuffer[32] = (tempval >> 24) & 0XFF;
    tempval = timestamp;
    packetBuffer[33] = (tempval >> 16) & 0xFF;
    tempval = timestamp;
    packetBuffer[34] = (tempval >> 8) & 0xFF;
    tempval = timestamp;
    packetBuffer[35] = (tempval) & 0xFF;

    packetBuffer[36] = 0;
    packetBuffer[37] = 0;
    packetBuffer[38] = 0;
    packetBuffer[39] = 0;

    //transmitt timestamp
    packetBuffer[40] = (tempval >> 24) & 0XFF;
    tempval = timestamp;
    packetBuffer[41] = (tempval >> 16) & 0xFF;
    tempval = timestamp;
    packetBuffer[42] = (tempval >> 8) & 0xFF;
    tempval = timestamp;
    packetBuffer[43] = (tempval) & 0xFF;

    packetBuffer[44] = 0;
    packetBuffer[45] = 0;
    packetBuffer[46] = 0;
    packetBuffer[47] = 0;


    // Reply to the IP address and port that sent the NTP request

    Udp.beginPacket(Remote, PortNum);
    Udp.write(packetBuffer, NTP_PACKET_SIZE);
    Udp.endPacket();
  }
}


////////////////////////////////////////

const uint8_t daysInMonth [] PROGMEM = {
  31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
}; //const or compiler complains

const unsigned long seventyYears = 2208988800UL; // to convert unix time to epoch

// NTP since 1900/01/01
static unsigned long int numberOfSecondsSince1900Epoch(uint16_t y, uint8_t m, uint8_t d, uint8_t h, uint8_t mm, uint8_t s) {
  if (y >= 1970)
    y -= 1970;
  uint16_t days = d;
  for (uint8_t i = 1; i < m; ++i)
    days += pgm_read_byte(daysInMonth + i - 1);
  if (m > 2 && y % 4 == 0)
    ++days;
  days += 365 * y + (y + 3) / 4 - 1;
  return days * 24L * 3600L + h * 3600L + mm * 60L + s + seventyYears;
}

Hoi.

Ik weet ook niet precies hoe NTP data word verstuurd, maar de tijd die je ontvangt klopt.
Want je hebt een verschil van precies 2 uur.
En dat is het verschil tussen CET + zomertijd en GMT, de basis van tijd.
Jouw PC is ingesteld op de tijdzone die bij Brussel en Amsterdam hoort (CET, Centraal Europese Tijd dus) plus zomertijd.
Die hoort ook in jouw Arduino en / of de modbus bron te staan, maar schijnbaar is dat niet het geval.
Daarom zou jij dus 2 keer een offset moeten gaan bedenken, 1 die het verschil tussen GMT (Greenwich Mean Time) instelt, en 1 die wel of niet zomertijd instelt.
Ik denk dat je dat het best in de code van je Arduino inbouwt, maar ik heb geen idee hoe de data in die code verpakt zit.

O… zo ver had ik eigenlijk helemaal nog niet na gedacht…
de laptop stond inderdaad op Brussel,Amsterdam…

dus pff… moeilijk verhaal weer… want in de plc heb ik geloof ik nergens iets van die tijd grenzen…

en ik heb een aantal HMI schermen, welke draaien op (touchscreen) PC’s… tis eigenlijk wel raar, wij werken veel met schneider XBT HMI schermen… en die kan je normaal wel allemaal op de zelfde tijd zetten… maar nu hebben ze ook een runtime versie van die schermen (software) welke je op een PC kan laten lopen…( dit omdat ze steeds grotere schermen willen, ik heb op 1 schip het al draaien op een 60inch TV scherm… :slight_smile: https://youtu.be/02KizgpKTRo?t=130 ) maar het lukt dus niet met de bestaande software de klokken van die PC goed te zetten… en daar heb ik nu een hoop gedoe mee… dat er bij Alarmen een andere tijd staat, omdat de tijd van het alarm uit de PLC komt, en die kan dus anders staan als de PLC… dus ik dacht misschien kan ik op deze manier iets maken…

dus waarschijnlijk kan ik dit dus wel werkend krijgen… nu alleen nog iets verzinnen dat het overal over de hele wereld werkt… (misschien de laptop op Engeland zetten ofzo… ) ik ga morgen verder testen…

Kijk, kleine wereld weer.
Mijn werk heeft wel raakvlakken met het jouwe.

Aangezien je aan boord telkens in een andere tijdzone zit, is het raadzaam om aan boord alles in dezelfde zone in te stellen.
Om het jezelf gemakkelijker te maken raad ik daarom aan om alles op GMT te zetten.
Tijdzones worden aangegeven met GMT +(tijdverschil).
En dat tijdverschil kan ook negatief zijn.
De AIS aan boord werkt ook met GMT en die kun je niet op lokale tijd zetten (zoals dat met de meeste GPS displays wel kan), en dat heeft ook als reden dat er daardoor geen misverstanden kunnen ontstaan voor wat betreft ETA.

Overigens is een GPS (kompas) een heel strakke tijdbron, want gebaseerd op een atoomklok en alle satellieten zijn gesynchroniseerd (want anders werkt het hele systeem erg onnauwkeurig).
Dus als je daar een lijntje vandaan kunt halen kun je dat wel als bron voor je NTP server gebruiken.
Houd er wel rekening mee dat de server ook een vertraging maakt en de tijd dus ietsje achter kan lopen.
Maar ik denk niet dat je een probleem zult hebben met een seconde verschil.

Dat is opzich ook een idee… kan ik ook eens naar informeren… want ook mijn PLC kan ik een NTP time server configureren… dan zou ik me schermPC’s en de PLC allemaal naar de zelfde kunnen laten kijken, en dan heb ik ook dat wat ik wil… bedankt voor de Tip!!!

ik had wel van schneider bijvoorbeeld de tip gehad om alles aan een time server te koppelen… maar ja… ik heb niet altijd internet aan boord, alleen (ook niet eens bij alle systemen, sommige systemen heb ik nooit internet…) alleen in de haven… daar kan ik misschien ook nog wel wat mee… want het systeem is voor laden/lossen, en dan zijn ze altijd in een haven…

want ja dat is ook het probleem… ik heb een aantal schepen die echt door verschillende tijdzones vaart, 1 bijvoorbeeld rond Australie…

Je moet dan wel nog steeds een NTP server opzetten, want uit een GPS komt geen NTP signaal.
Daarom zei ik ook bron voor je NTP server.
Uit een GPS haal je het best de $GPZDA sentence.
Deze bevat alleen de UTC / GMT tijd en de datum, en zelfs het tijdverschil voor de tijdzone waarin je je bevindt (ik weet niet zeker of deze laatste altijd beschikbaar zal zijn).
Maar UTC tijd en datum zitten ook in bijvoorbeeld de $GPRMC sentence, en die is vrijwel altijd direct beschikbaar.

Ik ga eerst eens uitzoeken wat ze bij de meeste schepen aan boord hebben… maar je heb me dus een tip gegeven in de goede richting!