Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions examples/udp_ntp_client/udp_ntp_client.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
Udp NTP Client

Get the time from a Network Time Protocol (NTP) time server
Demonstrates use of UDP sendPacket and ReceivePacket
For more on NTP time servers and the messages needed to communicate with them,
see https://en.wikipedia.org/wiki/Network_Time_Protocol

Example freely inspired from the Arduino Ethernet library
https://github.com/arduino-libraries/Ethernet/blob/master/examples/UdpNtpClient/UdpNtpClient.ino

This code is in the public domain.
*/

#include <Arduino_RouterBridge.h>

unsigned int localPort = 8888; // local port to listen for UDP packets
const char timeServer[] = "time.nist.gov"; // time.nist.gov NTP server
int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message

// A UDP instance to let us send and receive packets over UDP
BridgeUDP<4096> Udp(Bridge);

void setup() {
Monitor.begin();
Udp.begin(localPort);
}

void loop() {
sendNTPpacket(timeServer); // send an NTP packet to a time server

// wait one second to see if a reply is available
Udp.setTimeout(1000);
if (Udp.parsePacket()) {
// We've received a packet, read the data from it
byte packetBuffer[NTP_PACKET_SIZE];
Udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer

// the timestamp starts at byte 40 of the received packet and is four bytes,
// or two words, long.

// First, extract the two words:
unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);

// combine the four bytes (two words) into a long integer
// this is NTP time (seconds since Jan 1 1900):
unsigned long secsSince1900 = highWord << 16 | lowWord;
Monitor.print("Seconds since Jan 1 1900 = ");
Monitor.println(secsSince1900);

// now convert NTP time into everyday time:
// Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
const unsigned long seventyYears = 2208988800UL;
// subtract seventy years:
unsigned long epoch = secsSince1900 - seventyYears;
Monitor.print("Unix time = ");
Monitor.println(epoch);

// print the hour, minute and second:
Monitor.print("The UTC time is "); // UTC is the time at Greenwich Meridian (GMT)
Monitor.print((epoch % 86400L) / 3600); // print the hour (86400 equals secs per day)
Monitor.print(':');
if (((epoch % 3600) / 60) < 10) {
// In the first 10 minutes of each hour, we'll want a leading '0'
Monitor.print('0');
}
Monitor.print((epoch % 3600) / 60); // print the minute (3600 equals secs per minute)
Monitor.print(':');
if ((epoch % 60) < 10) {
// In the first 10 seconds of each minute, we'll want a leading '0'
Monitor.print('0');
}
Monitor.println(epoch % 60); // print the second
}

// wait ten seconds before asking for the time again
delay(10000);
}

// send an NTP request to the time server at the given address
void sendNTPpacket(const char * address) {
byte packetBuffer[NTP_PACKET_SIZE]; // buffer to hold incoming and outgoing packets

// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);

// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011; // LI, Version, Mode
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;

// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
Udp.beginPacket(address, 123); // NTP requests are to port 123
Udp.write(packetBuffer, NTP_PACKET_SIZE);
Udp.endPacket();
}

290 changes: 290 additions & 0 deletions extras/test/udp_echo/python/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
#!/usr/bin/env python3
"""
Simple UDP Echo Server

This server listens for UDP packets and echoes them back to the sender.
It can be used independently or with the Bridge server for testing.

Usage:
python main.py [--port PORT] [--prefix PREFIX]

Examples:
python main.py
python main.py --port 5000
python main.py --port 5000 --prefix "ECHO: "
"""

import socket
import sys
import argparse
import time
from datetime import datetime


def log(msg):
with open("log.log", 'a') as f:
f.write(f"{msg}\n")


class UDPEchoServer:
"""Simple UDP echo server"""

def __init__(self, port=5000, prefix="ECHO: ", buffer_size=4096):
self.port = port
self.prefix = prefix
self.buffer_size = buffer_size
self.socket = None
self.running = False

# Statistics
self.packets_received = 0
self.packets_sent = 0
self.bytes_received = 0
self.bytes_sent = 0
self.start_time = None

def start(self):
"""Start the echo server"""
try:
# Create UDP socket
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# Bind to all interfaces
self.socket.bind(('127.0.0.1', self.port))

log("=" * 60)
log("UDP Echo Server")
log("=" * 60)
log(f"Listening on: 127.0.0.1:{self.port}")
log(f"Echo prefix: \"{self.prefix}\"")
log(f"Buffer size: {self.buffer_size} bytes")
log("=" * 60)
log("Press Ctrl+C to stop\n")

self.running = True
self.start_time = time.time()

self.run()

except PermissionError:
log(f"ERROR: Permission denied. Port {self.port} may require sudo/admin.")
sys.exit(1)
except OSError as e:
log(f"ERROR: Cannot bind to port {self.port}: {e}")
sys.exit(1)
except KeyboardInterrupt:
log("\n\nShutting down...")
self.stop()
except Exception as e:
log(f"ERROR: {e}")
self.stop()
sys.exit(1)

