001/*----------------------------------------------------------------------------*/ 002/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */ 003/* Open Source Software - may be modified and shared by FRC teams. The code */ 004/* must be accompanied by the FIRST BSD license file in the root directory of */ 005/* the project. */ 006/*----------------------------------------------------------------------------*/ 007 008package edu.wpi.first.wpilibj.smartdashboard; 009 010import edu.wpi.first.networktables.EntryListenerFlags; 011import edu.wpi.first.networktables.NetworkTable; 012import edu.wpi.first.networktables.NetworkTableEntry; 013import edu.wpi.first.networktables.NetworkTableValue; 014import java.util.function.BooleanSupplier; 015import java.util.function.Consumer; 016import java.util.function.DoubleConsumer; 017import java.util.function.DoubleSupplier; 018import java.util.function.Function; 019import java.util.function.Supplier; 020import java.util.ArrayList; 021import java.util.List; 022 023public class SendableBuilderImpl implements SendableBuilder { 024 private static class Property { 025 Property(NetworkTable table, String key) { 026 m_entry = table.getEntry(key); 027 } 028 029 @Override 030 @SuppressWarnings("NoFinalizer") 031 public synchronized void finalize() { 032 stopListener(); 033 } 034 035 void startListener() { 036 if (m_entry.isValid() && m_listener == 0 && m_createListener != null) { 037 m_listener = m_createListener.apply(m_entry); 038 } 039 } 040 041 void stopListener() { 042 if (m_entry.isValid() && m_listener != 0) { 043 m_entry.removeListener(m_listener); 044 m_listener = 0; 045 } 046 } 047 048 final NetworkTableEntry m_entry; 049 int m_listener = 0; 050 Consumer<NetworkTableEntry> m_update; 051 Function<NetworkTableEntry, Integer> m_createListener; 052 } 053 054 private final List<Property> m_properties = new ArrayList<>(); 055 private Runnable m_safeState; 056 private Runnable m_updateTable; 057 private NetworkTable m_table; 058 059 /** 060 * Set the network table. Must be called prior to any Add* functions being 061 * called. 062 * @param table Network table 063 */ 064 public void setTable(NetworkTable table) { 065 m_table = table; 066 } 067 068 /** 069 * Get the network table. 070 * @return The network table 071 */ 072 public NetworkTable getTable() { 073 return m_table; 074 } 075 076 /** 077 * Update the network table values by calling the getters for all properties. 078 */ 079 public void updateTable() { 080 for (Property property : m_properties) { 081 if (property.m_update != null) { 082 property.m_update.accept(property.m_entry); 083 } 084 } 085 if (m_updateTable != null) { 086 m_updateTable.run(); 087 } 088 } 089 090 /** 091 * Hook setters for all properties. 092 */ 093 public void startListeners() { 094 for (Property property : m_properties) { 095 property.startListener(); 096 } 097 } 098 099 /** 100 * Unhook setters for all properties. 101 */ 102 public void stopListeners() { 103 for (Property property : m_properties) { 104 property.stopListener(); 105 } 106 } 107 108 /** 109 * Start LiveWindow mode by hooking the setters for all properties. Also 110 * calls the safeState function if one was provided. 111 */ 112 public void startLiveWindowMode() { 113 if (m_safeState != null) { 114 m_safeState.run(); 115 } 116 startListeners(); 117 } 118 119 /** 120 * Stop LiveWindow mode by unhooking the setters for all properties. Also 121 * calls the safeState function if one was provided. 122 */ 123 public void stopLiveWindowMode() { 124 stopListeners(); 125 if (m_safeState != null) { 126 m_safeState.run(); 127 } 128 } 129 130 /** 131 * Set the string representation of the named data type that will be used 132 * by the smart dashboard for this sendable. 133 * 134 * @param type data type 135 */ 136 @Override 137 public void setSmartDashboardType(String type) { 138 m_table.getEntry(".type").setString(type); 139 } 140 141 /** 142 * Set the function that should be called to set the Sendable into a safe 143 * state. This is called when entering and exiting Live Window mode. 144 * 145 * @param func function 146 */ 147 @Override 148 public void setSafeState(Runnable func) { 149 m_safeState = func; 150 } 151 152 /** 153 * Set the function that should be called to update the network table 154 * for things other than properties. Note this function is not passed 155 * the network table object; instead it should use the entry handles 156 * returned by getEntry(). 157 * 158 * @param func function 159 */ 160 @Override 161 public void setUpdateTable(Runnable func) { 162 m_updateTable = func; 163 } 164 165 /** 166 * Add a property without getters or setters. This can be used to get 167 * entry handles for the function called by setUpdateTable(). 168 * 169 * @param key property name 170 * @return Network table entry 171 */ 172 @Override 173 public NetworkTableEntry getEntry(String key) { 174 return m_table.getEntry(key); 175 } 176 177 /** 178 * Add a boolean property. 179 * 180 * @param key property name 181 * @param getter getter function (returns current value) 182 * @param setter setter function (sets new value) 183 */ 184 @Override 185 public void addBooleanProperty(String key, BooleanSupplier getter, BooleanConsumer setter) { 186 Property property = new Property(m_table, key); 187 if (getter != null) { 188 property.m_update = (entry) -> entry.setBoolean(getter.getAsBoolean()); 189 } 190 if (setter != null) { 191 property.m_createListener = (entry) -> entry.addListener((event) -> { 192 if (event.value.isBoolean()) { 193 setter.accept(event.value.getBoolean()); 194 } 195 }, EntryListenerFlags.kImmediate | EntryListenerFlags.kNew | EntryListenerFlags.kUpdate); 196 } 197 m_properties.add(property); 198 } 199 200 /** 201 * Add a double property. 202 * 203 * @param key property name 204 * @param getter getter function (returns current value) 205 * @param setter setter function (sets new value) 206 */ 207 @Override 208 public void addDoubleProperty(String key, DoubleSupplier getter, DoubleConsumer setter) { 209 Property property = new Property(m_table, key); 210 if (getter != null) { 211 property.m_update = (entry) -> entry.setDouble(getter.getAsDouble()); 212 } 213 if (setter != null) { 214 property.m_createListener = (entry) -> entry.addListener((event) -> { 215 if (event.value.isDouble()) { 216 setter.accept(event.value.getDouble()); 217 } 218 }, EntryListenerFlags.kImmediate | EntryListenerFlags.kNew | EntryListenerFlags.kUpdate); 219 } 220 m_properties.add(property); 221 } 222 223 /** 224 * Add a string property. 225 * 226 * @param key property name 227 * @param getter getter function (returns current value) 228 * @param setter setter function (sets new value) 229 */ 230 @Override 231 public void addStringProperty(String key, Supplier<String> getter, Consumer<String> setter) { 232 Property property = new Property(m_table, key); 233 if (getter != null) { 234 property.m_update = (entry) -> entry.setString(getter.get()); 235 } 236 if (setter != null) { 237 property.m_createListener = (entry) -> entry.addListener((event) -> { 238 if (event.value.isString()) { 239 setter.accept(event.value.getString()); 240 } 241 }, EntryListenerFlags.kImmediate | EntryListenerFlags.kNew | EntryListenerFlags.kUpdate); 242 } 243 m_properties.add(property); 244 } 245 246 /** 247 * Add a boolean array property. 248 * 249 * @param key property name 250 * @param getter getter function (returns current value) 251 * @param setter setter function (sets new value) 252 */ 253 @Override 254 public void addBooleanArrayProperty(String key, Supplier<boolean[]> getter, 255 Consumer<boolean[]> setter) { 256 Property property = new Property(m_table, key); 257 if (getter != null) { 258 property.m_update = (entry) -> entry.setBooleanArray(getter.get()); 259 } 260 if (setter != null) { 261 property.m_createListener = (entry) -> entry.addListener((event) -> { 262 if (event.value.isBooleanArray()) { 263 setter.accept(event.value.getBooleanArray()); 264 } 265 }, EntryListenerFlags.kImmediate | EntryListenerFlags.kNew | EntryListenerFlags.kUpdate); 266 } 267 m_properties.add(property); 268 } 269 270 /** 271 * Add a double array property. 272 * 273 * @param key property name 274 * @param getter getter function (returns current value) 275 * @param setter setter function (sets new value) 276 */ 277 @Override 278 public void addDoubleArrayProperty(String key, Supplier<double[]> getter, 279 Consumer<double[]> setter) { 280 Property property = new Property(m_table, key); 281 if (getter != null) { 282 property.m_update = (entry) -> entry.setDoubleArray(getter.get()); 283 } 284 if (setter != null) { 285 property.m_createListener = (entry) -> entry.addListener((event) -> { 286 if (event.value.isDoubleArray()) { 287 setter.accept(event.value.getDoubleArray()); 288 } 289 }, EntryListenerFlags.kImmediate | EntryListenerFlags.kNew | EntryListenerFlags.kUpdate); 290 } 291 m_properties.add(property); 292 } 293 294 /** 295 * Add a string array property. 296 * 297 * @param key property name 298 * @param getter getter function (returns current value) 299 * @param setter setter function (sets new value) 300 */ 301 @Override 302 public void addStringArrayProperty(String key, Supplier<String[]> getter, 303 Consumer<String[]> setter) { 304 Property property = new Property(m_table, key); 305 if (getter != null) { 306 property.m_update = (entry) -> entry.setStringArray(getter.get()); 307 } 308 if (setter != null) { 309 property.m_createListener = (entry) -> entry.addListener((event) -> { 310 if (event.value.isStringArray()) { 311 setter.accept(event.value.getStringArray()); 312 } 313 }, EntryListenerFlags.kImmediate | EntryListenerFlags.kNew | EntryListenerFlags.kUpdate); 314 } 315 m_properties.add(property); 316 } 317 318 /** 319 * Add a raw property. 320 * 321 * @param key property name 322 * @param getter getter function (returns current value) 323 * @param setter setter function (sets new value) 324 */ 325 @Override 326 public void addRawProperty(String key, Supplier<byte[]> getter, Consumer<byte[]> setter) { 327 Property property = new Property(m_table, key); 328 if (getter != null) { 329 property.m_update = (entry) -> entry.setRaw(getter.get()); 330 } 331 if (setter != null) { 332 property.m_createListener = (entry) -> entry.addListener((event) -> { 333 if (event.value.isRaw()) { 334 setter.accept(event.value.getRaw()); 335 } 336 }, EntryListenerFlags.kImmediate | EntryListenerFlags.kNew | EntryListenerFlags.kUpdate); 337 } 338 m_properties.add(property); 339 } 340 341 /** 342 * Add a NetworkTableValue property. 343 * 344 * @param key property name 345 * @param getter getter function (returns current value) 346 * @param setter setter function (sets new value) 347 */ 348 @Override 349 public void addValueProperty(String key, Supplier<NetworkTableValue> getter, 350 Consumer<NetworkTableValue> setter) { 351 Property property = new Property(m_table, key); 352 if (getter != null) { 353 property.m_update = (entry) -> entry.setValue(getter.get()); 354 } 355 if (setter != null) { 356 property.m_createListener = (entry) -> entry.addListener((event) -> { 357 setter.accept(event.value); 358 }, EntryListenerFlags.kImmediate | EntryListenerFlags.kNew | EntryListenerFlags.kUpdate); 359 } 360 m_properties.add(property); 361 } 362}