diff --git a/photon-lib/py/.gitignore b/photon-lib/py/.gitignore index 4593c5f4b1..f0c4ced6d6 100644 --- a/photon-lib/py/.gitignore +++ b/photon-lib/py/.gitignore @@ -2,3 +2,4 @@ photonlibpy.egg-info/ dist/ build/ .eggs/ +photonlibpy/version.py diff --git a/photon-lib/py/photonlibpy/photonCamera.py b/photon-lib/py/photonlibpy/photonCamera.py index f788322467..0d474a2434 100644 --- a/photon-lib/py/photonlibpy/photonCamera.py +++ b/photon-lib/py/photonlibpy/photonCamera.py @@ -1,8 +1,10 @@ from enum import Enum import ntcore from wpilib import Timer +import wpilib from photonlibpy.packet import Packet from photonlibpy.photonPipelineResult import PhotonPipelineResult +from photonlibpy.version import PHOTONVISION_VERSION, PHOTONLIB_VERSION class VisionLEDMode(Enum): @@ -12,36 +14,47 @@ class VisionLEDMode(Enum): kBlink = 2 +lastVersionTimeCheck = 0.0 +_VERSION_CHECK_ENABLED = True + + +def setVersionCheckEnabled(enabled: bool): + _VERSION_CHECK_ENABLED = enabled + + class PhotonCamera: def __init__(self, cameraName: str): instance = ntcore.NetworkTableInstance.getDefault() self.name = cameraName - photonvision_root_table = instance.getTable("photonvision") - cameraTable = photonvision_root_table.getSubTable(cameraName) - self.path = cameraTable.getPath() - self.rawBytesEntry = cameraTable.getRawTopic("rawBytes").subscribe( + self._tableName = "photonvision" + photonvision_root_table = instance.getTable(self._tableName) + self.cameraTable = photonvision_root_table.getSubTable(cameraName) + self.path = self.cameraTable.getPath() + self.rawBytesEntry = self.cameraTable.getRawTopic("rawBytes").subscribe( "rawBytes", bytes([]), ntcore.PubSubOptions(periodic=0.01, sendAll=True) ) - self.driverModePublisher = cameraTable.getBooleanTopic( + self.driverModePublisher = self.cameraTable.getBooleanTopic( "driverModeRequest" ).publish() - self.driverModeSubscriber = cameraTable.getBooleanTopic("driverMode").subscribe( - False - ) - self.inputSaveImgEntry = cameraTable.getIntegerTopic( + self.driverModeSubscriber = self.cameraTable.getBooleanTopic( + "driverMode" + ).subscribe(False) + self.inputSaveImgEntry = self.cameraTable.getIntegerTopic( "inputSaveImgCmd" ).getEntry(0) - self.outputSaveImgEntry = cameraTable.getIntegerTopic( + self.outputSaveImgEntry = self.cameraTable.getIntegerTopic( "outputSaveImgCmd" ).getEntry(0) - self.pipelineIndexRequest = cameraTable.getIntegerTopic( + self.pipelineIndexRequest = self.cameraTable.getIntegerTopic( "pipelineIndexRequest" ).publish() - self.pipelineIndexState = cameraTable.getIntegerTopic( + self.pipelineIndexState = self.cameraTable.getIntegerTopic( "pipelineIndexState" ).subscribe(0) - self.heartbeatEntry = cameraTable.getIntegerTopic("heartbeat").subscribe(-1) + self.heartbeatEntry = self.cameraTable.getIntegerTopic("heartbeat").subscribe( + -1 + ) self.ledModeRequest = photonvision_root_table.getIntegerTopic( "ledModeRequest" @@ -62,6 +75,8 @@ def __init__(self, cameraName: str): self.prevHeartbeatChangeTime = Timer.getFPGATimestamp() def getLatestResult(self) -> PhotonPipelineResult: + self._versionCheck() + retVal = PhotonPipelineResult() packetWithTimestamp = self.rawBytesEntry.getAtomic() byteList = packetWithTimestamp.value @@ -114,3 +129,42 @@ def isConnected(self) -> bool: self.prevHeartbeatChangeTime = now return (now - self.prevHeartbeatChangeTime) < 0.5 + + def _versionCheck(self) -> None: + if not _VERSION_CHECK_ENABLED: + return + + if (Timer.getFPGATimestamp() - lastVersionTimeCheck) < 5.0: + return + + if not self.heartbeatEntry.exists(): + cameraNames = ( + self.cameraTable.getInstance().getTable(self._tableName).getSubTables() + ) + if len(cameraNames) == 0: + wpilib.reportError( + "Could not find any PhotonVision coprocessors on NetworkTables. Double check that PhotonVision is running, and that your camera is connected!", + False, + ) + else: + wpilib.reportError( + f"PhotonVision coprocessor at path {self.path} not found in Network Tables. Double check that your camera names match! Only the following camera names were found: { ''.join(cameraNames)}", + True, + ) + + elif not self.isConnected(): + wpilib.reportWarning( + f"PhotonVision coprocessor at path {self.path} is not sending new data.", + True, + ) + + versionString = self.versionEntry.get(defaultValue="") + if len(versionString) > 0 and versionString != PHOTONVISION_VERSION: + wpilib.reportWarning( + "Photon version " + + PHOTONVISION_VERSION + + " does not match coprocessor version " + + versionString + + f"! Please install photonlibpy version {PHOTONLIB_VERSION}", + True, + ) diff --git a/photon-lib/py/setup.py b/photon-lib/py/setup.py index 08d369ed84..291af5c8e0 100644 --- a/photon-lib/py/setup.py +++ b/photon-lib/py/setup.py @@ -19,6 +19,11 @@ print(f"Building version {versionString}") +# Put the version info into a python file for runtime access +with open("photonlibpy/version.py", "w", encoding="utf-8") as fp: + fp.write(f'PHOTONLIB_VERSION="{versionString}"\n') + fp.write(f'PHOTONVISION_VERSION="{gitDescribeResult}"\n') + descriptionStr = f""" Pure-python implementation of PhotonLib for interfacing with PhotonVision on coprocessors. @@ -31,6 +36,8 @@ version=versionString, install_requires=[ "wpilib<2025,>=2024.0.0b2", + "robotpy-wpimath<2025,>=2024.0.0b2", + "pyntcore<2025,>=2024.0.0b2", ], description=descriptionStr, url="https://photonvision.org",