diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml new file mode 100644 index 0000000000..de54a54756 --- /dev/null +++ b/.github/workflows/python.yml @@ -0,0 +1,62 @@ +name: Build and Distribute PhotonLibPy + +permissions: + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing + +on: + push: + branches: [ master ] + tags: + - 'v*' + pull_request: + branches: [ master ] + +jobs: + buildAndDeploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + sparse-checkout-cone-mode: false + fetch-tags: true + fetch-depth: 99999 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.11 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel pytest + + - name: Build wheel + working-directory: ./photon-lib/py + run: | + python setup.py sdist bdist_wheel + + - name: Run Unit Tests + working-directory: ./photon-lib/py + run: | + pip install --no-cache-dir dist/*.whl + pytest + + + - name: Upload artifacts + uses: actions/upload-artifact@master + with: + name: dist + path: ./photon-lib/py/dist/ + + - name: Publish package distributions to TestPyPI + # Only upload on tags + if: startsWith(github.ref, 'refs/tags/v') + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages_dir: ./photon-lib/py/dist/ + + permissions: + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing diff --git a/photon-lib/py/.gitignore b/photon-lib/py/.gitignore new file mode 100644 index 0000000000..f0c4ced6d6 --- /dev/null +++ b/photon-lib/py/.gitignore @@ -0,0 +1,5 @@ +photonlibpy.egg-info/ +dist/ +build/ +.eggs/ +photonlibpy/version.py diff --git a/photon-lib/py/buildAndTest.bat b/photon-lib/py/buildAndTest.bat new file mode 100644 index 0000000000..b9774a54d6 --- /dev/null +++ b/photon-lib/py/buildAndTest.bat @@ -0,0 +1,14 @@ +:: Uninstall if it already was installed +pip uninstall -y photonlibpy + +:: Build wheel +python setup.py bdist_wheel + +:: Install whatever wheel was made +for %%f in (dist/*.whl) do ( + echo installing dist/%%f + pip install --no-cache-dir dist/%%f +) + +:: Run the test suite +pytest diff --git a/photon-lib/py/photonlibpy/__init__.py b/photon-lib/py/photonlibpy/__init__.py new file mode 100644 index 0000000000..d5b258f5e7 --- /dev/null +++ b/photon-lib/py/photonlibpy/__init__.py @@ -0,0 +1 @@ +# No one here but us chickens diff --git a/photon-lib/py/photonlibpy/multiTargetPNPResult.py b/photon-lib/py/photonlibpy/multiTargetPNPResult.py new file mode 100644 index 0000000000..63bbb34301 --- /dev/null +++ b/photon-lib/py/photonlibpy/multiTargetPNPResult.py @@ -0,0 +1,45 @@ +from dataclasses import dataclass, field +from wpimath.geometry import Transform3d +from photonlibpy.packet import Packet + + +@dataclass +class PNPResult: + _NUM_BYTES_IN_FLOAT = 8 + PACK_SIZE_BYTES = 1 + (_NUM_BYTES_IN_FLOAT * 7 * 2) + (_NUM_BYTES_IN_FLOAT * 3) + + isPresent: bool = False + best: Transform3d = field(default_factory=Transform3d) + alt: Transform3d = field(default_factory=Transform3d) + ambiguity: float = 0.0 + bestReprojError: float = 0.0 + altReprojError: float = 0.0 + + def createFromPacket(self, packet: Packet) -> Packet: + self.isPresent = packet.decodeBoolean() + self.best = packet.decodeTransform() + self.alt = packet.decodeTransform() + self.bestReprojError = packet.decodeDouble() + self.altReprojError = packet.decodeDouble() + self.ambiguity = packet.decodeDouble() + return packet + + +@dataclass +class MultiTargetPNPResult: + _MAX_IDS = 32 + # pnpresult + MAX_IDS possible targets (arbitrary upper limit that should never be hit, ideally) + _PACK_SIZE_BYTES = PNPResult.PACK_SIZE_BYTES + (1 * _MAX_IDS) + + estimatedPose: PNPResult = field(default_factory=PNPResult) + fiducialIDsUsed: list[int] = field(default_factory=list) + + def createFromPacket(self, packet: Packet) -> Packet: + self.estimatedPose = PNPResult() + self.estimatedPose.createFromPacket(packet) + self.fiducialIDsUsed = [] + for _ in range(MultiTargetPNPResult._MAX_IDS): + fidId = packet.decode16() + if fidId >= 0: + self.fiducialIDsUsed.append(fidId) + return packet diff --git a/photon-lib/py/photonlibpy/packet.py b/photon-lib/py/photonlibpy/packet.py new file mode 100644 index 0000000000..c3816d94f8 --- /dev/null +++ b/photon-lib/py/photonlibpy/packet.py @@ -0,0 +1,143 @@ +import struct +from wpimath.geometry import Transform3d, Translation3d, Rotation3d, Quaternion +import wpilib + + +class Packet: + def __init__(self, data: list[int]): + """ + * Constructs an empty packet. + * + * @param self.size The self.size of the packet buffer. + """ + self.packetData = data + self.size = len(data) + self.readPos = 0 + self.outOfBytes = False + + def clear(self): + """Clears the packet and resets the read and write positions.""" + self.packetData = [0] * self.size + self.readPos = 0 + self.outOfBytes = False + + def getSize(self): + return self.size + + _NO_MORE_BYTES_MESSAGE = """ + Photonlib - Ran out of bytes while decoding. + Make sure the version of photonvision on the coprocessor + matches the version of photonlib running in the robot code. + """ + + def _getNextByte(self) -> int: + retVal = 0x00 + + if not self.outOfBytes: + try: + retVal = 0x00FF & self.packetData[self.readPos] + self.readPos += 1 + except IndexError: + wpilib.reportError(Packet._NO_MORE_BYTES_MESSAGE, True) + self.outOfBytes = True + + return retVal + + def getData(self) -> list[int]: + """ + * Returns the packet data. + * + * @return The packet data. + """ + return self.packetData + + def setData(self, data: list[int]): + """ + * Sets the packet data. + * + * @param data The packet data. + """ + self.clear() + self.packetData = data + self.size = len(self.packetData) + + def _decodeGeneric(self, unpackFormat, numBytes): + # Read ints in from the data buffer + intList = [] + for _ in range(numBytes): + intList.append(self._getNextByte()) + + # Interpret the bytes as a floating point number + value = struct.unpack(unpackFormat, bytes(intList))[0] + + return value + + def decode8(self) -> int: + """ + * Returns a single decoded byte from the packet. + * + * @return A decoded byte from the packet. + """ + return self._decodeGeneric(">b", 1) + + def decode16(self) -> int: + """ + * Returns a single decoded byte from the packet. + * + * @return A decoded byte from the packet. + """ + return self._decodeGeneric(">h", 2) + + def decode32(self) -> int: + """ + * Returns a decoded int (32 bytes) from the packet. + * + * @return A decoded int from the packet. + """ + return self._decodeGeneric(">l", 4) + + def decodeDouble(self) -> float: + """ + * Returns a decoded double from the packet. + * + * @return A decoded double from the packet. + """ + return self._decodeGeneric(">d", 8) + + def decodeBoolean(self) -> bool: + """ + * Returns a decoded boolean from the packet. + * + * @return A decoded boolean from the packet. + """ + return self.decode8() == 1 + + def decodeDoubleArray(self, length: int) -> list[float]: + """ + * Returns a decoded array of floats from the packet. + * + * @return A decoded array of floats from the packet. + """ + ret = [] + for _ in range(length): + ret.append(self.decodeDouble()) + return ret + + def decodeTransform(self) -> Transform3d: + """ + * Returns a decoded Transform3d + * + * @return A decoded Tansform3d from the packet. + """ + x = self.decodeDouble() + y = self.decodeDouble() + z = self.decodeDouble() + translation = Translation3d(x, y, z) + + w = self.decodeDouble() + x = self.decodeDouble() + y = self.decodeDouble() + z = self.decodeDouble() + rotation = Rotation3d(Quaternion(w, x, y, z)) + + return Transform3d(translation, rotation) diff --git a/photon-lib/py/photonlibpy/photonCamera.py b/photon-lib/py/photonlibpy/photonCamera.py new file mode 100644 index 0000000000..0d474a2434 --- /dev/null +++ b/photon-lib/py/photonlibpy/photonCamera.py @@ -0,0 +1,170 @@ +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): + kDefault = -1 + kOff = 0 + kOn = 1 + 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 + 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 = self.cameraTable.getBooleanTopic( + "driverModeRequest" + ).publish() + self.driverModeSubscriber = self.cameraTable.getBooleanTopic( + "driverMode" + ).subscribe(False) + self.inputSaveImgEntry = self.cameraTable.getIntegerTopic( + "inputSaveImgCmd" + ).getEntry(0) + self.outputSaveImgEntry = self.cameraTable.getIntegerTopic( + "outputSaveImgCmd" + ).getEntry(0) + self.pipelineIndexRequest = self.cameraTable.getIntegerTopic( + "pipelineIndexRequest" + ).publish() + self.pipelineIndexState = self.cameraTable.getIntegerTopic( + "pipelineIndexState" + ).subscribe(0) + self.heartbeatEntry = self.cameraTable.getIntegerTopic("heartbeat").subscribe( + -1 + ) + + self.ledModeRequest = photonvision_root_table.getIntegerTopic( + "ledModeRequest" + ).publish() + self.ledModeState = photonvision_root_table.getIntegerTopic( + "ledModeState" + ).subscribe(-1) + self.versionEntry = photonvision_root_table.getStringTopic("version").subscribe( + "" + ) + + # Existing is enough to make this multisubscriber do its thing + self.topicNameSubscriber = ntcore.MultiSubscriber( + instance, ["/photonvision/"], ntcore.PubSubOptions(topicsOnly=True) + ) + + self.prevHeartbeat = 0 + self.prevHeartbeatChangeTime = Timer.getFPGATimestamp() + + def getLatestResult(self) -> PhotonPipelineResult: + self._versionCheck() + + retVal = PhotonPipelineResult() + packetWithTimestamp = self.rawBytesEntry.getAtomic() + byteList = packetWithTimestamp.value + timestamp = packetWithTimestamp.time + + if len(byteList) < 1: + return retVal + else: + retVal.populateFromPacket(Packet(byteList)) + # NT4 allows us to correct the timestamp based on when the message was sent + retVal.setTimestampSeconds( + timestamp / 1e-6 - retVal.getLatencyMillis() / 1e-3 + ) + return retVal + + def getDriverMode(self) -> bool: + return self.driverModeSubscriber.get() + + def setDriverMode(self, driverMode: bool) -> None: + self.driverModePublisher.set(driverMode) + + def takeInputSnapshot(self) -> None: + self.inputSaveImgEntry.set(self.inputSaveImgEntry.get() + 1) + + def takeOutputSnapshot(self) -> None: + self.outputSaveImgEntry.set(self.outputSaveImgEntry.get() + 1) + + def getPipelineIndex(self) -> int: + return self.pipelineIndexState.get(0) + + def setPipelineIndex(self, index: int) -> None: + self.pipelineIndexRequest.set(index) + + def getLEDMode(self) -> VisionLEDMode: + mode = self.ledModeState.get() + return VisionLEDMode(mode) + + def setLEDMode(self, led: VisionLEDMode) -> None: + self.ledModeRequest.set(led.value) + + def getName(self) -> str: + return self.name + + def isConnected(self) -> bool: + curHeartbeat = self.heartbeatEntry.get() + now = Timer.getFPGATimestamp() + + if curHeartbeat != self.prevHeartbeat: + self.prevHeartbeat = curHeartbeat + 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/photonlibpy/photonPipelineResult.py b/photon-lib/py/photonlibpy/photonPipelineResult.py new file mode 100644 index 0000000000..3fe02c9fea --- /dev/null +++ b/photon-lib/py/photonlibpy/photonPipelineResult.py @@ -0,0 +1,38 @@ +from dataclasses import dataclass, field + +from photonlibpy.multiTargetPNPResult import MultiTargetPNPResult +from photonlibpy.packet import Packet +from photonlibpy.photonTrackedTarget import PhotonTrackedTarget + + +@dataclass +class PhotonPipelineResult: + latencyMillis: float = -1.0 + timestampSec: float = -1.0 + targets: list[PhotonTrackedTarget] = field(default_factory=list) + multiTagResult: MultiTargetPNPResult = field(default_factory=MultiTargetPNPResult) + + def populateFromPacket(self, packet: Packet) -> Packet: + self.targets = [] + self.latencyMillis = packet.decodeDouble() + self.multiTagResult = MultiTargetPNPResult() + self.multiTagResult.createFromPacket(packet) + targetCount = packet.decode8() + for _ in range(targetCount): + target = PhotonTrackedTarget() + target.createFromPacket(packet) + self.targets.append(target) + + return packet + + def setTimestampSeconds(self, timestampSec: float) -> None: + self.timestampSec = timestampSec + + def getLatencyMillis(self) -> float: + return self.latencyMillis + + def getTimestamp(self) -> float: + return self.timestampSec + + def getTargets(self) -> list[PhotonTrackedTarget]: + return self.targets diff --git a/photon-lib/py/photonlibpy/photonTrackedTarget.py b/photon-lib/py/photonlibpy/photonTrackedTarget.py new file mode 100644 index 0000000000..56c225d997 --- /dev/null +++ b/photon-lib/py/photonlibpy/photonTrackedTarget.py @@ -0,0 +1,82 @@ +from dataclasses import dataclass, field +from wpimath.geometry import Transform3d +from photonlibpy.packet import Packet + + +@dataclass +class TargetCorner: + x: float + y: float + + +@dataclass +class PhotonTrackedTarget: + _MAX_CORNERS = 8 + _NUM_BYTES_IN_FLOAT = 8 + _PACK_SIZE_BYTES = _NUM_BYTES_IN_FLOAT * (5 + 7 + 2 * 4 + 1 + 7 + 2 * _MAX_CORNERS) + + yaw: float = 0.0 + pitch: float = 0.0 + area: float = 0.0 + skew: float = 0.0 + fiducialId: int = -1 + bestCameraToTarget: Transform3d = field(default_factory=Transform3d) + altCameraToTarget: Transform3d = field(default_factory=Transform3d) + minAreaRectCorners: list[TargetCorner] | None = None + detectedCorners: list[TargetCorner] | None = None + poseAmbiguity: float = 0.0 + + def getYaw(self) -> float: + return self.yaw + + def getPitch(self) -> float: + return self.pitch + + def getArea(self) -> float: + return self.area + + def getSkew(self) -> float: + return self.skew + + def getFiducialId(self) -> int: + return self.fiducialId + + def getPoseAmbiguity(self) -> float: + return self.poseAmbiguity + + def getMinAreaRectCorners(self) -> list[TargetCorner] | None: + return self.minAreaRectCorners + + def getDetectedCorners(self) -> list[TargetCorner] | None: + return self.detectedCorners + + def getBestCameraToTarget(self) -> Transform3d: + return self.bestCameraToTarget + + def getAlternateCameraToTarget(self) -> Transform3d: + return self.altCameraToTarget + + def _decodeTargetList(self, packet: Packet, numTargets: int) -> list[TargetCorner]: + retList = [] + for _ in range(numTargets): + cx = packet.decodeDouble() + cy = packet.decodeDouble() + retList.append(TargetCorner(cx, cy)) + return retList + + def createFromPacket(self, packet: Packet) -> Packet: + self.yaw = packet.decodeDouble() + self.pitch = packet.decodeDouble() + self.area = packet.decodeDouble() + self.skew = packet.decodeDouble() + self.fiducialId = packet.decode32() + + self.bestCameraToTarget = packet.decodeTransform() + self.altCameraToTarget = packet.decodeTransform() + + self.poseAmbiguity = packet.decodeDouble() + + self.minAreaRectCorners = self._decodeTargetList(packet, 4) # always four + numCorners = packet.decode8() + self.detectedCorners = self._decodeTargetList(packet, numCorners) + return packet diff --git a/photon-lib/py/setup.py b/photon-lib/py/setup.py new file mode 100644 index 0000000000..b1c3a06133 --- /dev/null +++ b/photon-lib/py/setup.py @@ -0,0 +1,54 @@ +from setuptools import setup, find_packages +import subprocess, re + +gitDescribeResult = ( + subprocess.check_output(["git", "describe", "--tags", "--match=v*", "--always"]) + .decode("utf-8") + .strip() +) + +m = re.search( + r"(v[0-9]{4}\.[0-9]{1}\.[0-9]{1})-?((?:beta)?(?:alpha)?)-?([0-9\.]*)", + gitDescribeResult, +) + +# Extract the first portion of the git describe result +# which should be PEP440 compliant +if m: + versionString = m.group(0) + prefix = m.group(1) + maturity = m.group(2) + suffix = m.group(3).replace(".", "") + versionString = f"{prefix}.{maturity}.{suffix}" + + +else: + print("Warning, no valid version found") + versionString = gitDescribeResult + +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. +Implemented with PhotonVision version {gitDescribeResult} . +""" + +setup( + name="photonlibpy", + packages=find_packages(), + 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", + author="Photonvision Development Team", +) diff --git a/photon-lib/py/test/data.py b/photon-lib/py/test/data.py new file mode 100644 index 0000000000..8c317dfe2f --- /dev/null +++ b/photon-lib/py/test/data.py @@ -0,0 +1,239 @@ +# fmt: off +rawBytes1 = [ + 64, 166, 117, 41, 225, 243, 165, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 0 +] +rawBytes2 = [ + 64, 114, 72, 58, 227, 96, 141, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 0 +] +rawBytes3 = [ + 64, 55, 65, 189, 215, 102, 131, 195, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 0 +] +rawBytes4 = [ + 64, 115, 23, 245, 248, 9, 145, 121, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 1, 64, 41, 32, 212, 70, 53, 253, 38, 64, 19, 140, + 198, 187, 206, 56, 251, 64, 38, 63, 170, 170, 170, 170, 170, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 17, 63, 226, 15, 19, 123, 229, 152, 236, 191, 191, 225, + 27, 96, 1, 219, 30, 63, 168, 175, 103, 65, 172, 24, 8, 63, 115, 51, 123, + 216, 202, 14, 128, 191, 155, 163, 119, 215, 217, 209, 224, 63, 212, 76, 79, + 227, 166, 197, 80, 63, 238, 85, 211, 252, 64, 132, 136, 63, 226, 13, 126, + 170, 219, 202, 209, 191, 190, 134, 80, 94, 25, 179, 17, 63, 168, 92, 53, + 102, 36, 28, 64, 63, 204, 203, 52, 12, 186, 226, 51, 63, 148, 67, 104, 89, + 131, 114, 208, 63, 211, 104, 18, 25, 149, 138, 78, 63, 237, 159, 242, 53, + 211, 204, 51, 63, 217, 254, 169, 82, 190, 36, 22, 64, 123, 96, 0, 6, 100, + 53, 178, 64, 112, 207, 255, 241, 198, 25, 18, 64, 132, 215, 255, 254, 189, + 61, 109, 64, 86, 191, 255, 217, 164, 214, 161, 64, 138, 55, 94, 60, 205, + 229, 39, 64, 115, 130, 222, 78, 57, 230, 238, 64, 131, 15, 94, 65, 66, 194, + 147, 64, 126, 162, 222, 73, 150, 202, 88, 4, 64, 130, 248, 64, 192, 0, 0, 0, + 64, 126, 15, 133, 64, 0, 0, 0, 64, 137, 206, 237, 128, 0, 0, 0, 64, 116, 48, + 240, 32, 0, 0, 0, 64, 132, 218, 43, 96, 0, 0, 0, 64, 86, 210, 155, 128, 0, + 0, 0, 64, 123, 102, 127, 192, 0, 0, 0, 64, 112, 211, 233, 96, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +] +rawBytes5 = [ + 64, 102, 149, 235, 181, 90, 192, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 1, 64, 21, 210, 112, 148, 86, 4, 131, 64, 3, 87, + 196, 18, 174, 105, 145, 64, 47, 80, 237, 9, 123, 66, 95, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 191, 240, 0, 0, + 0, 0, 0, 0, 64, 116, 192, 0, 0, 0, 0, 0, 64, 127, 128, 0, 0, 0, 0, 0, 64, + 116, 192, 0, 0, 0, 0, 0, 64, 100, 96, 0, 0, 0, 0, 0, 64, 133, 72, 0, 0, 0, + 0, 0, 64, 100, 96, 0, 0, 0, 0, 0, 64, 133, 72, 0, 0, 0, 0, 0, 64, 127, 128, + 0, 0, 0, 0, 0, 4, 64, 133, 78, 45, 224, 0, 0, 0, 64, 127, 129, 184, 160, 0, + 0, 0, 64, 133, 78, 1, 192, 0, 0, 0, 64, 100, 100, 194, 224, 0, 0, 0, 64, + 118, 181, 224, 64, 0, 0, 0, 64, 102, 98, 136, 0, 0, 0, 0, 64, 116, 207, 155, + 64, 0, 0, 0, 64, 126, 121, 100, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0 +] +rawBytes6 = [ + 64, 78, 129, 235, 32, 116, 234, 142, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 8, 64, 43, 213, 73, 8, 241, 221, 240, 192, 2, 146, + 71, 190, 201, 205, 25, 64, 37, 96, 141, 183, 156, 102, 0, 192, 9, 112, 76, + 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 191, + 240, 0, 0, 0, 0, 0, 0, 64, 101, 10, 36, 92, 234, 132, 108, 64, 89, 45, 24, + 60, 197, 216, 218, 64, 114, 7, 35, 134, 17, 204, 20, 64, 87, 129, 247, 219, + 201, 12, 226, 64, 114, 67, 177, 81, 138, 189, 202, 64, 100, 68, 236, 33, + 157, 19, 147, 64, 101, 131, 63, 243, 220, 103, 216, 64, 101, 26, 124, 82, + 27, 121, 143, 0, 192, 32, 131, 181, 181, 155, 145, 13, 192, 37, 92, 235, 61, + 221, 83, 253, 63, 173, 176, 233, 61, 133, 212, 255, 192, 73, 171, 139, 128, + 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 191, + 240, 0, 0, 0, 0, 0, 0, 64, 92, 42, 37, 190, 203, 146, 101, 64, 102, 61, 168, + 153, 137, 73, 186, 64, 94, 48, 100, 45, 88, 178, 51, 64, 100, 249, 193, 146, + 50, 77, 24, 64, 94, 220, 25, 65, 52, 109, 155, 64, 101, 62, 112, 102, 118, + 182, 70, 64, 92, 213, 218, 210, 167, 77, 205, 64, 102, 130, 87, 109, 205, + 178, 232, 0, 192, 19, 156, 59, 67, 88, 173, 35, 192, 37, 85, 193, 76, 240, + 22, 41, 63, 152, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 63, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 191, 240, 0, 0, 0, 0, 0, 0, 64, + 96, 128, 0, 0, 0, 0, 0, 64, 101, 128, 0, 0, 0, 0, 0, 64, 97, 64, 0, 0, 0, 0, + 0, 64, 101, 128, 0, 0, 0, 0, 0, 64, 97, 64, 0, 0, 0, 0, 0, 64, 101, 224, 0, + 0, 0, 0, 0, 64, 96, 128, 0, 0, 0, 0, 0, 64, 101, 224, 0, 0, 0, 0, 0, 0, 64, + 48, 164, 171, 25, 83, 4, 154, 192, 34, 178, 85, 3, 174, 51, 62, 63, 147, + 255, 255, 245, 245, 4, 85, 192, 28, 128, 4, 0, 0, 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, + 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 191, 240, 0, 0, 0, 0, 0, 0, 64, 110, + 24, 157, 164, 237, 183, 149, 64, 101, 4, 236, 103, 73, 0, 224, 64, 111, 24, + 157, 165, 1, 191, 69, 64, 100, 228, 236, 103, 153, 126, 38, 64, 111, 32, 0, + 27, 18, 72, 107, 64, 101, 32, 0, 24, 182, 255, 32, 64, 110, 32, 0, 26, 254, + 64, 187, 64, 101, 64, 0, 24, 102, 129, 218, 0, 63, 247, 149, 178, 38, 100, + 246, 86, 192, 9, 184, 134, 230, 194, 222, 110, 63, 150, 102, 102, 85, 138, + 188, 43, 64, 81, 228, 41, 192, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 63, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 191, 240, 0, 0, 0, 0, 0, 0, 64, 100, 147, 51, 63, + 205, 146, 189, 64, 96, 153, 153, 160, 125, 112, 225, 64, 100, 224, 0, 12, + 123, 135, 101, 64, 96, 128, 0, 6, 164, 45, 139, 64, 101, 35, 51, 64, 50, + 109, 67, 64, 97, 73, 153, 159, 130, 143, 31, 64, 100, 214, 102, 115, 132, + 120, 155, 64, 97, 99, 51, 57, 91, 210, 117, 0, 192, 33, 131, 192, 140, 90, + 220, 117, 192, 54, 53, 227, 123, 209, 81, 175, 63, 136, 0, 0, 0, 0, 0, 0, + 64, 86, 128, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 240, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 191, 240, 0, 0, 0, 0, 0, 0, 64, 92, 128, 0, 0, 0, 0, 0, 64, + 109, 128, 0, 0, 0, 0, 0, 64, 93, 64, 0, 0, 0, 0, 0, 64, 109, 128, 0, 0, 0, + 0, 0, 64, 93, 64, 0, 0, 0, 0, 0, 64, 109, 224, 0, 0, 0, 0, 0, 64, 92, 128, + 0, 0, 0, 0, 0, 64, 109, 224, 0, 0, 0, 0, 0, 0, 192, 25, 145, 202, 146, 13, + 244, 248, 192, 36, 180, 8, 18, 34, 149, 42, 63, 128, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 191, 240, 0, 0, 0, 0, 0, 0, 64, 95, 128, 0, 0, 0, 0, 0, 64, 101, 96, 0, + 0, 0, 0, 0, 64, 96, 32, 0, 0, 0, 0, 0, 64, 101, 96, 0, 0, 0, 0, 0, 64, 96, + 32, 0, 0, 0, 0, 0, 64, 101, 160, 0, 0, 0, 0, 0, 64, 95, 128, 0, 0, 0, 0, 0, + 64, 101, 160, 0, 0, 0, 0, 0, 0, 192, 35, 144, 241, 16, 205, 7, 236, 192, 0, + 254, 21, 96, 184, 112, 69, 63, 135, 255, 255, 219, 151, 33, 85, 192, 70, + 128, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 191, 240, 0, 0, 0, 0, 0, 0, 64, 91, 0, 0, 64, 109, 58, 156, 64, 96, + 96, 0, 31, 237, 203, 144, 64, 91, 192, 0, 63, 219, 151, 34, 64, 96, 0, 0, + 32, 54, 157, 78, 64, 92, 32, 0, 63, 146, 197, 100, 64, 96, 48, 0, 32, 18, + 52, 112, 64, 91, 96, 0, 64, 36, 104, 222, 64, 96, 144, 0, 31, 201, 98, 178, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +] diff --git a/photon-lib/py/test/photonlibpy_test.py b/photon-lib/py/test/photonlibpy_test.py new file mode 100644 index 0000000000..92b1636351 --- /dev/null +++ b/photon-lib/py/test/photonlibpy_test.py @@ -0,0 +1,46 @@ +from photonlibpy.packet import Packet +from photonlibpy.photonPipelineResult import PhotonPipelineResult +from data import rawBytes1 +from data import rawBytes2 +from data import rawBytes3 +from data import rawBytes4 +from data import rawBytes5 +from data import rawBytes6 + + +def setupCommon(bytesIn): + res = PhotonPipelineResult() + packet = Packet(bytesIn) + res.populateFromPacket(packet) + assert packet.outOfBytes is False + return res + + +def test_byteParse1(): + res = setupCommon(rawBytes1) + assert len(res.getTargets()) == 0 + + +def test_byteParse2(): + res = setupCommon(rawBytes2) + assert len(res.getTargets()) == 0 + + +def test_byteParse3(): + res = setupCommon(rawBytes3) + assert len(res.getTargets()) == 0 + + +def test_byteParse4(): + res = setupCommon(rawBytes4) + assert len(res.getTargets()) == 1 + + +def test_byteParse5(): + res = setupCommon(rawBytes5) + assert len(res.getTargets()) == 1 + + +def test_byteParse6(): + res = setupCommon(rawBytes6) + assert len(res.getTargets()) > 6