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