mx-remote 5.1.0__tar.gz → 5.3.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.1.0 → mx_remote-5.3.0}/PKG-INFO +1 -1
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/const.py +12 -1
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/const.pyi +2 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/Constants.py +4 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/Factory.py +11 -4
- mx_remote-5.3.0/mx_remote/proto/FrameEDIDProfile.py +53 -0
- mx_remote-5.3.0/mx_remote/proto/FrameEDIDProfile.pyi +15 -0
- mx_remote-5.3.0/mx_remote/proto/FrameSetName.py +62 -0
- mx_remote-5.3.0/mx_remote/proto/FrameSetName.pyi +17 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/Remote.py +72 -19
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/Remote.pyi +1 -1
- mx_remote-5.3.0/tests/entry.py +59 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/handlers.py +28 -0
- mx_remote-5.3.0/tests/handlers_cover.py +268 -0
- mx_remote-5.3.0/tests/hello.py +214 -0
- mx_remote-5.3.0/tests/proto_stamp.py +122 -0
- mx_remote-5.3.0/tests/roundtrip.py +176 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/run.py +4 -0
- mx_remote-5.1.0/mx_remote/proto/FrameEDIDProfile.py +0 -23
- mx_remote-5.1.0/mx_remote/proto/FrameEDIDProfile.pyi +0 -6
- mx_remote-5.1.0/mx_remote/proto/FramePDUState.py +0 -72
- mx_remote-5.1.0/mx_remote/proto/FramePDUState.pyi +0 -23
- mx_remote-5.1.0/mx_remote/proto/FrameSetName.py +0 -26
- mx_remote-5.1.0/mx_remote/proto/FrameSetName.pyi +0 -6
- mx_remote-5.1.0/mx_remote/proto/FrameV2IPStreamDetails.py +0 -60
- mx_remote-5.1.0/mx_remote/proto/FrameV2IPStreamDetails.pyi +0 -17
- mx_remote-5.1.0/mx_remote/proto/PDUState.py +0 -100
- mx_remote-5.1.0/mx_remote/proto/PDUState.pyi +0 -29
- mx_remote-5.1.0/mx_remote/remote/P8PDU.py +0 -156
- mx_remote-5.1.0/mx_remote/remote/P8PDU.pyi +0 -46
- mx_remote-5.1.0/tests/proto_stamp.py +0 -60
- {mx_remote-5.1.0 → mx_remote-5.3.0}/.gitignore +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/CLAUDE.md +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/LICENSE +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/README.md +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/hatch_build.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/Interface.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/Interface.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/Uid.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/Uid.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/__init__.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/__init__.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/main.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/main.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/BayConfig.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/BayConfig.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/Constants.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/Data.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/Data.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/Factory.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameAmpDolbySettings.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameAmpDolbySettings.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameAmpZoneSettings.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameAmpZoneSettings.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameAudioClip.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameAudioClip.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameAudioSetRoute.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameAudioSetRoute.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameBase.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameBase.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameBayConfig.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameBayConfig.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameBayConfigSecondary.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameBayConfigSecondary.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameBayHide.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameBayHide.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameBayStatus.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameBayStatus.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameConnectStatus.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameConnectStatus.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameDebug.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameDebug.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameDiscover.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameDiscover.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameEDID.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameEDID.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameFactoryReset.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameFactoryReset.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameFilterStatus.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameFilterStatus.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameFirmwareVersion.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameFirmwareVersion.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameHeader.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameHeader.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameHello.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameHello.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameLinks.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameLinks.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameMeshOperation.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameMeshOperation.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameMirrorStatus.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameMirrorStatus.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameMonitoringPulse.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameMonitoringPulse.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameNetworkStatus.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameNetworkStatus.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FramePowerChange.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FramePowerChange.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameRCAction.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameRCAction.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameRCIr.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameRCIr.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameRCKey.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameRCKey.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameRCSettings.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameRCSettings.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameReboot.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameReboot.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameRoutingChange.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameRoutingChange.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSetInstaller.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSetInstaller.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSetRoute.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSetRoute.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSetupStatus.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSetupStatus.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSignalStatus.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSignalStatus.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSignalStatusNew.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSignalStatusNew.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSysTemperature.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSysTemperature.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSystemStatus.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSystemStatus.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameTXRCAction.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameTXRCAction.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameTXRCKey.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameTXRCKey.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameTopology.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameTopology.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameTxIR.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameTxIR.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameUpgradeFPGA.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameUpgradeFPGA.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPAudio.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPAudio.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPBayMapping.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPBayMapping.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPDetectBays.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPDetectBays.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPLink.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPLink.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPMultiviewer.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPMultiviewer.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPPowerSave.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPPowerSave.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPSetMaster.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPSetMaster.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPSourceSwitch.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPSourceSwitch.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPSources.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPSources.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPStats.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPStats.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPTiling.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPTiling.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPVideoWall.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPVideoWall.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameVolume.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameVolume.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameVolumeDown.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameVolumeDown.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameVolumeSet.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameVolumeSet.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameVolumeUp.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameVolumeUp.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/LinkConfig.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/LinkConfig.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/Multiviewer.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/Multiviewer.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/Svd.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/Svd.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/V2IPConfig.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/V2IPConfig.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/V2IPStats.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/V2IPStats.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/__init__.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/__init__.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/svd.csv +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/py.typed +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/Bay.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/Bay.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/ConnectionAsync.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/ConnectionAsync.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/Device.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/Device.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/Link.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/Link.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/State.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/State.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/V2IP.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/V2IP.pyi +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/pyproject.toml +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/audio_ir.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/capture.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/e2e.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/merge.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/newops.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/offsets.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/protogate.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/rcsettings.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/smoke.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/sources.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/stats.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/structs.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/txresult.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/unknowns.py +0 -0
- {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/vdetails.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: mx_remote
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.3.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/
|
|
@@ -14,7 +14,7 @@ Default communication modes:
|
|
|
14
14
|
"""
|
|
15
15
|
import os
|
|
16
16
|
|
|
17
|
-
VERSION = '5.
|
|
17
|
+
VERSION = '5.3.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
|
|
@@ -108,9 +108,6 @@ def _mxr_frame_factory(hdr:FrameHeader, timestamp:float) -> FrameBase|None:
|
|
|
108
108
|
if hdr.opcode == 0x1F:
|
|
109
109
|
from .FrameV2IPSourceSwitch import FrameV2IPSourceSwitch
|
|
110
110
|
return FrameV2IPSourceSwitch(header=hdr, timestamp=timestamp)
|
|
111
|
-
if hdr.opcode == 0x16:
|
|
112
|
-
from .FramePDUState import FramePDUState
|
|
113
|
-
return FramePDUState(header=hdr, timestamp=timestamp)
|
|
114
111
|
if hdr.opcode == 0x20:
|
|
115
112
|
from .FrameV2IPLink import FrameV2IPLinkStatus
|
|
116
113
|
return FrameV2IPLinkStatus(header=hdr, timestamp=timestamp)
|
|
@@ -225,11 +222,21 @@ def _mxr_frame_factory(hdr:FrameHeader, timestamp:float) -> FrameBase|None:
|
|
|
225
222
|
|
|
226
223
|
# Opcodes reaching here are ones current firmware does not emit:
|
|
227
224
|
#
|
|
225
|
+
# 0x16 PDU_STATE dead; no current MatrixOS build
|
|
226
|
+
# supports it
|
|
228
227
|
# 0x17..0x1E the CEC range never implemented
|
|
229
|
-
# 0x25
|
|
228
|
+
# 0x25 SYS_BAY_V2IP_DETAILS retired 2024-09, replaced by 0x3C
|
|
230
229
|
# 0x2D VIDEO_CLOCK_RATE_OLD superseded by clock_rate in 0x31
|
|
231
230
|
# 0x2E, 0x2F V2IP_BLIST_* behind V2IP_SUPPORT_BLACKLIST, which
|
|
232
231
|
# no project defines
|
|
232
|
+
# 0x33 RESERVED_1 was MX_OP_NB, the table's end sentinel
|
|
233
|
+
#
|
|
234
|
+
# Never reuse 0x16 or 0x25. Both carried a real payload - 0x16 a PDU's state,
|
|
235
|
+
# 0x25 v2ip_device_sources, with video, audio and anc at 0, 8 and 16,
|
|
236
|
+
# audio_return at 24 and source_clock at 32 - so a unit old enough to decode
|
|
237
|
+
# them reads anything reissued under those numbers as the old payload rather
|
|
238
|
+
# than ignoring it. 0x33 is reserved for a different reason: it was only ever
|
|
239
|
+
# the table's end marker, so no unit ever read a payload from it.
|
|
233
240
|
#
|
|
234
241
|
# Add decoders here rather than removing them: a unit on older firmware still
|
|
235
242
|
# emits superseded opcodes, which is what 0x06, 0x36 and 0x47 are for. An
|
|
@@ -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: ...
|
|
@@ -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: ...
|
|
@@ -14,10 +14,11 @@ import aiofiles
|
|
|
14
14
|
import aiohttp
|
|
15
15
|
import os
|
|
16
16
|
from pathlib import Path
|
|
17
|
+
import random
|
|
17
18
|
import time
|
|
18
19
|
|
|
19
20
|
from .ConnectionAsync import ConnectionAsync
|
|
20
|
-
from ..const import __version__
|
|
21
|
+
from ..const import MXR_HELLO_INTERVAL_MIN, MXR_HELLO_INTERVAL_RAND, __version__
|
|
21
22
|
from .Device import Device
|
|
22
23
|
from ..Interface import ConnectionCallbacks, DeviceRegistry, MxrDeviceUid, BayLinks, BayBase, DeviceBase, MxrCallbacks, AudioEndpoint, mxr_broadcast_address
|
|
23
24
|
from ..proto.Constants import MXR_PROTOCOL_VERSION
|
|
@@ -60,7 +61,8 @@ class Remote(DeviceRegistry, ConnectionCallbacks):
|
|
|
60
61
|
self._callbacks = State(callbacks, http_session)
|
|
61
62
|
self.remotes:dict[MxrDeviceUid,Device] = {}
|
|
62
63
|
self._links = BayLinks(self)
|
|
63
|
-
self.
|
|
64
|
+
self._hello_due = 0.0
|
|
65
|
+
self._probe_failures = 0
|
|
64
66
|
self._tasks:set[asyncio.Task[None]] = set()
|
|
65
67
|
self._uid:bytes|None = None
|
|
66
68
|
self._uid_path = uid_path
|
|
@@ -218,19 +220,56 @@ class Remote(DeviceRegistry, ConnectionCallbacks):
|
|
|
218
220
|
return True
|
|
219
221
|
return False
|
|
220
222
|
|
|
223
|
+
def _on_task_done(self, task:'asyncio.Task[None]') -> None:
|
|
224
|
+
'''Drop a finished background task, and report it if something killed it.
|
|
225
|
+
|
|
226
|
+
asyncio only surfaces an unretrieved task exception when the task object is
|
|
227
|
+
garbage collected, which is a different moment and may never come, so a task
|
|
228
|
+
that dies otherwise does so in silence. Retrieving it here is what makes any
|
|
229
|
+
future background task fail loudly, including one whose own guard misses.
|
|
230
|
+
'''
|
|
231
|
+
self._tasks.discard(task)
|
|
232
|
+
if task.cancelled():
|
|
233
|
+
return
|
|
234
|
+
exc = task.exception()
|
|
235
|
+
if (exc is not None):
|
|
236
|
+
_LOGGER.error(f"background task {task.get_name()} died: {exc!r}")
|
|
237
|
+
|
|
221
238
|
async def _background_probe(self) -> None:
|
|
222
239
|
while not self._closing:
|
|
223
240
|
await asyncio.sleep(1)
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
241
|
+
try:
|
|
242
|
+
self._probe_once()
|
|
243
|
+
self._probe_failures = 0
|
|
244
|
+
except Exception as e:
|
|
245
|
+
# A send raises on a closed or unreachable socket. Letting that
|
|
246
|
+
# escape ends the task, and this loop is the only thing that
|
|
247
|
+
# announces this client and drives discovery, so one transient
|
|
248
|
+
# failure would silence it permanently.
|
|
249
|
+
#
|
|
250
|
+
# A disconnected interface fails every tick, so only the first
|
|
251
|
+
# carries a traceback: a full one per second buries everything
|
|
252
|
+
# else in the log, and the repeats say nothing the first did not.
|
|
253
|
+
self._probe_failures += 1
|
|
254
|
+
if (self._probe_failures == 1):
|
|
255
|
+
_LOGGER.warning(f"probe failed: {traceback.format_exc()}")
|
|
256
|
+
else:
|
|
257
|
+
_LOGGER.debug(f"probe still failing ({self._probe_failures}): {e!r}")
|
|
258
|
+
|
|
259
|
+
def _probe_once(self) -> None:
|
|
260
|
+
'''One pass of the probe loop: announce if due, then discover if needed.'''
|
|
261
|
+
if (time.time() >= self._hello_due):
|
|
262
|
+
self.tx_hello()
|
|
263
|
+
tx_discover = False
|
|
264
|
+
if (not self.has_completed_devices()):
|
|
265
|
+
tx_discover = True
|
|
266
|
+
else:
|
|
267
|
+
for _, device in self.remotes.items():
|
|
268
|
+
device.check_online()
|
|
269
|
+
if not device.check_configuration_complete_timeout():
|
|
270
|
+
tx_discover = True
|
|
271
|
+
if tx_discover and ((time.time() - self._discover_timeout) >= 5):
|
|
272
|
+
self.tx_discover()
|
|
234
273
|
|
|
235
274
|
async def start_async(self) -> None:
|
|
236
275
|
'''Start the server that listens for mx_remote frames from other devices.'''
|
|
@@ -240,7 +279,7 @@ class Remote(DeviceRegistry, ConnectionCallbacks):
|
|
|
240
279
|
await self.conn.start_srv()
|
|
241
280
|
checker = asyncio.create_task(self._background_probe())
|
|
242
281
|
self._tasks.add(checker)
|
|
243
|
-
checker.add_done_callback(self.
|
|
282
|
+
checker.add_done_callback(self._on_task_done)
|
|
244
283
|
|
|
245
284
|
async def stop_async(self) -> None:
|
|
246
285
|
'''Stop the server and close all connections.'''
|
|
@@ -335,14 +374,32 @@ class Remote(DeviceRegistry, ConnectionCallbacks):
|
|
|
335
374
|
_LOGGER.debug("discovering devices")
|
|
336
375
|
return self.transmit(pkt.frame)
|
|
337
376
|
|
|
377
|
+
def _arm_hello(self) -> None:
|
|
378
|
+
'''Schedule the next announcement, mirroring the firmware's hello timer.
|
|
379
|
+
|
|
380
|
+
MatrixOS re-draws min 2.5s + random(0..2.5s) after each hello it sends,
|
|
381
|
+
so a mesh of devices does not synchronise into bursts. The interval is
|
|
382
|
+
re-drawn per send rather than fixed for the same reason.
|
|
383
|
+
'''
|
|
384
|
+
self._hello_due = time.time() + MXR_HELLO_INTERVAL_MIN \
|
|
385
|
+
+ (random.random() * MXR_HELLO_INTERVAL_RAND)
|
|
386
|
+
|
|
338
387
|
def tx_hello(self) -> int:
|
|
339
388
|
'''Transmit a hello frame to announce this device on the network.'''
|
|
340
389
|
pkt = constructFrameHello(self)
|
|
341
390
|
if (pkt is None):
|
|
342
391
|
return 0
|
|
343
392
|
_LOGGER.debug("sending hello")
|
|
344
|
-
|
|
345
|
-
|
|
393
|
+
sent = self.transmit(pkt.frame)
|
|
394
|
+
if (sent > 0):
|
|
395
|
+
# Only a hello that went out buys the next interval. A send that wrote
|
|
396
|
+
# nothing leaves _hello_due where it was, so the next probe tick tries
|
|
397
|
+
# again, which is what firmware does - it resets hello_timeout only in
|
|
398
|
+
# the success branch of mxr_transmit. Arming here on failure instead
|
|
399
|
+
# would silence a client for a full interval every time a write failed,
|
|
400
|
+
# and on an interface that never writes, permanently.
|
|
401
|
+
self._arm_hello()
|
|
402
|
+
return sent
|
|
346
403
|
|
|
347
404
|
def on_connection_made(self) -> None:
|
|
348
405
|
'''Callback invoked after ConnectionAsync has started the server.'''
|
|
@@ -374,10 +431,6 @@ class Remote(DeviceRegistry, ConnectionCallbacks):
|
|
|
374
431
|
timestamp = time.time()
|
|
375
432
|
self.process_frame(timestamp=timestamp, data=data, addr=addr)
|
|
376
433
|
|
|
377
|
-
if (self.conn is not None):
|
|
378
|
-
if (timestamp - self._last_hello >= 30):
|
|
379
|
-
self.tx_hello()
|
|
380
|
-
|
|
381
434
|
@override
|
|
382
435
|
def on_mxr_update(self, data:Any) -> None:
|
|
383
436
|
if isinstance(data, FrameHello):
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import aiohttp
|
|
2
2
|
from ..Interface import AudioEndpoint as AudioEndpoint, BayBase as BayBase, BayLinks as BayLinks, ConnectionCallbacks as ConnectionCallbacks, DeviceBase as DeviceBase, DeviceRegistry as DeviceRegistry, MxrCallbacks as MxrCallbacks, mxr_broadcast_address as mxr_broadcast_address
|
|
3
3
|
from ..Uid import MxrDeviceUid as MxrDeviceUid
|
|
4
|
-
from ..const import MX_BCAST_UDP_PORT as MX_BCAST_UDP_PORT, MX_MCAST_UDP_IP as MX_MCAST_UDP_IP, MX_MCAST_UDP_PORT as MX_MCAST_UDP_PORT, __version__ as __version__
|
|
4
|
+
from ..const import MXR_HELLO_INTERVAL_MIN as MXR_HELLO_INTERVAL_MIN, MXR_HELLO_INTERVAL_RAND as MXR_HELLO_INTERVAL_RAND, MX_BCAST_UDP_PORT as MX_BCAST_UDP_PORT, MX_MCAST_UDP_IP as MX_MCAST_UDP_IP, MX_MCAST_UDP_PORT as MX_MCAST_UDP_PORT, __version__ as __version__
|
|
5
5
|
from ..proto.Constants import MXR_PROTOCOL_VERSION as MXR_PROTOCOL_VERSION
|
|
6
6
|
from ..proto.Factory import process_mxr_frame as process_mxr_frame
|
|
7
7
|
from ..proto.FrameDiscover import constructFrameDiscover as constructFrameDiscover
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Enter where the runtime enters.
|
|
2
|
+
|
|
3
|
+
on_datagram_received is what ConnectionAsync calls. process_frame is one level
|
|
4
|
+
below it and is what a test reaches for, because it takes the arguments a test
|
|
5
|
+
already has - so a layer that only the runtime touches goes uncovered while the
|
|
6
|
+
suite looks like it exercises the receive path.
|
|
7
|
+
|
|
8
|
+
Here that layer re-announces this client's hello. It has one other caller, at
|
|
9
|
+
startup, so if it stopped working the client would announce once and then go
|
|
10
|
+
silent to every peer that started later.
|
|
11
|
+
"""
|
|
12
|
+
import os
|
|
13
|
+
import struct
|
|
14
|
+
import sys
|
|
15
|
+
import time
|
|
16
|
+
|
|
17
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
18
|
+
import logging
|
|
19
|
+
logging.disable(logging.CRITICAL)
|
|
20
|
+
|
|
21
|
+
import mx_remote
|
|
22
|
+
from mx_remote.Uid import MxrDeviceUid
|
|
23
|
+
from mx_remote.proto.Factory import create_mxr_frame
|
|
24
|
+
|
|
25
|
+
UID = bytes(range(1, 17))
|
|
26
|
+
ADDR = ('192.0.2.9', 8812)
|
|
27
|
+
|
|
28
|
+
class RecordingConn:
|
|
29
|
+
"""Stands in for ConnectionAsync so transmits can be counted."""
|
|
30
|
+
def __init__(self):
|
|
31
|
+
self.sent = []
|
|
32
|
+
def transmit(self, data):
|
|
33
|
+
self.sent.append(data)
|
|
34
|
+
return len(data)
|
|
35
|
+
|
|
36
|
+
def nm(s, sz=16):
|
|
37
|
+
b = s.encode('ascii')[:sz]
|
|
38
|
+
return b + bytes(sz - len(b))
|
|
39
|
+
|
|
40
|
+
HELLO = (struct.pack('<H', 0x28) + nm('MX-1') + nm('P8SN12345678') + nm('4.7.9')
|
|
41
|
+
+ struct.pack('<I', 1 << 5))
|
|
42
|
+
|
|
43
|
+
mx = mx_remote.Remote(open_connection=False)
|
|
44
|
+
mx._uid = bytes(range(100, 116))
|
|
45
|
+
conn = RecordingConn()
|
|
46
|
+
mx.conn = conn
|
|
47
|
+
|
|
48
|
+
# a datagram arriving must be decoded, not merely counted
|
|
49
|
+
mx.on_datagram_received(create_mxr_frame(UID, 0x00, HELLO), ADDR)
|
|
50
|
+
assert mx.get_by_uid(MxrDeviceUid(UID)) is not None, 'the datagram was not processed'
|
|
51
|
+
print('entry : a datagram reaching on_datagram_received is decoded')
|
|
52
|
+
|
|
53
|
+
# Hello timing is not asserted here. It belongs to whatever drives the
|
|
54
|
+
# announcement, and pinning it to a datagram arriving would encode the trigger
|
|
55
|
+
# rather than the behaviour - an assertion that no datagram sends a hello
|
|
56
|
+
# passes trivially once nothing does.
|
|
57
|
+
|
|
58
|
+
print()
|
|
59
|
+
print('ALL OK')
|
|
@@ -32,26 +32,54 @@ import mx_remote.proto as proto_pkg # noqa: E402
|
|
|
32
32
|
from mx_remote.proto.FrameBase import FrameBase # noqa: E402
|
|
33
33
|
|
|
34
34
|
BASELINE = {
|
|
35
|
+
'FrameAmpDolbySettings.FrameAmpDolbySettings',
|
|
35
36
|
'FrameAmpZoneSettings.FrameAmpZoneSettings',
|
|
36
37
|
'FrameAudioClip.FrameAudioClip',
|
|
37
38
|
'FrameAudioSetRoute.FrameAudioSetRoute',
|
|
38
39
|
'FrameBayConfig.FrameBayConfig',
|
|
40
|
+
'FrameBayConfigSecondary.FrameBayConfigSecondary',
|
|
41
|
+
'FrameBayHide.FrameBayHide',
|
|
39
42
|
'FrameBayStatus.FrameBayStatus',
|
|
43
|
+
'FrameConnectStatus.FrameConnectStatus',
|
|
44
|
+
'FrameDebug.FrameDebug',
|
|
45
|
+
'FrameFilterStatus.FrameFilterStatus',
|
|
40
46
|
'FrameFirmwareVersion.FrameFirmwareVersion',
|
|
41
47
|
'FrameHello.FrameHello',
|
|
42
48
|
'FrameLinks.FrameLinks',
|
|
49
|
+
'FrameMeshOperation.FrameMeshOperation',
|
|
50
|
+
'FrameMirrorStatus.FrameMirrorStatus',
|
|
51
|
+
'FrameNetworkStatus.FrameNetworkStatus',
|
|
52
|
+
'FramePowerChange.FramePowerChange',
|
|
43
53
|
'FrameRCAction.FrameRCAction',
|
|
44
54
|
'FrameRCKey.FrameRCKey',
|
|
55
|
+
'FrameRCSettings.FrameRCSettings',
|
|
45
56
|
'FrameRoutingChange.FrameRoutingChange',
|
|
57
|
+
'FrameSignalStatus.FrameSignalStatus',
|
|
46
58
|
'FrameSignalStatusNew.FrameSignalStatusNew',
|
|
59
|
+
'FrameSysTemperature.FrameSysTemperature',
|
|
60
|
+
'FrameSystemStatus.FrameSystemStatus',
|
|
47
61
|
'FrameTXRCAction.FrameTXRCAction',
|
|
48
62
|
'FrameTXRCKey.FrameTXRCKey',
|
|
63
|
+
'FrameTopology.FrameTopology',
|
|
64
|
+
'FrameV2IPAudio.FrameV2IPAudio',
|
|
65
|
+
'FrameV2IPAudio.FrameV2IPAudioChangeSource',
|
|
66
|
+
'FrameV2IPAudio.FrameV2IPAudioConfig',
|
|
67
|
+
'FrameV2IPAudio.FrameV2IPAudioLinks',
|
|
68
|
+
'FrameV2IPBayMapping.FrameV2IPBayMapping',
|
|
49
69
|
'FrameV2IPDetectBays.FrameV2IPDetectBays',
|
|
50
70
|
'FrameV2IPDeviceConfiguration.FrameV2IPDeviceConfiguration',
|
|
71
|
+
'FrameV2IPManualSourceSwitch.FrameV2IPManualSourceSwitch',
|
|
51
72
|
'FrameV2IPMultiviewer.FrameV2IPMultiviewer',
|
|
73
|
+
'FrameV2IPPowerSave.FrameV2IPPowerSave',
|
|
74
|
+
'FrameV2IPSourceSwitch.FrameV2IPSourceSwitch',
|
|
52
75
|
'FrameV2IPSources.FrameV2IPSources',
|
|
53
76
|
'FrameV2IPStats.FrameV2IPStats',
|
|
77
|
+
'FrameV2IPTiling.FrameV2IPTiling',
|
|
54
78
|
'FrameV2IPVideoWall.FrameV2IPVideoWall',
|
|
79
|
+
'FrameVolume.FrameVolume',
|
|
80
|
+
'FrameVolumeDown.FrameVolumeDown',
|
|
81
|
+
'FrameVolumeSet.FrameVolumeSet',
|
|
82
|
+
'FrameVolumeUp.FrameVolumeUp',
|
|
55
83
|
}
|
|
56
84
|
|
|
57
85
|
def main() -> int:
|