mx-remote 5.0.0__tar.gz → 5.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mx_remote-5.0.0 → mx_remote-5.2.0}/CLAUDE.md +13 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/PKG-INFO +1 -1
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/Interface.py +114 -2
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/Interface.pyi +29 -1
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/const.py +12 -1
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/const.pyi +2 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/Constants.py +4 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameAmpZoneSettings.py +1 -1
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameBase.py +33 -2
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameBase.pyi +4 -1
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameBayHide.py +1 -1
- mx_remote-5.2.0/mx_remote/proto/FrameEDIDProfile.py +53 -0
- mx_remote-5.2.0/mx_remote/proto/FrameEDIDProfile.pyi +15 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameMeshOperation.py +1 -1
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameRCAction.py +1 -1
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameReboot.py +1 -1
- mx_remote-5.2.0/mx_remote/proto/FrameSetName.py +62 -0
- mx_remote-5.2.0/mx_remote/proto/FrameSetName.pyi +17 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameSignalStatusNew.py +10 -19
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameSignalStatusNew.pyi +2 -8
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameTXRCAction.py +1 -1
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameTXRCKey.py +1 -1
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPAudio.py +3 -3
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.py +1 -1
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPMultiviewer.py +15 -15
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPSourceSwitch.py +1 -1
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameVolumeSet.py +1 -1
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/remote/Bay.py +28 -1
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/remote/Bay.pyi +5 -2
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/remote/Device.py +9 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/remote/Device.pyi +3 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/remote/Remote.py +72 -19
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/remote/Remote.pyi +1 -1
- mx_remote-5.2.0/tests/entry.py +59 -0
- mx_remote-5.2.0/tests/hello.py +197 -0
- mx_remote-5.2.0/tests/proto_stamp.py +122 -0
- mx_remote-5.2.0/tests/protogate.py +85 -0
- mx_remote-5.2.0/tests/roundtrip.py +176 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/tests/run.py +5 -0
- mx_remote-5.2.0/tests/txresult.py +198 -0
- mx_remote-5.2.0/tests/vdetails.py +86 -0
- mx_remote-5.0.0/mx_remote/proto/FrameEDIDProfile.py +0 -23
- mx_remote-5.0.0/mx_remote/proto/FrameEDIDProfile.pyi +0 -6
- mx_remote-5.0.0/mx_remote/proto/FrameSetName.py +0 -26
- mx_remote-5.0.0/mx_remote/proto/FrameSetName.pyi +0 -6
- mx_remote-5.0.0/tests/proto_stamp.py +0 -60
- mx_remote-5.0.0/tests/txresult.py +0 -105
- {mx_remote-5.0.0 → mx_remote-5.2.0}/.gitignore +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/LICENSE +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/README.md +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/hatch_build.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/Uid.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/Uid.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/__init__.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/__init__.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/main.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/main.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/BayConfig.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/BayConfig.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/Constants.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/Data.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/Data.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/Factory.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/Factory.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameAmpDolbySettings.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameAmpDolbySettings.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameAmpZoneSettings.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameAudioClip.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameAudioClip.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameAudioSetRoute.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameAudioSetRoute.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameBayConfig.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameBayConfig.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameBayConfigSecondary.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameBayConfigSecondary.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameBayHide.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameBayStatus.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameBayStatus.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameConnectStatus.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameConnectStatus.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameDebug.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameDebug.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameDiscover.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameDiscover.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameEDID.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameEDID.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameFactoryReset.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameFactoryReset.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameFilterStatus.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameFilterStatus.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameFirmwareVersion.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameFirmwareVersion.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameHeader.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameHeader.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameHello.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameHello.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameLinks.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameLinks.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameMeshOperation.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameMirrorStatus.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameMirrorStatus.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameMonitoringPulse.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameMonitoringPulse.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameNetworkStatus.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameNetworkStatus.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FramePDUState.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FramePDUState.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FramePowerChange.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FramePowerChange.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameRCAction.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameRCIr.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameRCIr.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameRCKey.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameRCKey.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameRCSettings.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameRCSettings.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameReboot.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameRoutingChange.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameRoutingChange.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameSetInstaller.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameSetInstaller.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameSetRoute.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameSetRoute.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameSetupStatus.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameSetupStatus.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameSignalStatus.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameSignalStatus.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameSysTemperature.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameSysTemperature.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameSystemStatus.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameSystemStatus.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameTXRCAction.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameTXRCKey.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameTopology.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameTopology.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameTxIR.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameTxIR.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameUpgradeFPGA.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameUpgradeFPGA.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPAudio.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPBayMapping.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPBayMapping.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPDetectBays.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPDetectBays.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPLink.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPLink.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPMultiviewer.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPPowerSave.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPPowerSave.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPSetMaster.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPSetMaster.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPSourceSwitch.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPSources.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPSources.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPStats.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPStats.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPStreamDetails.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPStreamDetails.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPTiling.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPTiling.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPVideoWall.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameV2IPVideoWall.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameVolume.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameVolume.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameVolumeDown.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameVolumeDown.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameVolumeSet.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameVolumeUp.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/FrameVolumeUp.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/LinkConfig.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/LinkConfig.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/Multiviewer.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/Multiviewer.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/PDUState.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/PDUState.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/Svd.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/Svd.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/V2IPConfig.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/V2IPConfig.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/V2IPStats.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/V2IPStats.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/__init__.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/__init__.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/proto/svd.csv +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/py.typed +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/remote/ConnectionAsync.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/remote/ConnectionAsync.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/remote/Link.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/remote/Link.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/remote/P8PDU.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/remote/P8PDU.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/remote/State.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/remote/State.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/remote/V2IP.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/mx_remote/remote/V2IP.pyi +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/pyproject.toml +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/tests/audio_ir.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/tests/capture.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/tests/e2e.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/tests/handlers.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/tests/merge.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/tests/newops.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/tests/offsets.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/tests/rcsettings.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/tests/smoke.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/tests/sources.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/tests/stats.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/tests/structs.py +0 -0
- {mx_remote-5.0.0 → mx_remote-5.2.0}/tests/unknowns.py +0 -0
|
@@ -52,6 +52,19 @@ replaced. Its expected values come from firmware behaviour rather than from this
|
|
|
52
52
|
decoder, which is the property a synthetic fixture cannot have — assert against
|
|
53
53
|
something external, or a fixture only proves the decoder agrees with itself.
|
|
54
54
|
|
|
55
|
+
Two things a test of a *command* must do, both of which look like coverage
|
|
56
|
+
when they are missing:
|
|
57
|
+
|
|
58
|
+
- **Assert both directions.** A command that returns False for an unrelated
|
|
59
|
+
reason satisfies a failure-only assertion on its own, so the success case is
|
|
60
|
+
what gives the failure case meaning.
|
|
61
|
+
- **Pick an input that can reach the send.** Every multiviewer getter reads
|
|
62
|
+
`UNKNOWN` until a config frame arrives, and each setter returns True without
|
|
63
|
+
transmitting when the value already matches — so a table built from the first
|
|
64
|
+
enum member, which is `UNKNOWN`, never transmits at all.
|
|
65
|
+
|
|
66
|
+
`tests/txresult.py` is the worked example of both.
|
|
67
|
+
|
|
55
68
|
Validate wire changes against live hardware as well, by running `mxr` or
|
|
56
69
|
replaying a capture file with `mxr -i`.
|
|
57
70
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: mx_remote
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.2.0
|
|
4
4
|
Summary: Python 3 library for interfacing with MX Remote compatible devices
|
|
5
5
|
Project-URL: Homepage, https://github.com/opdenkamp/mx-remote/
|
|
6
6
|
Project-URL: Documentation, https://github.com/opdenkamp/mx-remote/
|
|
@@ -173,10 +173,96 @@ class ConnectStatus(IntEnum):
|
|
|
173
173
|
def __repr__(self) -> str:
|
|
174
174
|
return str(self)
|
|
175
175
|
|
|
176
|
+
class VideoColourSpace(IntEnum):
|
|
177
|
+
'''Video colour space encoding format.'''
|
|
178
|
+
RGB = 0
|
|
179
|
+
YUV444 = 1
|
|
180
|
+
YUV422 = 2
|
|
181
|
+
YUV420 = 3
|
|
182
|
+
|
|
183
|
+
def __str__(self) -> str:
|
|
184
|
+
if self.value == 0:
|
|
185
|
+
return 'RGB'
|
|
186
|
+
if self.value == 1:
|
|
187
|
+
return '4:4:4'
|
|
188
|
+
if self.value == 2:
|
|
189
|
+
return '4:2:2'
|
|
190
|
+
if self.value == 3:
|
|
191
|
+
return '4:2:0'
|
|
192
|
+
return 'unknown'
|
|
193
|
+
|
|
194
|
+
class VideoSignalDetails:
|
|
195
|
+
"""
|
|
196
|
+
Video format of a bay: CTA-861 svd, colour space, bit depth, frame rate kind.
|
|
197
|
+
|
|
198
|
+
``from_report`` says where it came from, because the two sources do not carry
|
|
199
|
+
the same currency. A signal status report is the live format and arrives on a
|
|
200
|
+
signal change. The bay config snapshot is a fallback for a bay no report has
|
|
201
|
+
been seen for: it refreshes on the bay config broadcast cycle, so it can lag a
|
|
202
|
+
change by a broadcast interval.
|
|
203
|
+
|
|
204
|
+
``bpp`` is a bit depth in both cases, not a wire index. None means no format
|
|
205
|
+
is set; 0 means one is set whose depth is not known, and those are different
|
|
206
|
+
answers.
|
|
207
|
+
"""
|
|
208
|
+
def __init__(self, svd:int|None, colour:VideoColourSpace|None, bpp:int|None,
|
|
209
|
+
non_int:bool|None, from_report:bool) -> None:
|
|
210
|
+
self._svd = svd
|
|
211
|
+
self._colour = colour
|
|
212
|
+
self._bpp = bpp
|
|
213
|
+
self._non_int = non_int
|
|
214
|
+
self._from_report = from_report
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def svd(self) -> int|None:
|
|
218
|
+
'''CTA-861 short video descriptor, or None when no format is set.'''
|
|
219
|
+
return self._svd
|
|
220
|
+
|
|
221
|
+
@property
|
|
222
|
+
def colour(self) -> VideoColourSpace|None:
|
|
223
|
+
'''Colour space, or None when no format is set or the value is unknown to this build.'''
|
|
224
|
+
return self._colour
|
|
225
|
+
|
|
226
|
+
@property
|
|
227
|
+
def bpp(self) -> int|None:
|
|
228
|
+
'''Bit depth, 0 when a format is set whose depth is not known.'''
|
|
229
|
+
return self._bpp
|
|
230
|
+
|
|
231
|
+
@property
|
|
232
|
+
def non_int(self) -> bool|None:
|
|
233
|
+
'''True for a non-integer (1000/1001) frame rate.'''
|
|
234
|
+
return self._non_int
|
|
235
|
+
|
|
236
|
+
@property
|
|
237
|
+
def from_report(self) -> bool:
|
|
238
|
+
'''True when this came from a signal status report rather than the bay config snapshot.'''
|
|
239
|
+
return self._from_report
|
|
240
|
+
|
|
241
|
+
def __eq__(self, other:Any) -> bool:
|
|
242
|
+
if not isinstance(other, VideoSignalDetails):
|
|
243
|
+
return NotImplemented
|
|
244
|
+
return (self._svd == other._svd) and (self._colour == other._colour) \
|
|
245
|
+
and (self._bpp == other._bpp) and (self._non_int == other._non_int)
|
|
246
|
+
|
|
247
|
+
def __hash__(self) -> int:
|
|
248
|
+
return hash((self._svd, self._colour, self._bpp, self._non_int))
|
|
249
|
+
|
|
250
|
+
def __str__(self) -> str:
|
|
251
|
+
if self._svd is None:
|
|
252
|
+
return 'no format'
|
|
253
|
+
src = 'report' if self._from_report else 'bay config'
|
|
254
|
+
bpp = f', {self._bpp}bpp' if self._bpp else ''
|
|
255
|
+
return f'svd {self._svd}, {self._colour}{bpp} ({src})'
|
|
256
|
+
|
|
257
|
+
def __repr__(self) -> str:
|
|
258
|
+
return str(self)
|
|
259
|
+
|
|
176
260
|
class SignalStatus:
|
|
177
|
-
def __init__(self, detected:bool, description:str|None=None
|
|
261
|
+
def __init__(self, detected:bool, description:str|None=None,
|
|
262
|
+
details:VideoSignalDetails|None=None) -> None:
|
|
178
263
|
self._detected = detected
|
|
179
264
|
self._description = description
|
|
265
|
+
self._details = details
|
|
180
266
|
|
|
181
267
|
@property
|
|
182
268
|
def detected(self) -> bool:
|
|
@@ -186,6 +272,11 @@ class SignalStatus:
|
|
|
186
272
|
def description(self) -> str|None:
|
|
187
273
|
return self._description
|
|
188
274
|
|
|
275
|
+
@property
|
|
276
|
+
def details(self) -> VideoSignalDetails|None:
|
|
277
|
+
'''Structured video format, or None when the report carried no valid stream.'''
|
|
278
|
+
return self._details
|
|
279
|
+
|
|
189
280
|
class AmpDolbySettings:
|
|
190
281
|
"""
|
|
191
282
|
Dolby Digital settings for an amplifier
|
|
@@ -984,6 +1075,15 @@ class BayBase(ABC):
|
|
|
984
1075
|
def signal_type(self) -> str:
|
|
985
1076
|
'''Audio or video signal type'''
|
|
986
1077
|
|
|
1078
|
+
@property
|
|
1079
|
+
@abstractmethod
|
|
1080
|
+
def video_details(self) -> VideoSignalDetails|None:
|
|
1081
|
+
'''Current video format, or None if neither source has carried one.
|
|
1082
|
+
|
|
1083
|
+
Prefers the signal status report, which arrives on a signal change, and
|
|
1084
|
+
falls back to the bay config snapshot for a bay no report has been seen
|
|
1085
|
+
for. Read from_report to tell which one this is.'''
|
|
1086
|
+
|
|
987
1087
|
@property
|
|
988
1088
|
@abstractmethod
|
|
989
1089
|
def hpd_detected(self) -> bool:
|
|
@@ -1795,7 +1895,19 @@ class DeviceBase(ABC):
|
|
|
1795
1895
|
@property
|
|
1796
1896
|
@abstractmethod
|
|
1797
1897
|
def protocol(self) -> int:
|
|
1798
|
-
|
|
1898
|
+
"""Highest protocol version this device advertises, or 0 if none has been seen."""
|
|
1899
|
+
|
|
1900
|
+
@abstractmethod
|
|
1901
|
+
def supports_opcode(self, opcode:int) -> bool:
|
|
1902
|
+
"""True when this device can receive opcode.
|
|
1903
|
+
|
|
1904
|
+
Each opcode has a minimum protocol version, and a device whose cap is
|
|
1905
|
+
lower drops the frame without answering - there is no NAK at any layer,
|
|
1906
|
+
so an ungated send looks like success. A device that has advertised no
|
|
1907
|
+
version yet returns True: refusing on that would break every send made
|
|
1908
|
+
before the first hello arrives, and the firmware itself only compares
|
|
1909
|
+
against a version it has.
|
|
1910
|
+
"""
|
|
1799
1911
|
|
|
1800
1912
|
@property
|
|
1801
1913
|
@abstractmethod
|
|
@@ -48,12 +48,35 @@ class ConnectStatus(IntEnum):
|
|
|
48
48
|
CONNECTED: int
|
|
49
49
|
DISCONNECTED: int
|
|
50
50
|
|
|
51
|
+
class VideoColourSpace(IntEnum):
|
|
52
|
+
RGB: int
|
|
53
|
+
YUV444: int
|
|
54
|
+
YUV422: int
|
|
55
|
+
YUV420: int
|
|
56
|
+
|
|
57
|
+
class VideoSignalDetails:
|
|
58
|
+
def __init__(self, svd: int | None, colour: VideoColourSpace | None, bpp: int | None, non_int: bool | None, from_report: bool) -> None: ...
|
|
59
|
+
@property
|
|
60
|
+
def svd(self) -> int | None: ...
|
|
61
|
+
@property
|
|
62
|
+
def colour(self) -> VideoColourSpace | None: ...
|
|
63
|
+
@property
|
|
64
|
+
def bpp(self) -> int | None: ...
|
|
65
|
+
@property
|
|
66
|
+
def non_int(self) -> bool | None: ...
|
|
67
|
+
@property
|
|
68
|
+
def from_report(self) -> bool: ...
|
|
69
|
+
def __eq__(self, other: Any) -> bool: ...
|
|
70
|
+
def __hash__(self) -> int: ...
|
|
71
|
+
|
|
51
72
|
class SignalStatus:
|
|
52
|
-
def __init__(self, detected: bool, description: str | None = None) -> None: ...
|
|
73
|
+
def __init__(self, detected: bool, description: str | None = None, details: VideoSignalDetails | None = None) -> None: ...
|
|
53
74
|
@property
|
|
54
75
|
def detected(self) -> bool: ...
|
|
55
76
|
@property
|
|
56
77
|
def description(self) -> str | None: ...
|
|
78
|
+
@property
|
|
79
|
+
def details(self) -> VideoSignalDetails | None: ...
|
|
57
80
|
|
|
58
81
|
class AmpDolbySettings:
|
|
59
82
|
mode: int
|
|
@@ -420,6 +443,9 @@ class BayBase(ABC):
|
|
|
420
443
|
def signal_type(self) -> str: ...
|
|
421
444
|
@property
|
|
422
445
|
@abstractmethod
|
|
446
|
+
def video_details(self) -> VideoSignalDetails | None: ...
|
|
447
|
+
@property
|
|
448
|
+
@abstractmethod
|
|
423
449
|
def hpd_detected(self) -> bool: ...
|
|
424
450
|
@property
|
|
425
451
|
@abstractmethod
|
|
@@ -796,6 +822,8 @@ class DeviceBase(ABC):
|
|
|
796
822
|
@property
|
|
797
823
|
@abstractmethod
|
|
798
824
|
def protocol(self) -> int: ...
|
|
825
|
+
@abstractmethod
|
|
826
|
+
def supports_opcode(self, opcode: int) -> bool: ...
|
|
799
827
|
@property
|
|
800
828
|
@abstractmethod
|
|
801
829
|
def is_mesh_master(self) -> bool: ...
|
|
@@ -14,7 +14,7 @@ Default communication modes:
|
|
|
14
14
|
"""
|
|
15
15
|
import os
|
|
16
16
|
|
|
17
|
-
VERSION = '5.
|
|
17
|
+
VERSION = '5.2.0'
|
|
18
18
|
__version__ = VERSION
|
|
19
19
|
|
|
20
20
|
MX_BCAST_UDP_PORT = 8811
|
|
@@ -26,6 +26,17 @@ MX_MCAST_UDP_IP = '224.8.8.8'
|
|
|
26
26
|
MX_MCAST_UDP_PORT = 8812
|
|
27
27
|
"""UDP port used in multicast mode (default)."""
|
|
28
28
|
|
|
29
|
+
MXR_HELLO_INTERVAL_MIN = 2.5
|
|
30
|
+
"""Shortest gap between hello announcements, in seconds (firmware ``hello_timeout`` minimum)."""
|
|
31
|
+
|
|
32
|
+
MXR_HELLO_INTERVAL_RAND = 2.5
|
|
33
|
+
"""Random spread added to each hello interval, in seconds.
|
|
34
|
+
|
|
35
|
+
Mirrors ``rand_timeout_init(&mxr_class->hello_timeout, 2500, 2500)``: the delay is
|
|
36
|
+
re-drawn after every hello that is sent, so devices announcing on the same mesh do
|
|
37
|
+
not converge into synchronised bursts.
|
|
38
|
+
"""
|
|
39
|
+
|
|
29
40
|
V2IP_UDP_PORT_VIDEO = 50020
|
|
30
41
|
"""Default destination UDP port for a V2IP video stream (firmware ``V2IP_UDP_PORT_VIDEO``)."""
|
|
31
42
|
|
|
@@ -651,6 +651,10 @@ class EdidProfile(IntEnum):
|
|
|
651
651
|
return '4K HDR Dolby Atmos'
|
|
652
652
|
if (self.value >= EdidProfile.SINK_1.value) and (self.value <= EdidProfile.SINK_32.value):
|
|
653
653
|
return f'copy from sink #{self.value - EdidProfile.SINK_1.value + 1}'
|
|
654
|
+
if self.value == EdidProfile.UNKNOWN.value:
|
|
655
|
+
# decode_enum lands here for any value this build has no member for,
|
|
656
|
+
# so it is a profile we cannot name, not one somebody configured
|
|
657
|
+
return 'unknown'
|
|
654
658
|
return f"custom #{self.value}"
|
|
655
659
|
|
|
656
660
|
TEMPLATE_1080P_STEREO = 0
|
|
@@ -65,7 +65,7 @@ class FrameAmpZoneSettings(FrameBase):
|
|
|
65
65
|
for r in range(5):
|
|
66
66
|
payload.append(settings.eq_right[r] & 0xFF)
|
|
67
67
|
payload += bytes([0, 0]) # padding
|
|
68
|
-
return FrameBase.construct_base(mxr=mxr, opcode=0x3D, protocol=0x1C, payload=payload)
|
|
68
|
+
return FrameBase.construct_base(target=target, mxr=mxr, opcode=0x3D, protocol=0x1C, payload=payload)
|
|
69
69
|
|
|
70
70
|
@property
|
|
71
71
|
def target_device(self) -> DeviceBase|None:
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
'''Base class and utilities for decoded MX Remote protocol frames.'''
|
|
9
9
|
|
|
10
|
+
from typing import Any
|
|
11
|
+
import logging
|
|
10
12
|
from functools import cached_property
|
|
11
13
|
import time
|
|
12
14
|
from .Constants import MXR_OPCODE_VERSIONS, MXR_PROTOCOL_VERSION
|
|
@@ -14,6 +16,8 @@ from .FrameHeader import FrameHeader
|
|
|
14
16
|
from ..Interface import DeviceBase, DeviceRegistry, BayBase
|
|
15
17
|
from ..Uid import MxrDeviceUid
|
|
16
18
|
|
|
19
|
+
_LOGGER = logging.getLogger(__name__)
|
|
20
|
+
|
|
17
21
|
def append_payload_str(payload:list[int], value:str, sz:int) -> list[int]:
|
|
18
22
|
'''Append a fixed-size ASCII string to a payload byte list, zero-padded to sz.
|
|
19
23
|
|
|
@@ -46,14 +50,19 @@ class FrameBase:
|
|
|
46
50
|
return MXR_OPCODE_VERSIONS.get(opcode, MXR_PROTOCOL_VERSION)
|
|
47
51
|
|
|
48
52
|
@staticmethod
|
|
49
|
-
def construct_base(mxr:DeviceRegistry, opcode:int, protocol:int|None=None, payload:bytes=bytes([]), size:int|None=None) -> 'FrameBase|None':
|
|
53
|
+
def construct_base(mxr:DeviceRegistry, opcode:int, protocol:int|None=None, payload:bytes=bytes([]), size:int|None=None, target:Any=None) -> 'FrameBase|None':
|
|
50
54
|
'''Construct a new frame for transmission with the given opcode and payload.
|
|
51
55
|
|
|
52
56
|
Leave protocol unset unless the payload really does need a newer
|
|
53
57
|
receiver than the opcode itself does; it then defaults to the opcode's
|
|
54
|
-
own minimum from MXR_OPCODE_VERSIONS.
|
|
58
|
+
own minimum from MXR_OPCODE_VERSIONS.
|
|
59
|
+
|
|
60
|
+
Pass target for anything addressed at one device, so a frame it cannot
|
|
61
|
+
receive is refused here rather than sent into silence.'''
|
|
55
62
|
if (protocol is None):
|
|
56
63
|
protocol = FrameBase.opcode_protocol(opcode)
|
|
64
|
+
if not FrameBase.target_supports(mxr=mxr, target=target, opcode=opcode):
|
|
65
|
+
return None
|
|
57
66
|
header = FrameHeader.construct(mxr=mxr, opcode=opcode, protocol=protocol)
|
|
58
67
|
if (header is None):
|
|
59
68
|
return None
|
|
@@ -66,6 +75,28 @@ class FrameBase:
|
|
|
66
75
|
rv.payload = payload
|
|
67
76
|
return rv
|
|
68
77
|
|
|
78
|
+
@staticmethod
|
|
79
|
+
def target_supports(mxr:DeviceRegistry, target:Any, opcode:int) -> bool:
|
|
80
|
+
'''Whether a frame addressed at target can be received by it.
|
|
81
|
+
|
|
82
|
+
Accepts what the constructFrame helpers are given: a device, a bay, a
|
|
83
|
+
uid, or None for a broadcast, which has no single target and is always
|
|
84
|
+
allowed.
|
|
85
|
+
'''
|
|
86
|
+
if (target is None):
|
|
87
|
+
return True
|
|
88
|
+
device = getattr(target, 'device', None) or (target if hasattr(target, 'supports_opcode') else None)
|
|
89
|
+
if (device is None):
|
|
90
|
+
device = mxr.get_by_uid(target) if isinstance(target, (str, bytes, MxrDeviceUid)) else None
|
|
91
|
+
if (device is None) or not hasattr(device, 'supports_opcode'):
|
|
92
|
+
return True
|
|
93
|
+
if device.supports_opcode(opcode):
|
|
94
|
+
return True
|
|
95
|
+
_LOGGER.warning(
|
|
96
|
+
f"not sending opcode {opcode:02X} to {device}: it advertises protocol "
|
|
97
|
+
f"{device.protocol:02X} and the opcode needs {FrameBase.opcode_protocol(opcode):02X}")
|
|
98
|
+
return False
|
|
99
|
+
|
|
69
100
|
@property
|
|
70
101
|
def mxr(self) -> DeviceRegistry:
|
|
71
102
|
'''Remote instance.'''
|
|
@@ -4,6 +4,7 @@ from .Constants import MXR_OPCODE_VERSIONS as MXR_OPCODE_VERSIONS, MXR_PROTOCOL_
|
|
|
4
4
|
from .FrameHeader import FrameHeader as FrameHeader
|
|
5
5
|
from _typeshed import Incomplete
|
|
6
6
|
from functools import cached_property
|
|
7
|
+
from typing import Any
|
|
7
8
|
|
|
8
9
|
def append_payload_str(payload: list[int], value: str, sz: int) -> list[int]: ...
|
|
9
10
|
|
|
@@ -14,7 +15,9 @@ class FrameBase:
|
|
|
14
15
|
@staticmethod
|
|
15
16
|
def opcode_protocol(opcode: int) -> int: ...
|
|
16
17
|
@staticmethod
|
|
17
|
-
def construct_base(mxr: DeviceRegistry, opcode: int, protocol: int | None = None, payload: bytes = ..., size: int | None = None) -> FrameBase | None: ...
|
|
18
|
+
def construct_base(mxr: DeviceRegistry, opcode: int, protocol: int | None = None, payload: bytes = ..., size: int | None = None, target: Any = None) -> FrameBase | None: ...
|
|
19
|
+
@staticmethod
|
|
20
|
+
def target_supports(mxr: DeviceRegistry, target: Any, opcode: int) -> bool: ...
|
|
18
21
|
@property
|
|
19
22
|
def mxr(self) -> DeviceRegistry: ...
|
|
20
23
|
@property
|
|
@@ -22,7 +22,7 @@ class FrameBayHide(FrameBase):
|
|
|
22
22
|
bytes([(target.port >> 0) & 0xFF, (target.port >> 8) & 0xFF]) + \
|
|
23
23
|
bytes([1 if hidden else 0]) + \
|
|
24
24
|
bytes([0 for _ in range(5)])
|
|
25
|
-
return FrameBase.construct_base(mxr=mxr, opcode=0x27, payload=payload)
|
|
25
|
+
return FrameBase.construct_base(target=target, mxr=mxr, opcode=0x27, payload=payload)
|
|
26
26
|
|
|
27
27
|
@cached_property
|
|
28
28
|
def target(self) -> DeviceBase|None:
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
######################################################
|
|
2
|
+
## MX Remote Python Interface ##
|
|
3
|
+
## ##
|
|
4
|
+
## author: Lars Op den Kamp (lars@opdenkamp-it.nl) ##
|
|
5
|
+
## copyright (c) 2021-2026 Op den Kamp IT Solutions ##
|
|
6
|
+
######################################################
|
|
7
|
+
'''Protocol frame for EDID profile change commands.'''
|
|
8
|
+
|
|
9
|
+
from .FrameBase import FrameBase
|
|
10
|
+
from ..Interface import DeviceRegistry, DeviceBase, EdidProfile, MxrDeviceUid
|
|
11
|
+
from .Constants import decode_enum
|
|
12
|
+
|
|
13
|
+
class FrameEDIDProfile(FrameBase):
|
|
14
|
+
'''Change an EDID profile on a target device.'''
|
|
15
|
+
@staticmethod
|
|
16
|
+
def construct(mxr:DeviceRegistry, target:DeviceBase, profile:EdidProfile) -> FrameBase|None:
|
|
17
|
+
'''Build an EDID profile change frame for transmission.'''
|
|
18
|
+
payload = target.remote_id.byte_value + \
|
|
19
|
+
bytes([(profile.value >> 0) & 0xFF, (profile.value >> 8) & 0xFF]) + \
|
|
20
|
+
bytes([0 for _ in range(6)])
|
|
21
|
+
return FrameBase.construct_base(target=target, mxr=mxr, opcode=0x34, payload=payload)
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def target_uid(self) -> MxrDeviceUid|None:
|
|
25
|
+
'''Device whose EDID profile is being set.'''
|
|
26
|
+
return self.payload_uuid(0)
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def target_device(self) -> DeviceBase|None:
|
|
30
|
+
return self.mxr.get_by_uid(self.target_uid)
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def profile_raw(self) -> int|None:
|
|
34
|
+
'''Profile as carried on the wire, before mapping to a local one.'''
|
|
35
|
+
return self.payload_u16(16)
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def profile(self) -> EdidProfile|None:
|
|
39
|
+
'''Requested EDID profile.
|
|
40
|
+
|
|
41
|
+
A value this build has no member for reads as EdidProfile.UNKNOWN rather
|
|
42
|
+
than raising or being folded into a real profile. Read profile_raw when
|
|
43
|
+
the distinction matters.
|
|
44
|
+
'''
|
|
45
|
+
return decode_enum(EdidProfile, self.profile_raw)
|
|
46
|
+
|
|
47
|
+
def __str__(self) -> str:
|
|
48
|
+
# The frame names no bay because it never needs to: the receiver applies it
|
|
49
|
+
# to MBAY_ID_0, its own input bay, so a device is the whole address.
|
|
50
|
+
target = self.target_device if (self.target_device is not None) else self.target_uid
|
|
51
|
+
profile = f"{self.profile} ({self.profile_raw})" \
|
|
52
|
+
if (self.profile == EdidProfile.UNKNOWN) else str(self.profile)
|
|
53
|
+
return f"set edid profile: {target} input 0 -> {profile}"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from ..Interface import DeviceBase as DeviceBase, DeviceRegistry as DeviceRegistry, EdidProfile as EdidProfile, MxrDeviceUid as MxrDeviceUid
|
|
2
|
+
from .Constants import decode_enum as decode_enum
|
|
3
|
+
from .FrameBase import FrameBase as FrameBase
|
|
4
|
+
|
|
5
|
+
class FrameEDIDProfile(FrameBase):
|
|
6
|
+
@staticmethod
|
|
7
|
+
def construct(mxr: DeviceRegistry, target: DeviceBase, profile: EdidProfile) -> FrameBase | None: ...
|
|
8
|
+
@property
|
|
9
|
+
def target_uid(self) -> MxrDeviceUid | None: ...
|
|
10
|
+
@property
|
|
11
|
+
def target_device(self) -> DeviceBase | None: ...
|
|
12
|
+
@property
|
|
13
|
+
def profile_raw(self) -> int | None: ...
|
|
14
|
+
@property
|
|
15
|
+
def profile(self) -> EdidProfile | None: ...
|
|
@@ -55,7 +55,7 @@ class FrameMeshOperation(FrameBase):
|
|
|
55
55
|
payload += option.remote_id.byte_value
|
|
56
56
|
else:
|
|
57
57
|
payload += bytes([0 for _ in range(16)])
|
|
58
|
-
return FrameBase.construct_base(mxr=mxr, opcode=0x3B, payload=payload)
|
|
58
|
+
return FrameBase.construct_base(target=target, mxr=mxr, opcode=0x3B, payload=payload)
|
|
59
59
|
|
|
60
60
|
@cached_property
|
|
61
61
|
def operation(self) -> MeshOperation|None:
|
|
@@ -21,7 +21,7 @@ class FrameRCAction(FrameBase):
|
|
|
21
21
|
'''Build an RC action frame for transmission.'''
|
|
22
22
|
payload = target.device.remote_id.byte_value
|
|
23
23
|
payload += bytes([(target.port & 0xFF), ((target.port >> 8) & 0xFF), (int(action.value) & 0xFF), ((int(action.value) >> 8) & 0xFF)])
|
|
24
|
-
return FrameBase.construct_base(mxr=mxr, opcode=0x0E, payload=payload)
|
|
24
|
+
return FrameBase.construct_base(target=target, mxr=mxr, opcode=0x0E, payload=payload)
|
|
25
25
|
|
|
26
26
|
@cached_property
|
|
27
27
|
def bay(self) -> BayBase|None:
|
|
@@ -15,7 +15,7 @@ class FrameReboot(FrameBase):
|
|
|
15
15
|
@staticmethod
|
|
16
16
|
def construct(mxr:DeviceRegistry, target:DeviceBase) -> FrameBase|None:
|
|
17
17
|
'''Build a reboot frame for transmission to the target device.'''
|
|
18
|
-
return FrameBase.construct_base(mxr=mxr, opcode=0x28, payload=target.remote_id.byte_value)
|
|
18
|
+
return FrameBase.construct_base(target=target, mxr=mxr, opcode=0x28, payload=target.remote_id.byte_value)
|
|
19
19
|
|
|
20
20
|
def __str__(self) -> str:
|
|
21
21
|
return "reboot"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
######################################################
|
|
2
|
+
## MX Remote Python Interface ##
|
|
3
|
+
## ##
|
|
4
|
+
## author: Lars Op den Kamp (lars@opdenkamp-it.nl) ##
|
|
5
|
+
## copyright (c) 2021-2026 Op den Kamp IT Solutions ##
|
|
6
|
+
######################################################
|
|
7
|
+
'''Protocol frame for changing a bay name on a device.'''
|
|
8
|
+
|
|
9
|
+
from .Constants import MXR_DEVICE_NAME_LEN
|
|
10
|
+
from .FrameBase import FrameBase
|
|
11
|
+
from ..Interface import DeviceRegistry, BayBase, DeviceBase, MxrDeviceUid
|
|
12
|
+
|
|
13
|
+
class FrameSetName(FrameBase):
|
|
14
|
+
'''Change a bay name.'''
|
|
15
|
+
@staticmethod
|
|
16
|
+
def construct(mxr:DeviceRegistry, target:BayBase, name:str) -> FrameBase|None:
|
|
17
|
+
'''Build a bay name change frame for transmission.'''
|
|
18
|
+
# 15, not 16: the receiver copies MXR_DEVICE_NAME_LEN - 1 bytes and
|
|
19
|
+
# terminates, so a name filling the field is stored one short. Sending
|
|
20
|
+
# all 16 means the bay ends up named something other than what was
|
|
21
|
+
# asked for, with success reported either way.
|
|
22
|
+
name = name[:MXR_DEVICE_NAME_LEN - 1]
|
|
23
|
+
name_bytes = name.encode(encoding='ascii', errors='replace').ljust(MXR_DEVICE_NAME_LEN, b'\x00')
|
|
24
|
+
payload = target.device.remote_id.byte_value + \
|
|
25
|
+
bytes([(target.port >> 0) & 0xFF, (target.port >> 8) & 0xFF]) + \
|
|
26
|
+
name_bytes
|
|
27
|
+
return FrameBase.construct_base(target=target, mxr=mxr, opcode=0x22, protocol=0x11, payload=payload)
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def target_uid(self) -> MxrDeviceUid|None:
|
|
31
|
+
'''Device whose bay is being renamed.'''
|
|
32
|
+
return self.payload_uuid(0)
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def target_device(self) -> DeviceBase|None:
|
|
36
|
+
return self.mxr.get_by_uid(self.target_uid)
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def port(self) -> int|None:
|
|
40
|
+
'''Port id of the bay being renamed (mbay_port_id, a u16).'''
|
|
41
|
+
return self.payload_u16(16)
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def bay(self) -> BayBase|None:
|
|
45
|
+
return self.payload_bay(device=self.target_device, idx=16, u16=True)
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def name(self) -> str|None:
|
|
49
|
+
'''The new name, read from its fixed 16-byte field.
|
|
50
|
+
|
|
51
|
+
The field carries no terminator of its own, so a name that fills it has
|
|
52
|
+
none and must not be read as a C string: read it at its width. A sender
|
|
53
|
+
that fills all MXR_DEVICE_NAME_LEN bytes is readable here, even though a
|
|
54
|
+
receiver keeps only the first MXR_DEVICE_NAME_LEN - 1 of them.
|
|
55
|
+
'''
|
|
56
|
+
return self.payload_str(18, 16)
|
|
57
|
+
|
|
58
|
+
def __str__(self) -> str:
|
|
59
|
+
target = self.bay if (self.bay is not None) else self.target_device
|
|
60
|
+
if (target is None):
|
|
61
|
+
return f"set bay name: port {self.port} -> '{self.name}'"
|
|
62
|
+
return f"set bay name: {target} -> '{self.name}'"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from ..Interface import BayBase as BayBase, DeviceBase as DeviceBase, DeviceRegistry as DeviceRegistry, MxrDeviceUid as MxrDeviceUid
|
|
2
|
+
from .Constants import MXR_DEVICE_NAME_LEN as MXR_DEVICE_NAME_LEN
|
|
3
|
+
from .FrameBase import FrameBase as FrameBase
|
|
4
|
+
|
|
5
|
+
class FrameSetName(FrameBase):
|
|
6
|
+
@staticmethod
|
|
7
|
+
def construct(mxr: DeviceRegistry, target: BayBase, name: str) -> FrameBase | None: ...
|
|
8
|
+
@property
|
|
9
|
+
def target_uid(self) -> MxrDeviceUid | None: ...
|
|
10
|
+
@property
|
|
11
|
+
def target_device(self) -> DeviceBase | None: ...
|
|
12
|
+
@property
|
|
13
|
+
def port(self) -> int | None: ...
|
|
14
|
+
@property
|
|
15
|
+
def bay(self) -> BayBase | None: ...
|
|
16
|
+
@property
|
|
17
|
+
def name(self) -> str | None: ...
|
|
@@ -9,29 +9,12 @@
|
|
|
9
9
|
from enum import IntEnum
|
|
10
10
|
from functools import cached_property
|
|
11
11
|
from .FrameBase import FrameBase
|
|
12
|
-
from ..Interface import BayBase, SignalStatus
|
|
12
|
+
from ..Interface import VideoColourSpace, VideoSignalDetails, BayBase, SignalStatus
|
|
13
13
|
from ..Uid import MxrDeviceUid
|
|
14
14
|
import struct
|
|
15
15
|
from .Svd import SvdMap, Svd
|
|
16
16
|
from .Constants import BayStatusMask, MxrSignalType, decode_enum
|
|
17
17
|
|
|
18
|
-
class VideoColourSpace(IntEnum):
|
|
19
|
-
'''Video colour space encoding format.'''
|
|
20
|
-
RGB = 0
|
|
21
|
-
YUV444 = 1
|
|
22
|
-
YUV422 = 2
|
|
23
|
-
YUV420 = 3
|
|
24
|
-
|
|
25
|
-
def __str__(self) -> str:
|
|
26
|
-
if self.value == 0:
|
|
27
|
-
return 'RGB'
|
|
28
|
-
if self.value == 1:
|
|
29
|
-
return '4:4:4'
|
|
30
|
-
if self.value == 2:
|
|
31
|
-
return '4:2:2'
|
|
32
|
-
if self.value == 3:
|
|
33
|
-
return '4:2:0'
|
|
34
|
-
return 'unknown'
|
|
35
18
|
|
|
36
19
|
class AvDetailsSupportFlags:
|
|
37
20
|
'''Bitmask indicating which AV detail fields are present in the frame.'''
|
|
@@ -318,9 +301,17 @@ class FrameSignalStatusNew(FrameBase):
|
|
|
318
301
|
if self.stream_flags.video_hdr:
|
|
319
302
|
signal_type += ' HDR'
|
|
320
303
|
signal_type += f' / {self.frame_rate}Hz'
|
|
304
|
+
details = VideoSignalDetails(
|
|
305
|
+
svd=self.video.svd.id,
|
|
306
|
+
colour=self.video.colour_space,
|
|
307
|
+
bpp=self.video.colour_depth,
|
|
308
|
+
non_int=(self.stream_flags is not None) and self.stream_flags.video_non_int_clock,
|
|
309
|
+
from_report=True)
|
|
321
310
|
else:
|
|
322
311
|
signal_type = 'No Signal'
|
|
323
|
-
|
|
312
|
+
details = None
|
|
313
|
+
bay.on_mxr_update(SignalStatus(detected=self.stream_valid, description=signal_type,
|
|
314
|
+
details=details))
|
|
324
315
|
|
|
325
316
|
def __str__(self) -> str:
|
|
326
317
|
if (self.payload is None):
|
|
@@ -1,18 +1,12 @@
|
|
|
1
|
-
from ..Interface import BayBase as BayBase, SignalStatus as SignalStatus
|
|
1
|
+
from ..Interface import BayBase as BayBase, SignalStatus as SignalStatus, VideoColourSpace as VideoColourSpace, VideoSignalDetails as VideoSignalDetails
|
|
2
2
|
from ..Uid import MxrDeviceUid as MxrDeviceUid
|
|
3
3
|
from .Constants import BayStatusMask as BayStatusMask, MxrSignalType as MxrSignalType, decode_enum as decode_enum
|
|
4
4
|
from .FrameBase import FrameBase as FrameBase
|
|
5
5
|
from .Svd import Svd as Svd, SvdMap as SvdMap
|
|
6
6
|
from _typeshed import Incomplete
|
|
7
|
-
from enum import IntEnum
|
|
7
|
+
from enum import IntEnum as IntEnum
|
|
8
8
|
from functools import cached_property
|
|
9
9
|
|
|
10
|
-
class VideoColourSpace(IntEnum):
|
|
11
|
-
RGB: int
|
|
12
|
-
YUV444: int
|
|
13
|
-
YUV422: int
|
|
14
|
-
YUV420: int
|
|
15
|
-
|
|
16
10
|
class AvDetailsSupportFlags:
|
|
17
11
|
data: Incomplete
|
|
18
12
|
def __init__(self, data: int) -> None: ...
|
|
@@ -30,7 +30,7 @@ class FrameTXRCAction(FrameBase):
|
|
|
30
30
|
'''Build an RC action frame for transmission to the target bay.'''
|
|
31
31
|
payload = target.device.remote_id.byte_value
|
|
32
32
|
payload += bytes([(target.port & 0xFF), ((target.port >> 8) & 0xFF), (int(action.value) & 0xFF), ((int(action.value) >> 8) & 0xFF)])
|
|
33
|
-
return FrameBase.construct_base(mxr=mxr, opcode=0x0E, payload=payload)
|
|
33
|
+
return FrameBase.construct_base(target=target, mxr=mxr, opcode=0x0E, payload=payload)
|
|
34
34
|
|
|
35
35
|
@cached_property
|
|
36
36
|
def target_device(self) -> DeviceBase|None:
|
|
@@ -30,7 +30,7 @@ class FrameTXRCKey(FrameBase):
|
|
|
30
30
|
payload = target.device.remote_id.byte_value
|
|
31
31
|
payload += bytes([(target.port & 0xFF), ((target.port >> 8) & 0xFF),
|
|
32
32
|
(int(key.value) & 0xFF), ((int(key.value) >> 8) & 0xFF)])
|
|
33
|
-
return FrameBase.construct_base(mxr=mxr, opcode=0x0C, payload=payload)
|
|
33
|
+
return FrameBase.construct_base(target=target, mxr=mxr, opcode=0x0C, payload=payload)
|
|
34
34
|
|
|
35
35
|
@cached_property
|
|
36
36
|
def target_uid(self) -> MxrDeviceUid|None:
|