diff --git a/examples/opcua_server/opcua_server.ino b/examples/opcua_server/opcua_server.ino index b5a84da..cbdc533 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. */ @@ -188,6 +189,19 @@ 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); + 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. */ o1heap_ins = o1heapInit(OPC_UA_SERVER_THREAD_HEAP.data(), OPC_UA_SERVER_THREAD_HEAP.size()); if (o1heap_ins == nullptr) { diff --git a/src/Arduino_open62541.h b/src/Arduino_open62541.h index 4ca5ed8..68fe6a4 100644 --- a/src/Arduino_open62541.h +++ b/src/Arduino_open62541.h @@ -15,6 +15,8 @@ #include "open62541.h" #include "o1heap/o1heap.h" +#include "NTPUtils.h" +#include "cvt_time.h" #include "ArduinoOpta.h" #include "ArduinoOptaVariant.h" diff --git a/src/NTPUtils.cpp b/src/NTPUtils.cpp new file mode 100644 index 0000000..5aac874 --- /dev/null +++ b/src/NTPUtils.cpp @@ -0,0 +1,86 @@ +/* + * 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" + + +/************************************************************************************** + * NAMESPACE + **************************************************************************************/ + +namespace opcua +{ + +/************************************************************************************** + * 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(); +} + +/************************************************************************************** + * NAMESPACE + **************************************************************************************/ + +} /* opcua */ diff --git a/src/NTPUtils.h b/src/NTPUtils.h new file mode 100644 index 0000000..b11a216 --- /dev/null +++ b/src/NTPUtils.h @@ -0,0 +1,57 @@ +/* + * 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 + +/************************************************************************************** + * NAMESPACE + **************************************************************************************/ + +namespace opcua +{ + +/************************************************************************************** + * 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); +}; + +/************************************************************************************** + * NAMESPACE + **************************************************************************************/ + +} /* opcua */ 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; } 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 */