001// Copyright (c) FIRST and other WPILib contributors. 002// Open Source Software; you can modify and/or share it under the terms of 003// the WPILib BSD license file in the root directory of this project. 004 005package edu.wpi.first.epilogue.logging; 006 007import edu.wpi.first.networktables.BooleanArrayPublisher; 008import edu.wpi.first.networktables.BooleanPublisher; 009import edu.wpi.first.networktables.DoubleArrayPublisher; 010import edu.wpi.first.networktables.DoublePublisher; 011import edu.wpi.first.networktables.FloatArrayPublisher; 012import edu.wpi.first.networktables.FloatPublisher; 013import edu.wpi.first.networktables.IntegerArrayPublisher; 014import edu.wpi.first.networktables.IntegerPublisher; 015import edu.wpi.first.networktables.NetworkTableInstance; 016import edu.wpi.first.networktables.ProtobufPublisher; 017import edu.wpi.first.networktables.Publisher; 018import edu.wpi.first.networktables.RawPublisher; 019import edu.wpi.first.networktables.StringArrayPublisher; 020import edu.wpi.first.networktables.StringPublisher; 021import edu.wpi.first.networktables.StructArrayPublisher; 022import edu.wpi.first.networktables.StructPublisher; 023import edu.wpi.first.util.protobuf.Protobuf; 024import edu.wpi.first.util.struct.Struct; 025import java.util.HashMap; 026import java.util.HashSet; 027import java.util.Map; 028import java.util.Set; 029import java.util.function.Function; 030import us.hebi.quickbuf.ProtoMessage; 031 032/** 033 * A backend implementation that sends data over network tables. Be careful when using this, since 034 * sending too much data may cause bandwidth or CPU starvation. 035 */ 036public class NTEpilogueBackend implements EpilogueBackend { 037 private final NetworkTableInstance m_nt; 038 039 private final Map<String, Publisher> m_publishers = new HashMap<>(); 040 private final Map<String, NestedBackend> m_nestedBackends = new HashMap<>(); 041 private final Set<Struct<?>> m_seenSchemas = new HashSet<>(); 042 private final Set<Protobuf<?, ?>> m_seenProtos = new HashSet<>(); 043 private final Function<String, IntegerPublisher> m_createIntPublisher; 044 private final Function<String, FloatPublisher> m_createFloatPublisher; 045 private final Function<String, DoublePublisher> m_createDoublePublisher; 046 private final Function<String, BooleanPublisher> m_createBooleanPublisher; 047 private final Function<String, RawPublisher> m_createRawPublisher; 048 private final Function<String, IntegerArrayPublisher> m_createIntegerArrayPublisher; 049 private final Function<String, FloatArrayPublisher> m_createFloatArrayPublisher; 050 private final Function<String, DoubleArrayPublisher> m_createDoubleArrayPublisher; 051 private final Function<String, BooleanArrayPublisher> m_createBooleanArrayPublisher; 052 private final Function<String, StringPublisher> m_createStringPublisher; 053 private final Function<String, StringArrayPublisher> m_createStringArrayPublisher; 054 055 /** 056 * Creates a logging backend that sends information to NetworkTables. 057 * 058 * @param nt the NetworkTable instance to use to send data to 059 */ 060 @SuppressWarnings("unchecked") 061 public NTEpilogueBackend(NetworkTableInstance nt) { 062 this.m_nt = nt; 063 m_createIntPublisher = identifier -> m_nt.getIntegerTopic(identifier).publish(); 064 m_createFloatPublisher = identifier -> m_nt.getFloatTopic(identifier).publish(); 065 m_createDoublePublisher = identifier -> m_nt.getDoubleTopic(identifier).publish(); 066 m_createBooleanPublisher = identifier -> m_nt.getBooleanTopic(identifier).publish(); 067 m_createRawPublisher = identifier -> m_nt.getRawTopic(identifier).publish("raw"); 068 m_createIntegerArrayPublisher = identifier -> m_nt.getIntegerArrayTopic(identifier).publish(); 069 m_createFloatArrayPublisher = identifier -> m_nt.getFloatArrayTopic(identifier).publish(); 070 m_createDoubleArrayPublisher = identifier -> m_nt.getDoubleArrayTopic(identifier).publish(); 071 m_createBooleanArrayPublisher = identifier -> m_nt.getBooleanArrayTopic(identifier).publish(); 072 m_createStringPublisher = identifier -> m_nt.getStringTopic(identifier).publish(); 073 m_createStringArrayPublisher = identifier -> m_nt.getStringArrayTopic(identifier).publish(); 074 } 075 076 @Override 077 public EpilogueBackend getNested(String path) { 078 if (!m_nestedBackends.containsKey(path)) { 079 var nested = new NestedBackend(path, this); 080 m_nestedBackends.put(path, nested); 081 return nested; 082 } 083 084 return m_nestedBackends.get(path); 085 } 086 087 @Override 088 public void log(String identifier, int value) { 089 ((IntegerPublisher) m_publishers.computeIfAbsent(identifier, m_createIntPublisher)).set(value); 090 } 091 092 @Override 093 public void log(String identifier, long value) { 094 ((IntegerPublisher) m_publishers.computeIfAbsent(identifier, m_createIntPublisher)).set(value); 095 } 096 097 @Override 098 public void log(String identifier, float value) { 099 ((FloatPublisher) m_publishers.computeIfAbsent(identifier, m_createFloatPublisher)).set(value); 100 } 101 102 @Override 103 public void log(String identifier, double value) { 104 ((DoublePublisher) m_publishers.computeIfAbsent(identifier, m_createDoublePublisher)) 105 .set(value); 106 } 107 108 @Override 109 public void log(String identifier, boolean value) { 110 ((BooleanPublisher) m_publishers.computeIfAbsent(identifier, m_createBooleanPublisher)) 111 .set(value); 112 } 113 114 @Override 115 public void log(String identifier, byte[] value) { 116 ((RawPublisher) m_publishers.computeIfAbsent(identifier, m_createRawPublisher)).set(value); 117 } 118 119 @Override 120 @SuppressWarnings("PMD.UnnecessaryCastRule") 121 public void log(String identifier, int[] value) { 122 // NT backend only supports int64[], so we have to manually widen to 64 bits before sending 123 long[] widened = new long[value.length]; 124 125 for (int i = 0; i < value.length; i++) { 126 widened[i] = (long) value[i]; 127 } 128 129 ((IntegerArrayPublisher) 130 m_publishers.computeIfAbsent(identifier, m_createIntegerArrayPublisher)) 131 .set(widened); 132 } 133 134 @Override 135 public void log(String identifier, long[] value) { 136 ((IntegerArrayPublisher) 137 m_publishers.computeIfAbsent(identifier, m_createIntegerArrayPublisher)) 138 .set(value); 139 } 140 141 @Override 142 public void log(String identifier, float[] value) { 143 ((FloatArrayPublisher) m_publishers.computeIfAbsent(identifier, m_createFloatArrayPublisher)) 144 .set(value); 145 } 146 147 @Override 148 public void log(String identifier, double[] value) { 149 ((DoubleArrayPublisher) m_publishers.computeIfAbsent(identifier, m_createDoubleArrayPublisher)) 150 .set(value); 151 } 152 153 @Override 154 public void log(String identifier, boolean[] value) { 155 ((BooleanArrayPublisher) 156 m_publishers.computeIfAbsent(identifier, m_createBooleanArrayPublisher)) 157 .set(value); 158 } 159 160 @Override 161 public void log(String identifier, String value) { 162 ((StringPublisher) m_publishers.computeIfAbsent(identifier, m_createStringPublisher)) 163 .set(value); 164 } 165 166 @Override 167 public void log(String identifier, String[] value) { 168 ((StringArrayPublisher) m_publishers.computeIfAbsent(identifier, m_createStringArrayPublisher)) 169 .set(value); 170 } 171 172 @Override 173 @SuppressWarnings("unchecked") 174 public <S> void log(String identifier, S value, Struct<S> struct) { 175 // NetworkTableInstance.addSchema has checks that we're able to skip, avoiding allocations 176 if (m_seenSchemas.add(struct)) { 177 m_nt.addSchema(struct); 178 } 179 180 if (m_publishers.containsKey(identifier)) { 181 ((StructPublisher<S>) m_publishers.get(identifier)).set(value); 182 } else { 183 StructPublisher<S> publisher = m_nt.getStructTopic(identifier, struct).publish(); 184 m_publishers.put(identifier, publisher); 185 publisher.set(value); 186 } 187 } 188 189 @Override 190 @SuppressWarnings("unchecked") 191 public <S> void log(String identifier, S[] value, Struct<S> struct) { 192 // NetworkTableInstance.addSchema has checks that we're able to skip, avoiding allocations 193 if (m_seenSchemas.add(struct)) { 194 m_nt.addSchema(struct); 195 } 196 197 if (m_publishers.containsKey(identifier)) { 198 ((StructArrayPublisher<S>) m_publishers.get(identifier)).set(value); 199 } else { 200 StructArrayPublisher<S> publisher = m_nt.getStructArrayTopic(identifier, struct).publish(); 201 m_publishers.put(identifier, publisher); 202 publisher.set(value); 203 } 204 } 205 206 @Override 207 @SuppressWarnings("unchecked") 208 public <P, M extends ProtoMessage<M>> void log(String identifier, P value, Protobuf<P, M> proto) { 209 // NetworkTableInstance.addSchema has checks that we're able to skip, avoiding allocations 210 if (m_seenProtos.add(proto)) { 211 m_nt.addSchema(proto); 212 } 213 214 if (m_publishers.containsKey(identifier)) { 215 ((ProtobufPublisher<P>) m_publishers.get(identifier)).set(value); 216 } else { 217 ProtobufPublisher<P> publisher = m_nt.getProtobufTopic(identifier, proto).publish(); 218 m_publishers.put(identifier, publisher); 219 publisher.set(value); 220 } 221 } 222}