Skip to content

Commit

Permalink
Merge pull request #6 from bcmi-labs/feature/ntp-time-sync
Browse files Browse the repository at this point in the history
Use NTP to synchronize time of internal RTC for usage by OPC/UA server
  • Loading branch information
aentinger authored Jul 17, 2024
2 parents 32830bd + 139228c commit 5407a89
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 2 deletions.
14 changes: 14 additions & 0 deletions examples/opcua_server/opcua_server.ino
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "PortentaEthernet.h"
#include "Arduino_open62541.h"
#include <mbed_rtc_time.h>

#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. */
Expand Down Expand Up @@ -225,6 +226,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) {
Expand Down
2 changes: 2 additions & 0 deletions src/Arduino_open62541.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

#include "open62541.h"
#include "o1heap/o1heap.h"
#include "NTPUtils.h"
#include "cvt_time.h"

#include "ArduinoOpta.h"
#include "ArduinoOptaVariant.h"
Expand Down
86 changes: 86 additions & 0 deletions src/NTPUtils.cpp
Original file line number Diff line number Diff line change
@@ -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 */
57 changes: 57 additions & 0 deletions src/NTPUtils.h
Original file line number Diff line number Diff line change
@@ -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 <Arduino.h>
#include <Udp.h>

/**************************************************************************************
* 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 */
7 changes: 5 additions & 2 deletions src/arch/posix/ua_clock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
70 changes: 70 additions & 0 deletions src/cvt_time.cpp
Original file line number Diff line number Diff line change
@@ -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 <stdio.h>
#include <string.h>

/**************************************************************************************
* 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 */
35 changes: 35 additions & 0 deletions src/cvt_time.h
Original file line number Diff line number Diff line change
@@ -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 <time.h>

/**************************************************************************************
* NAMESPACE
**************************************************************************************/

namespace opcua
{

/**************************************************************************************
* FUNCTION DECLARATION
**************************************************************************************/

time_t cvt_time(char const * time);

/**************************************************************************************
* NAMESPACE
**************************************************************************************/

} /* opcua */

0 comments on commit 5407a89

Please sign in to comment.