def run(self):
"""Main server loop"""
while self.running:
try:
# Receive data
data, addr = self.socket.recvfrom(self.buffer_size)
log(f"Attempt to read incoming messages")

self.packets_received += 1
self.bytes_received += len(data)

# Print received packet info
timestamp = datetime.now().strftime("%H:%M:%S.%f")[:-3]
log(f"[{timestamp}] Received {len(data)} bytes from {addr[0]}:{addr[1]}")

# Decode and print message (if printable)
try:
message = data.decode('utf-8', errors='ignore')
if message.isprintable() or message.strip():
log(f" Message: \"{message}\"")
except:
log(f" Data: {data[:50]}{'...' if len(data) > 50 else ''}")

# Prepare echo response
if self.prefix:
response = self.prefix.encode() + data
else:
response = data

# Send echo back
sent = self.socket.sendto(response, addr)

self.packets_sent += 1
self.bytes_sent += sent

log(f" Echoed: {sent} bytes\n")

except socket.timeout:
log("UDP socket timeout")
continue
except KeyboardInterrupt:
log("Keyboard interrupt")
raise
except Exception as e:
log(f"Error handling packet: {e}\n")

def stop(self):
"""Stop the server"""
self.running = False

if self.socket:
self.socket.close()

# Print statistics
if self.start_time:
runtime = time.time() - self.start_time

log("\n" + "=" * 60)
log("Server Statistics")
log("=" * 60)
log(f"Runtime: {runtime:.1f} seconds")
log(f"Packets received: {self.packets_received}")
log(f"Packets sent: {self.packets_sent}")
log(f"Bytes received: {self.bytes_received}")
log(f"Bytes sent: {self.bytes_sent}")

if runtime > 0:
log(f"Packets/sec: {self.packets_received / runtime:.2f}")
log(f"Throughput: {self.bytes_received / runtime:.2f} bytes/sec")

log("=" * 60)

log("Server stopped")


def test_server(host='127.0.0.1', port=5000):
"""Test the echo server by sending test packets"""
log("\n" + "=" * 60)
log("Testing Echo Server")
log("=" * 60)
log(f"Target: {host}:{port}\n")

test_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
test_sock.settimeout(2.0)

test_messages = [
"Hello, Echo!",
"Test message 1",
"This is a longer test message with more content",
"12345",
"Special chars: !@#$%^&*()",
]

passed = 0
failed = 0

for i, msg in enumerate(test_messages, 1):
log(f"[Test {i}/{len(test_messages)}] Sending: \"{msg}\"")

try:
# Send
test_sock.sendto(msg.encode(), (host, port))

# Receive
data, addr = test_sock.recvfrom(4096)
response = data.decode('utf-8', errors='ignore')

log(f" Received: \"{response}\"")

# Check if echo contains original message
if msg in response:
log(" ✓ PASS\n")
passed += 1
else:
log(" ✗ FAIL - Response doesn't contain original message\n")
failed += 1

except socket.timeout:
log(" ✗ FAIL - Timeout (no response)\n")
failed += 1
except Exception as e:
log(f" ✗ FAIL - Error: {e}\n")
failed += 1

test_sock.close()

log("=" * 60)
log(f"Test Results: {passed} passed, {failed} failed")
log("=" * 60)


def main():
parser = argparse.ArgumentParser(
description='Simple UDP Echo Server',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s Start server on default port 5000
%(prog)s --port 8888 Start server on port 8888
%(prog)s --prefix "REPLY: " Use custom echo prefix
%(prog)s --no-prefix Echo without prefix
%(prog)s --test Test the server
"""
)

parser.add_argument(
'-p', '--port',
type=int,
default=5000,
help='UDP port to listen on (default: 5000)'
)

parser.add_argument(
'--prefix',
type=str,
default='ECHO: ',
help='Prefix to add to echoed messages (default: "ECHO: ")'
)

parser.add_argument(
'--no-prefix',
action='store_true',
help='Echo without any prefix'
)

parser.add_argument(
'-b', '--buffer-size',
type=int,
default=4096,
help='Buffer size in bytes (default: 4096)'
)

parser.add_argument(
'--test',
action='store_true',
help='Test the echo server by sending test packets'
)

parser.add_argument(
'--test-host',
type=str,
default='127.0.0.1',
help='Host to test (default: 127.0.0.1)'
)

args = parser.parse_args()

# Handle test mode
if args.test:
test_server(args.test_host, args.port)
return

# Handle no-prefix option
prefix = '' if args.no_prefix else args.prefix

# Start server
server = UDPEchoServer(
port=args.port,
prefix=prefix,
buffer_size=args.buffer_size
)

server.start()


if __name__ == "__main__":
main()
Loading
Loading