001/* 002 * Copyright (C) Photon Vision. 003 * 004 * This program is free software: you can redistribute it and/or modify 005 * it under the terms of the GNU General Public License as published by 006 * the Free Software Foundation, either version 3 of the License, or 007 * (at your option) any later version. 008 * 009 * This program is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 012 * GNU General Public License for more details. 013 * 014 * You should have received a copy of the GNU General Public License 015 * along with this program. If not, see <https://www.gnu.org/licenses/>. 016 */ 017 018package org.photonvision.jni; 019 020import edu.wpi.first.networktables.NetworkTablesJNI; 021 022/** 023 * Send ping-pongs to estimate server time, relative to nt::Now. The underlying implementation does 024 * technically allow us to provide a different source, but all photon code assumes nt::Now is used 025 */ 026public class TimeSyncClient { 027 public static class PingMetadata { 028 // offset, us 029 public long offset; 030 // outgoing count 031 public long pingsSent; 032 // incoming count 033 public long pongsReceived; 034 // when we last heard back from the server, uS, in local time base 035 public long lastPongTime; 036 // RTT2, time from ping send to pong receive at the client, uS 037 public long rtt2; 038 039 public PingMetadata( 040 long offset, long pingsSent, long pongsReceived, long lastPongTime, long rtt2) { 041 this.offset = offset; 042 this.pingsSent = pingsSent; 043 this.pongsReceived = pongsReceived; 044 this.lastPongTime = lastPongTime; 045 this.rtt2 = rtt2; 046 } 047 048 @Override 049 public String toString() { 050 return "PingMetadata [offset=" 051 + offset 052 + ", pingsSent=" 053 + pingsSent 054 + ", pongsReceived=" 055 + pongsReceived 056 + ", lastPongTime=" 057 + lastPongTime 058 + ", rtt2=" 059 + rtt2 060 + "]"; 061 } 062 063 /** 064 * How long, in us, since we last heard back from the server 065 * 066 * @return Time between last pong RX and now, or Long.MAX_VALUE if we have heard zero pongs 067 */ 068 public long timeSinceLastPong() { 069 // If no pongs, it's been forever 070 if (pongsReceived < 1) { 071 return Long.MAX_VALUE; 072 } 073 074 return NetworkTablesJNI.now() - lastPongTime; 075 } 076 } 077 078 private final Object mutex = new Object(); 079 080 private long handle; 081 private String server; 082 private int port; 083 private double interval; 084 085 public TimeSyncClient(String server, int port, double interval) { 086 this.server = server; 087 this.port = port; 088 this.interval = interval; 089 090 this.handle = TimeSyncClient.create(server, port, interval); 091 TimeSyncClient.start(handle); 092 } 093 094 public void setServer(String newServer) { 095 if (!server.equals(newServer)) { 096 synchronized (mutex) { 097 stop(); 098 this.handle = TimeSyncClient.create(newServer, port, interval); 099 TimeSyncClient.start(handle); 100 this.server = newServer; 101 } 102 } 103 } 104 105 public void stop() { 106 synchronized (mutex) { 107 if (handle != 0) { 108 TimeSyncClient.stop(handle); 109 handle = 0; 110 } 111 } 112 } 113 114 /** 115 * This offset, when added to the current value of nt::now(), yields the timestamp in the timebase 116 * of the TSP Server 117 * 118 * @return 119 */ 120 public long getOffset() { 121 synchronized (mutex) { 122 if (handle != 0) { 123 return TimeSyncClient.getOffset(handle); 124 } 125 126 System.err.println("TimeSyncClient: use after free?"); 127 return 0; 128 } 129 } 130 131 /** 132 * Best estimate of the current timestamp at the TSP server 133 * 134 * @return The current time estimate, in microseconds, at the TSP server 135 */ 136 public long currentServerTimestamp() { 137 return NetworkTablesJNI.now() + getOffset(); 138 } 139 140 public PingMetadata getPingMetadata() { 141 synchronized (mutex) { 142 if (handle != 0) { 143 return TimeSyncClient.getLatestMetadata(handle); 144 } 145 146 System.err.println("TimeSyncClient: use after free?"); 147 return new PingMetadata(0, 0, 0, 0, 0); 148 } 149 } 150 151 public String getServer() { 152 return server; 153 } 154 155 private static native long create(String serverIP, int serverPort, double pingIntervalSeconds); 156 157 private static native void start(long handle); 158 159 private static native void stop(long handle); 160 161 private static native long getOffset(long handle); 162 163 private static native PingMetadata getLatestMetadata(long handle); 164}