From f9ef82473ceeeee1e2a3084dab2ae3a42e690932 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Thu, 20 Jun 2024 06:28:26 +0200 Subject: [PATCH 1/4] Adding class NTPUtils for obtaining a NTP timestamp via the internet. --- src/Arduino_open62541.h | 1 + src/NTPUtils.cpp | 71 +++++++++++++++++++++++++++++++++++++++++ src/NTPUtils.h | 44 +++++++++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 src/NTPUtils.cpp create mode 100644 src/NTPUtils.h diff --git a/src/Arduino_open62541.h b/src/Arduino_open62541.h index 4ca5ed8..3e6a57c 100644 --- a/src/Arduino_open62541.h +++ b/src/Arduino_open62541.h @@ -15,6 +15,7 @@ #include "open62541.h" #include "o1heap/o1heap.h" +#include "NTPUtils.h" #include "ArduinoOpta.h" #include "ArduinoOptaVariant.h" diff --git a/src/NTPUtils.cpp b/src/NTPUtils.cpp new file mode 100644 index 0000000..344451b --- /dev/null +++ b/src/NTPUtils.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2024 Arduino + * + * SPDX-License-Identifier: MPL-2.0 + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +/************************************************************************************** + * INCLUDE + **************************************************************************************/ + +#include "NTPUtils.h" + +/************************************************************************************** + * PUBLIC MEMBER FUNCTIONS + **************************************************************************************/ + +unsigned long NTPUtils::getTime(UDP & udp) +{ + udp.begin(NTP_LOCAL_PORT); + + sendNTPpacket(udp); + + bool is_timeout = false; + unsigned long const start = millis(); + do + { + is_timeout = (millis() - start) >= NTP_TIMEOUT_MS; + } while(!is_timeout && !udp.parsePacket()); + + if(is_timeout) { + udp.stop(); + return 0; + } + + uint8_t ntp_packet_buf[NTP_PACKET_SIZE]; + udp.read(ntp_packet_buf, NTP_PACKET_SIZE); + udp.stop(); + + unsigned long const highWord = word(ntp_packet_buf[40], ntp_packet_buf[41]); + unsigned long const lowWord = word(ntp_packet_buf[42], ntp_packet_buf[43]); + unsigned long const secsSince1900 = highWord << 16 | lowWord; + unsigned long const seventyYears = 2208988800UL; + unsigned long const epoch = secsSince1900 - seventyYears; + + return epoch; +} + +/************************************************************************************** + * PRIVATE MEMBER FUNCTIONS + **************************************************************************************/ + +void NTPUtils::sendNTPpacket(UDP & udp) +{ + uint8_t ntp_packet_buf[NTP_PACKET_SIZE] = {0}; + + ntp_packet_buf[0] = 0b11100011; + ntp_packet_buf[1] = 0; + ntp_packet_buf[2] = 6; + ntp_packet_buf[3] = 0xEC; + ntp_packet_buf[12] = 49; + ntp_packet_buf[13] = 0x4E; + ntp_packet_buf[14] = 49; + ntp_packet_buf[15] = 52; + + udp.beginPacket(NTP_TIME_SERVER, NTP_TIME_SERVER_PORT); + udp.write(ntp_packet_buf, NTP_PACKET_SIZE); + udp.endPacket(); +} diff --git a/src/NTPUtils.h b/src/NTPUtils.h new file mode 100644 index 0000000..c56133d --- /dev/null +++ b/src/NTPUtils.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 Arduino + * + * SPDX-License-Identifier: MPL-2.0 + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +/* + This Utility Class is derived from the example code found here https://www.arduino.cc/en/Tutorial/UdpNTPClient + For more information on NTP (Network Time Protocol) you can refer to this Wikipedia article https://en.wikipedia.org/wiki/Network_Time_Protocol +*/ + +/************************************************************************************** + * INCLUDE + **************************************************************************************/ + +#include +#include + +/************************************************************************************** + * CLASS DECLARATION + **************************************************************************************/ + +class NTPUtils +{ +public: + + static unsigned long getTime(UDP & udp); + +private: + + static size_t const NTP_PACKET_SIZE = 48; + static int const NTP_TIME_SERVER_PORT = 123; + static int const NTP_LOCAL_PORT = 8888; + + static unsigned long const NTP_TIMEOUT_MS = 1000; + static constexpr const char * NTP_TIME_SERVER = "time.arduino.cc"; + + static void sendNTPpacket(UDP & udp); +}; From 3efe47189989e05ce05304508e9936da26dcc49b Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Thu, 20 Jun 2024 06:31:43 +0200 Subject: [PATCH 2/4] Move NTPUtils into namespace OPC/UA in order to prevent clashes with other classes. --- examples/opcua_server/opcua_server.ino | 9 ++++++ src/NTPUtils.cpp | 45 +++++++++++++++++--------- src/NTPUtils.h | 27 ++++++++++++---- 3 files changed, 59 insertions(+), 22 deletions(-) diff --git a/examples/opcua_server/opcua_server.ino b/examples/opcua_server/opcua_server.ino index b5a84da..a428662 100644 --- a/examples/opcua_server/opcua_server.ino +++ b/examples/opcua_server/opcua_server.ino @@ -188,6 +188,15 @@ void setup() for (;;) { } } + /* Try and obtain the current time via NTP and configure the Arduino + * Opta's onboard RTC accordingly. The RTC is then used inside the + * open62541 Arduino wrapper to obtain the correct timestamps for + * the OPC/UA server. + */ + EthernetUDP udp_client; + auto const epoch = opcua::NTPUtils::getTime(udp_client); + Serial.print("epoch = "); Serial.println(epoch); + /* Initialize heap memory. */ o1heap_ins = o1heapInit(OPC_UA_SERVER_THREAD_HEAP.data(), OPC_UA_SERVER_THREAD_HEAP.size()); if (o1heap_ins == nullptr) { diff --git a/src/NTPUtils.cpp b/src/NTPUtils.cpp index 344451b..5aac874 100644 --- a/src/NTPUtils.cpp +++ b/src/NTPUtils.cpp @@ -13,11 +13,19 @@ #include "NTPUtils.h" + +/************************************************************************************** + * NAMESPACE + **************************************************************************************/ + +namespace opcua +{ + /************************************************************************************** * PUBLIC MEMBER FUNCTIONS **************************************************************************************/ -unsigned long NTPUtils::getTime(UDP & udp) +unsigned long NTPUtils::getTime(UDP &udp) { udp.begin(NTP_LOCAL_PORT); @@ -28,22 +36,23 @@ unsigned long NTPUtils::getTime(UDP & udp) do { is_timeout = (millis() - start) >= NTP_TIMEOUT_MS; - } while(!is_timeout && !udp.parsePacket()); + } while (!is_timeout && !udp.parsePacket()); - if(is_timeout) { + if (is_timeout) + { udp.stop(); return 0; } - + uint8_t ntp_packet_buf[NTP_PACKET_SIZE]; udp.read(ntp_packet_buf, NTP_PACKET_SIZE); udp.stop(); - unsigned long const highWord = word(ntp_packet_buf[40], ntp_packet_buf[41]); - unsigned long const lowWord = word(ntp_packet_buf[42], ntp_packet_buf[43]); + unsigned long const highWord = word(ntp_packet_buf[40], ntp_packet_buf[41]); + unsigned long const lowWord = word(ntp_packet_buf[42], ntp_packet_buf[43]); unsigned long const secsSince1900 = highWord << 16 | lowWord; - unsigned long const seventyYears = 2208988800UL; - unsigned long const epoch = secsSince1900 - seventyYears; + unsigned long const seventyYears = 2208988800UL; + unsigned long const epoch = secsSince1900 - seventyYears; return epoch; } @@ -52,20 +61,26 @@ unsigned long NTPUtils::getTime(UDP & udp) * PRIVATE MEMBER FUNCTIONS **************************************************************************************/ -void NTPUtils::sendNTPpacket(UDP & udp) +void NTPUtils::sendNTPpacket(UDP &udp) { uint8_t ntp_packet_buf[NTP_PACKET_SIZE] = {0}; - - ntp_packet_buf[0] = 0b11100011; - ntp_packet_buf[1] = 0; - ntp_packet_buf[2] = 6; - ntp_packet_buf[3] = 0xEC; + + ntp_packet_buf[0] = 0b11100011; + ntp_packet_buf[1] = 0; + ntp_packet_buf[2] = 6; + ntp_packet_buf[3] = 0xEC; ntp_packet_buf[12] = 49; ntp_packet_buf[13] = 0x4E; ntp_packet_buf[14] = 49; ntp_packet_buf[15] = 52; - + udp.beginPacket(NTP_TIME_SERVER, NTP_TIME_SERVER_PORT); udp.write(ntp_packet_buf, NTP_PACKET_SIZE); udp.endPacket(); } + +/************************************************************************************** + * NAMESPACE + **************************************************************************************/ + +} /* opcua */ diff --git a/src/NTPUtils.h b/src/NTPUtils.h index c56133d..b11a216 100644 --- a/src/NTPUtils.h +++ b/src/NTPUtils.h @@ -21,6 +21,13 @@ #include #include +/************************************************************************************** + * NAMESPACE + **************************************************************************************/ + +namespace opcua +{ + /************************************************************************************** * CLASS DECLARATION **************************************************************************************/ @@ -29,16 +36,22 @@ class NTPUtils { public: - static unsigned long getTime(UDP & udp); + static unsigned long getTime(UDP &udp); private: - static size_t const NTP_PACKET_SIZE = 48; - static int const NTP_TIME_SERVER_PORT = 123; - static int const NTP_LOCAL_PORT = 8888; + static size_t const NTP_PACKET_SIZE = 48; + static int const NTP_TIME_SERVER_PORT = 123; + static int const NTP_LOCAL_PORT = 8888; - static unsigned long const NTP_TIMEOUT_MS = 1000; - static constexpr const char * NTP_TIME_SERVER = "time.arduino.cc"; + static unsigned long const NTP_TIMEOUT_MS = 1000; + static constexpr const char *NTP_TIME_SERVER = "time.arduino.cc"; - static void sendNTPpacket(UDP & udp); + static void sendNTPpacket(UDP &udp); }; + +/************************************************************************************** + * NAMESPACE + **************************************************************************************/ + +} /* opcua */ From 4eff67f7b09983d7e6fa5889963a7d60e722c4bf Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Thu, 20 Jun 2024 06:51:12 +0200 Subject: [PATCH 3/4] Use timestmap from NTP client to configure Arduino Opta's onboard RTC. Then use the RTC timestamp to obtain the current time. --- examples/opcua_server/opcua_server.ino | 5 ++++- src/arch/posix/ua_clock.cpp | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/examples/opcua_server/opcua_server.ino b/examples/opcua_server/opcua_server.ino index a428662..4960b3a 100644 --- a/examples/opcua_server/opcua_server.ino +++ b/examples/opcua_server/opcua_server.ino @@ -4,6 +4,7 @@ #include "PortentaEthernet.h" #include "Arduino_open62541.h" +#include #ifndef ARDUINO_OPEN62541_O1HEAP_DEBUG # define ARDUINO_OPEN62541_O1HEAP_DEBUG (0) /* Change to (1) if you want to see debug messages on Serial concerning o1heap memory calls. */ @@ -195,7 +196,9 @@ void setup() */ EthernetUDP udp_client; auto const epoch = opcua::NTPUtils::getTime(udp_client); - Serial.print("epoch = "); Serial.println(epoch); + if (epoch > 0) { + set_time(epoch); /* Directly set RTC of Arduino Opta. */ + } /* Initialize heap memory. */ o1heap_ins = o1heapInit(OPC_UA_SERVER_THREAD_HEAP.data(), OPC_UA_SERVER_THREAD_HEAP.size()); diff --git a/src/arch/posix/ua_clock.cpp b/src/arch/posix/ua_clock.cpp index dab6b3f..5bdbbfb 100644 --- a/src/arch/posix/ua_clock.cpp +++ b/src/arch/posix/ua_clock.cpp @@ -18,8 +18,11 @@ extern "C" { int clock_gettime(clockid_t clk_id, struct timespec *tp) { - tp->tv_sec = millis() / 1000; - tp->tv_nsec = (millis() % 1000) * 1000000; + /* Obtain time from RTC. */ + time_t const epoch = time(NULL); + /* No nanosecond resolution. */ + tp->tv_sec = epoch; + tp->tv_nsec = 0; return 0; } From 139228cfd532951a40c2fb2f3d52c42d3d049ae2 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Thu, 20 Jun 2024 07:05:58 +0200 Subject: [PATCH 4/4] Use compile time timestamp as NTP backup. --- examples/opcua_server/opcua_server.ino | 2 + src/Arduino_open62541.h | 1 + src/cvt_time.cpp | 70 ++++++++++++++++++++++++++ src/cvt_time.h | 35 +++++++++++++ 4 files changed, 108 insertions(+) create mode 100644 src/cvt_time.cpp create mode 100644 src/cvt_time.h diff --git a/examples/opcua_server/opcua_server.ino b/examples/opcua_server/opcua_server.ino index 4960b3a..cbdc533 100644 --- a/examples/opcua_server/opcua_server.ino +++ b/examples/opcua_server/opcua_server.ino @@ -198,6 +198,8 @@ void setup() auto const epoch = opcua::NTPUtils::getTime(udp_client); if (epoch > 0) { set_time(epoch); /* Directly set RTC of Arduino Opta. */ + } else { + set_time(opcua::cvt_time(__DATE__)); /* Configure Arduino Opta with time at compile time as last time of defense. */ } /* Initialize heap memory. */ diff --git a/src/Arduino_open62541.h b/src/Arduino_open62541.h index 3e6a57c..68fe6a4 100644 --- a/src/Arduino_open62541.h +++ b/src/Arduino_open62541.h @@ -16,6 +16,7 @@ #include "open62541.h" #include "o1heap/o1heap.h" #include "NTPUtils.h" +#include "cvt_time.h" #include "ArduinoOpta.h" #include "ArduinoOptaVariant.h" diff --git a/src/cvt_time.cpp b/src/cvt_time.cpp new file mode 100644 index 0000000..46e6d6a --- /dev/null +++ b/src/cvt_time.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024 Arduino + * + * SPDX-License-Identifier: MPL-2.0 + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +/************************************************************************************** + * INCLUDE + **************************************************************************************/ + +#include "cvt_time.h" + +#include +#include + +/************************************************************************************** + * NAMESPACE + **************************************************************************************/ + +namespace opcua +{ + +/************************************************************************************** + * FUNCTION DEFINITION + **************************************************************************************/ + +time_t cvt_time(char const * time) +{ + static time_t build_time = 0; + + if (!build_time) { + char s_month[5]; + int month, day, year; + struct tm t = + { + 0 /* tm_sec */, + 0 /* tm_min */, + 0 /* tm_hour */, + 0 /* tm_mday */, + 0 /* tm_mon */, + 0 /* tm_year */, + 0 /* tm_wday */, + 0 /* tm_yday */, + 0 /* tm_isdst */ + }; + static const char month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec"; + + sscanf(time, "%s %d %d", s_month, &day, &year); + + month = (strstr(month_names, s_month) - month_names) / 3; + + t.tm_mon = month; + t.tm_mday = day; + t.tm_year = year - 1900; + t.tm_isdst = -1; + + build_time = mktime(&t); + } + + return build_time; +} + +/************************************************************************************** + * NAMESPACE + **************************************************************************************/ + +} /* opcua */ diff --git a/src/cvt_time.h b/src/cvt_time.h new file mode 100644 index 0000000..231d182 --- /dev/null +++ b/src/cvt_time.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024 Arduino + * + * SPDX-License-Identifier: MPL-2.0 + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +/************************************************************************************** + * INCLUDE + **************************************************************************************/ + +#include + +/************************************************************************************** + * NAMESPACE + **************************************************************************************/ + +namespace opcua +{ + +/************************************************************************************** + * FUNCTION DECLARATION + **************************************************************************************/ + +time_t cvt_time(char const * time); + +/************************************************************************************** + * NAMESPACE + **************************************************************************************/ + +} /* opcua */