# validated: 2017-12-09 EN f9bece2ffbf7 edu/wpi/first/wpilibj/livewindow/LiveWindow.java
# ----------------------------------------------------------------------------
# Copyright (c) 2008-2017 FIRST. All Rights Reserved.
# Open Source Software - may be modified and shared by FRC teams. The code
# must be accompanied by the FIRST BSD license file in the root directory of
# the project.
# ----------------------------------------------------------------------------
import threading
from networktables import NetworkTablesInstance
from .sendablebuilder import SendableBuilder
import warnings
import logging
logger = logging.getLogger(__name__)
__all__ = ["LiveWindow"]
class Component:
def __init__(self, sendable, parent):
self.sendable = sendable
self.parent = parent
self.builder = SendableBuilder()
self.firstTime = True
self.telemetryEnabled = True
class _LiveWindowComponent:
"""A LiveWindow component is a device (sensor or actuator) that should be
added to the SmartDashboard in test mode. The components are cached until
the first time the robot enters Test mode. This allows the components to
be inserted, then renamed."""
def __init__(self, subsystem, name, isSensor):
self.subsystem = subsystem
self.name = str(name)
self.isSensor = isSensor
[docs]class LiveWindow:
"""The public interface for putting sensors and
actuators on the LiveWindow."""
components = {}
_liveWindowTable = None
_statusTable = None
_enabledEntry = None
startLiveWindow = False
liveWindowEnabled = False
telemetryEnabled = True
mutex = threading.RLock()
[docs] @classmethod
def liveWindowTable(cls):
if cls._liveWindowTable is None:
cls._liveWindowTable = NetworkTablesInstance.getDefault().getTable("LiveWindow")
return cls._liveWindowTable
[docs] @classmethod
def statusTable(cls):
if cls._statusTable is None:
cls._statusTable = cls.liveWindowTable().getSubTable(".status")
return cls._statusTable
[docs] @classmethod
def enabledEntry(cls):
if cls._enabledEntry is None:
cls._enabledEntry = cls.statusTable().getEntry("LW Enabled")
return cls._enabledEntry
@classmethod
def _reset(cls):
cls.components = {}
cls._liveWindowTable = None
cls._statusTable = None
cls._enabledEntry = None
cls.startLiveWindow = False
cls.liveWindowEnabled = False
cls.telemetryEnabled = True
[docs] @classmethod
def isEnabled(cls):
return cls.liveWindowEnabled
[docs] @classmethod
def setEnabled(cls, enabled):
"""Set the enabled state of LiveWindow. If it's being enabled, turn
off the scheduler and remove all the commands from the queue and
enable all the components registered for LiveWindow. If it's being
disabled, stop all the registered components and reenable the
scheduler.
TODO: add code to disable PID loops when enabling LiveWindow. The
commands should reenable the PID loops themselves when they get
rescheduled. This prevents arms from starting to move around, etc.
after a period of adjusting them in LiveWindow mode.
"""
from .command import Scheduler
if cls.liveWindowEnabled != enabled:
scheduler = Scheduler.getInstance()
if enabled:
logger.info("Starting live window mode.")
scheduler.disable()
scheduler.removeAll()
else:
logger.info("Stopping live window mode.")
for component in cls.components.values():
component.builder.stopLiveWindowMode()
scheduler.enable()
cls.startLiveWindow = enabled
cls.liveWindowEnabled = enabled
cls.enabledEntry().setBoolean(enabled)
[docs] @classmethod
def run(cls):
"""The run method is called repeatedly to keep the values refreshed
on the screen in test mode.
.. deprecated:: 2018.0.0
No longer required
"""
warnings.warn("run is deprecated. It is no longer required.",
DeprecationWarning, stacklevel=2)
cls.updateValues()
[docs] @classmethod
def addSensor(cls, subsystem, name, component):
"""Add a Sensor associated with the subsystem and with call it by the
given name.
:param subsystem: The subsystem this component is part of.
:param name: The name of this component.
:param component: A LiveWindowSendable component that represents a
sensor.
.. deprecated:: 2018.0.0
Use :meth:`.Sendable.setName` instead.
"""
warnings.warn("addSensor is deprecated. " +
"Use Sendable.setName(subsystem, name) instead.",
DeprecationWarning, stacklevel=2)
with cls.mutex:
cls.add(component)
component.setName(subsystem, name)
[docs] @classmethod
def addActuator(cls, subsystem, name, component):
"""Add an Actuator associated with the subsystem and with call it by
the given name.
:param subsystem: The subsystem this component is part of.
:param name: The name of this component.
:param component: A LiveWindowSendable component that represents a actuator.
.. deprecated:: 2018.0.0
Use :meth:`.Sendable.setName` instead.
"""
warnings.warn("addActuator is deprecated. " +
"Use Sendable.setName(subsystem, name) instead.",
DeprecationWarning, stacklevel=2)
with cls.mutex:
cls.add(component)
component.setName(subsystem, name)
[docs] @classmethod
def addSensorChannel(cls, moduleType, channel, component):
"""Add Sensor to LiveWindow. The components are shown with the type
and channel like this: Gyro[0] for a gyro object connected to the
first analog channel.
:param moduleType: A string indicating the type of the module used in
the naming (above)
:param channel: The channel number the device is connected to
:param component: A reference to the object being added
.. deprecated:: 2018.0.0
Use :meth:`.SendableBase.setName` instead.
"""
warnings.warn("addSensorChannel is deprecated. "+
"Use SendableBase.setName(moduleType, channel) instead.",
DeprecationWarning, stacklevel=2)
cls.add(component)
component.setName("Ungrouped", "%s[%s]" % (moduleType, channel))
[docs] @classmethod
def addActuatorChannel(cls, moduleType, channel, component):
"""Add Actuator to LiveWindow. The components are shown with the
module type, slot and channel like this: Servo[0,2] for a servo
object connected to the first digital module and PWM port 2.
:param moduleType: A string that defines the module name in the label
for the value
:param channel: The channel number the device is plugged into
(usually PWM)
:param component: The reference to the object being added
.. deprecated:: 2018.0.0
Use :meth:`.SendableBase.setName` instead.
"""
warnings.warn("addActuatorChannel is deprecated. " +
"Use SendableBase.setName(moduleType, channel) instead.",
DeprecationWarning, stacklevel=2)
cls.add(component)
component.setName("Ungrouped", "%s[%s]" % (moduleType, channel))
[docs] @classmethod
def addActuatorModuleChannel(cls, moduleType, moduleNumber, channel, component):
"""Add Actuator to LiveWindow. The components are shown with the
module type, slot and channel like this: Servo[0,2] for a servo
object connected to the first digital module and PWM port 2.
:param moduleType: A string that defines the module name in the label
for the value
:param moduleNumber: The number of the particular module type
:param channel: The channel number the device is plugged into
(usually PWM)
:param component: The reference to the object being added
.. deprecated:: 2018.0.0
Use :meth:`.SendableBase.setName` instead.
"""
warnings.warn("addActuatorModuleChannel is deprecated. " +
"Use SendableBase.setName(moduleType, moduleNumber, channel) instead.",
DeprecationWarning, stacklevel=2)
cls.add(component)
component.setName("Ungrouped", "%s[%s,%s]" % (moduleType, moduleNumber, channel))
[docs] @classmethod
def add(cls, sendable):
"""
Add a component to the LiveWindow.
:param sendable: component to add
"""
with cls.mutex:
if sendable not in cls.components:
cls.components[sendable] = Component(sendable, None)
[docs] @classmethod
def addChild(cls, parent, child):
"""
Add a child component to a component.
:param parent: parent component
:param child: child component
"""
with cls.mutex:
component = cls.components.get(child, None)
if component is None:
component = Component(None, parent)
cls.components[child] = component
else:
component.parent = parent
component.telemetryEnabled = False
[docs] @classmethod
def remove(cls, sendable):
"""
Remove a component from the LiveWindow.
@param sendable component to remove
"""
with cls.mutex:
if sendable in cls.components:
component = cls.components[sendable]
del cls.components[sendable]
if cls.isEnabled():
component.builder.stopLiveWindowMode()
[docs] @classmethod
def enableTelemetry(cls, sendable):
"""
Enable telemetry for a single component.
:param sendable: component
"""
with cls.mutex:
cls.telemetryEnabled = True
component = cls.components.get(sendable, None)
if component is not None:
component.telemetryEnabled = True
[docs] @classmethod
def disableTelemetry(cls, sendable):
"""
Disable telemetry for a single component.
:param sendable: component
"""
with cls.mutex:
component = cls.components.get(sendable, None)
if component is not None:
component.telemetryEnabled = False
[docs] @classmethod
def disableAllTelemetry(cls):
""" Disable ALL telemetry """
with cls.mutex:
cls.telemetryEnabled = False
for component in cls.components.values():
component.telemetryEnabled = False
[docs] @classmethod
def updateValues(cls):
with cls.mutex:
# only do this if either LiveWindow mode or telemetry is enabled
if not cls.liveWindowEnabled and not cls.telemetryEnabled:
return
for component in cls.components.values():
if component.sendable is not None and component.parent is None and (cls.liveWindowEnabled or component.telemetryEnabled):
if component.firstTime:
# By holding off creating the NetworkTable entries, it allows the
# components to be redefined. This allows default sensor and actuator
# values to be created that are replaced with the custom names from
# users calling setName.
name = component.sendable.getName()
if name == "":
continue
subsystem = component.sendable.getSubsystem()
ssTable = cls.liveWindowTable().getSubTable(subsystem)
if name == subsystem:
table = ssTable
else:
table = ssTable.getSubTable(name)
table.getEntry(".name").setString(name)
component.builder.setTable(table)
component.sendable.initSendable(component.builder)
ssTable.getEntry(".type").setString("LW Subsystem")
component.firstTime = False
if cls.startLiveWindow:
component.builder.startLiveWindowMode()
component.builder.updateTable()
cls.startLiveWindow = False