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.wpilibj.livewindow; 006 007import edu.wpi.first.networktables.BooleanPublisher; 008import edu.wpi.first.networktables.NetworkTable; 009import edu.wpi.first.networktables.NetworkTableInstance; 010import edu.wpi.first.networktables.StringPublisher; 011import edu.wpi.first.networktables.StringTopic; 012import edu.wpi.first.util.sendable.Sendable; 013import edu.wpi.first.util.sendable.SendableRegistry; 014import edu.wpi.first.wpilibj.smartdashboard.SendableBuilderImpl; 015 016/** 017 * The LiveWindow class is the public interface for putting sensors and actuators on the LiveWindow. 018 */ 019public final class LiveWindow { 020 private static class Component implements AutoCloseable { 021 @Override 022 public void close() { 023 if (m_namePub != null) { 024 m_namePub.close(); 025 m_namePub = null; 026 } 027 if (m_typePub != null) { 028 m_typePub.close(); 029 m_typePub = null; 030 } 031 } 032 033 boolean m_firstTime = true; 034 boolean m_telemetryEnabled; 035 StringPublisher m_namePub; 036 StringPublisher m_typePub; 037 } 038 039 private static final int dataHandle = SendableRegistry.getDataHandle(); 040 private static final NetworkTable liveWindowTable = 041 NetworkTableInstance.getDefault().getTable("LiveWindow"); 042 private static final NetworkTable statusTable = liveWindowTable.getSubTable(".status"); 043 private static final BooleanPublisher enabledPub = 044 statusTable.getBooleanTopic("LW Enabled").publish(); 045 private static boolean startLiveWindow; 046 private static boolean liveWindowEnabled; 047 private static boolean telemetryEnabled; 048 049 private static Runnable enabledListener; 050 private static Runnable disabledListener; 051 052 static { 053 SendableRegistry.setLiveWindowBuilderFactory(SendableBuilderImpl::new); 054 enabledPub.set(false); 055 } 056 057 private static Component getOrAdd(Sendable sendable) { 058 Component data = (Component) SendableRegistry.getData(sendable, dataHandle); 059 if (data == null) { 060 data = new Component(); 061 SendableRegistry.setData(sendable, dataHandle, data); 062 } 063 return data; 064 } 065 066 private LiveWindow() { 067 throw new UnsupportedOperationException("This is a utility class!"); 068 } 069 070 /** 071 * Sets function to be called when LiveWindow is enabled. 072 * 073 * @param runnable function (or null for none) 074 */ 075 public static synchronized void setEnabledListener(Runnable runnable) { 076 enabledListener = runnable; 077 } 078 079 /** 080 * Sets function to be called when LiveWindow is disabled. 081 * 082 * @param runnable function (or null for none) 083 */ 084 public static synchronized void setDisabledListener(Runnable runnable) { 085 disabledListener = runnable; 086 } 087 088 /** 089 * Returns true if LiveWindow is enabled. 090 * 091 * @return True if LiveWindow is enabled. 092 */ 093 public static synchronized boolean isEnabled() { 094 return liveWindowEnabled; 095 } 096 097 /** 098 * Set the enabled state of LiveWindow. 099 * 100 * <p>If it's being enabled, turn off the scheduler and remove all the commands from the queue and 101 * enable all the components registered for LiveWindow. If it's being disabled, stop all the 102 * registered components and re-enable the scheduler. 103 * 104 * <p>TODO: add code to disable PID loops when enabling LiveWindow. The commands should re-enable 105 * the PID loops themselves when they get rescheduled. This prevents arms from starting to move 106 * around, etc. after a period of adjusting them in LiveWindow mode. 107 * 108 * @param enabled True to enable LiveWindow. 109 */ 110 public static synchronized void setEnabled(boolean enabled) { 111 if (liveWindowEnabled != enabled) { 112 startLiveWindow = enabled; 113 liveWindowEnabled = enabled; 114 updateValues(); // Force table generation now to make sure everything is defined 115 if (enabled) { 116 System.out.println("Starting live window mode."); 117 if (enabledListener != null) { 118 enabledListener.run(); 119 } 120 } else { 121 System.out.println("stopping live window mode."); 122 SendableRegistry.foreachLiveWindow( 123 dataHandle, cbdata -> ((SendableBuilderImpl) cbdata.builder).stopLiveWindowMode()); 124 if (disabledListener != null) { 125 disabledListener.run(); 126 } 127 } 128 enabledPub.set(enabled); 129 } 130 } 131 132 /** 133 * Enable telemetry for a single component. 134 * 135 * @param sendable component 136 */ 137 public static synchronized void enableTelemetry(Sendable sendable) { 138 // Re-enable global setting in case disableAllTelemetry() was called. 139 telemetryEnabled = true; 140 getOrAdd(sendable).m_telemetryEnabled = true; 141 } 142 143 /** 144 * Disable telemetry for a single component. 145 * 146 * @param sendable component 147 */ 148 public static synchronized void disableTelemetry(Sendable sendable) { 149 getOrAdd(sendable).m_telemetryEnabled = false; 150 } 151 152 /** Disable ALL telemetry. */ 153 public static synchronized void disableAllTelemetry() { 154 telemetryEnabled = false; 155 SendableRegistry.foreachLiveWindow( 156 dataHandle, 157 cbdata -> { 158 if (cbdata.data == null) { 159 cbdata.data = new Component(); 160 } 161 ((Component) cbdata.data).m_telemetryEnabled = false; 162 }); 163 } 164 165 /** Enable ALL telemetry. */ 166 public static synchronized void enableAllTelemetry() { 167 telemetryEnabled = true; 168 SendableRegistry.foreachLiveWindow( 169 dataHandle, 170 cbdata -> { 171 if (cbdata.data == null) { 172 cbdata.data = new Component(); 173 } 174 ((Component) cbdata.data).m_telemetryEnabled = true; 175 }); 176 } 177 178 /** 179 * Tell all the sensors to update (send) their values. 180 * 181 * <p>Actuators are handled through callbacks on their value changing from the SmartDashboard 182 * widgets. 183 */ 184 public static synchronized void updateValues() { 185 // Only do this if either LiveWindow mode or telemetry is enabled. 186 if (!liveWindowEnabled && !telemetryEnabled) { 187 return; 188 } 189 190 SendableRegistry.foreachLiveWindow( 191 dataHandle, 192 cbdata -> { 193 if (cbdata.sendable == null || cbdata.parent != null) { 194 return; 195 } 196 197 if (cbdata.data == null) { 198 cbdata.data = new Component(); 199 } 200 201 Component component = (Component) cbdata.data; 202 203 if (!liveWindowEnabled && !component.m_telemetryEnabled) { 204 return; 205 } 206 207 if (component.m_firstTime) { 208 // By holding off creating the NetworkTable entries, it allows the 209 // components to be redefined. This allows default sensor and actuator 210 // values to be created that are replaced with the custom names from 211 // users calling setName. 212 if (cbdata.name.isEmpty()) { 213 return; 214 } 215 NetworkTable ssTable = liveWindowTable.getSubTable(cbdata.subsystem); 216 NetworkTable table; 217 // Treat name==subsystem as top level of subsystem 218 if (cbdata.name.equals(cbdata.subsystem)) { 219 table = ssTable; 220 } else { 221 table = ssTable.getSubTable(cbdata.name); 222 } 223 component.m_namePub = new StringTopic(table.getTopic(".name")).publish(); 224 component.m_namePub.set(cbdata.name); 225 ((SendableBuilderImpl) cbdata.builder).setTable(table); 226 cbdata.sendable.initSendable(cbdata.builder); 227 component.m_typePub = new StringTopic(ssTable.getTopic(".type")).publish(); 228 component.m_typePub.set("LW Subsystem"); 229 230 component.m_firstTime = false; 231 } 232 233 if (startLiveWindow) { 234 ((SendableBuilderImpl) cbdata.builder).startLiveWindowMode(); 235 } 236 cbdata.builder.update(); 237 }); 238 239 startLiveWindow = false; 240 } 241}