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 org.wpilib.networktables.NetworkTablesJNI; 021 022/** 023 * Send ping-pongs to estimate server time, relative to wpi::nt::Now. The underlying implementation 024 * does technically allow us to provide a different source, but all photon code assumes wpi::nt::Now 025 * is used 026 */ 027public class TimeSyncClient { 028 public static class PingMetadata { 029 // offset, us 030 public long offset; 031 // outgoing count 032 public long pingsSent; 033 // incoming count 034 public long pongsReceived; 035 // when we last heard back from the server, uS, in local time base 036 public long lastPongTime; 037 // RTT2, time from ping send to pong receive at the client, uS 038 public long rtt2; 039 040 public PingMetadata( 041 long offset, long pingsSent, long pongsReceived, long lastPongTime, long rtt2) { 042 this.offset = offset; 043 this.pingsSent = pingsSent; 044 this.pongsReceived = pongsReceived; 045 this.lastPongTime = lastPongTime; 046 this.rtt2 = rtt2; 047 } 048 049 @Override 050 public String toString() { 051 return "PingMetadata [offset=" 052 + offset 053 + ", pingsSent=" 054 + pingsSent 055 + ", pongsReceived=" 056 + pongsReceived 057 + ", lastPongTime=" 058 + lastPongTime 059 + ", rtt2=" 060 + rtt2 061 + "]"; 062 } 063 064 /** 065 * How long, in us, since we last heard back from the server 066 * 067 * @return Time between last pong RX and now, or Long.MAX_VALUE if we have heard zero pongs 068 */ 069 public long timeSinceLastPong() { 070 // If no pongs, it's been forever 071 if (pongsReceived < 1) { 072 return Long.MAX_VALUE; 073 } 074 075 return NetworkTablesJNI.now() - lastPongTime; 076 } 077 } 078 079 private final Object mutex = new Object(); 080 081 private long handle; 082 private String server; 083 private int port; 084 private double interval; 085 086 public TimeSyncClient(String server, int port, double interval) { 087 this.server = server; 088 this.port = port; 089 this.interval = interval; 090 091 this.handle = TimeSyncClient.create(server, port, interval); 092 TimeSyncClient.start(handle); 093 } 094 095 public void setServer(String newServer) { 096 if (!server.equals(newServer)) { 097 synchronized (mutex) { 098 stop(); 099 this.handle = TimeSyncClient.create(newServer, port, interval); 100 TimeSyncClient.start(handle); 101 this.server = newServer; 102 } 103 } 104 } 105 106 public void stop() { 107 synchronized (mutex) { 108 if (handle != 0) { 109 TimeSyncClient.stop(handle); 110 handle = 0; 111 } 112 } 113 } 114 115 /** 116 * This offset, when added to the current value of wpi::nt::now(), yields the timestamp in the 117 * timebase of the TSP Server 118 * 119 * @return 120 */ 121 public long getOffset() { 122 synchronized (mutex) { 123 if (handle != 0) { 124 return TimeSyncClient.getOffset(handle); 125 } 126 127 System.err.println("TimeSyncClient: use after free?"); 128 return 0; 129 } 130 } 131 132 /** 133 * Best estimate of the current timestamp at the TSP server 134 * 135 * @return The current time estimate, in microseconds, at the TSP server 136 */ 137 public long currentServerTimestamp() { 138 return NetworkTablesJNI.now() + getOffset(); 139 } 140 141 public PingMetadata getPingMetadata() { 142 synchronized (mutex) { 143 if (handle != 0) { 144 return TimeSyncClient.getLatestMetadata(handle); 145 } 146 147 System.err.println("TimeSyncClient: use after free?"); 148 return new PingMetadata(0, 0, 0, 0, 0); 149 } 150 } 151 152 public String getServer() { 153 return server; 154 } 155 156 private static native long create(String serverIP, int serverPort, double pingIntervalSeconds); 157 158 private static native void start(long handle); 159 160 private static native void stop(long handle); 161 162 private static native long getOffset(long handle); 163 164 private static native PingMetadata getLatestMetadata(long handle); 165}