mx-remote 5.9.0__tar.gz → 5.9.1__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.9.0 → mx_remote-5.9.1}/PKG-INFO +1 -1
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/Interface.py +2 -2
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/Interface.pyi +1 -1
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/const.py +9 -1
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/const.pyi +1 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameLinks.py +1 -5
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/remote/Device.py +50 -23
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/remote/Device.pyi +2 -1
- mx_remote-5.9.1/tests/complete.py +218 -0
- mx_remote-5.9.0/tests/complete.py +0 -105
- {mx_remote-5.9.0 → mx_remote-5.9.1}/.gitignore +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/AGENTS.md +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/CLAUDE.md +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/LICENSE +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/README.md +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/docs/README.md +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/docs/_static/brand.css +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/docs/_static/favicon.svg +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/docs/_static/logo-dark.svg +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/docs/_static/logo.svg +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/docs/api.md +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/docs/audio.md +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/docs/callbacks.md +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/docs/conf.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/docs/configuration.md +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/docs/devices-and-bays.md +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/docs/diagnostics.md +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/docs/index.md +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/docs/multiviewer.md +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/docs/oneip.md +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/docs/requirements.txt +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/docs/routing.md +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/hatch_build.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/Uid.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/Uid.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/__init__.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/__init__.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/compat.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/compat.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/main.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/main.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/BayConfig.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/BayConfig.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/Constants.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/Constants.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/Data.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/Data.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/Factory.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/Factory.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameAmpDolbySettings.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameAmpDolbySettings.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameAmpZoneSettings.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameAmpZoneSettings.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameAudioClip.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameAudioClip.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameAudioSetRoute.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameAudioSetRoute.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameBase.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameBase.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameBayConfig.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameBayConfig.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameBayConfigSecondary.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameBayConfigSecondary.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameBayHide.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameBayHide.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameBayStatus.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameBayStatus.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameConnectStatus.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameConnectStatus.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameDebug.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameDebug.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameDiscover.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameDiscover.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameEDID.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameEDID.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameEDIDProfile.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameEDIDProfile.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameFactoryReset.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameFactoryReset.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameFilterStatus.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameFilterStatus.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameFirmwareVersion.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameFirmwareVersion.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameHeader.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameHeader.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameHello.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameHello.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameLinks.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameMeshOperation.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameMeshOperation.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameMirrorStatus.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameMirrorStatus.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameMonitoringPulse.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameMonitoringPulse.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameNetworkStatus.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameNetworkStatus.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FramePowerChange.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FramePowerChange.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameRCAction.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameRCAction.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameRCIr.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameRCIr.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameRCKey.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameRCKey.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameRCSettings.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameRCSettings.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameReboot.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameReboot.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameRoutingChange.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameRoutingChange.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameSetInstaller.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameSetInstaller.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameSetName.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameSetName.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameSetRoute.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameSetRoute.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameSetupStatus.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameSetupStatus.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameSignalStatus.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameSignalStatus.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameSignalStatusNew.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameSignalStatusNew.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameSysTemperature.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameSysTemperature.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameSystemStatus.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameSystemStatus.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameTXRCAction.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameTXRCAction.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameTXRCKey.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameTXRCKey.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameTopology.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameTopology.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameTxIR.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameTxIR.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameUpgradeFPGA.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameUpgradeFPGA.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPAudio.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPAudio.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPBayMapping.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPBayMapping.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPDetectBays.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPDetectBays.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPDeviceConfiguration.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPDeviceConfiguration.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPLink.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPLink.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPManualSourceSwitch.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPManualSourceSwitch.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPMultiviewer.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPMultiviewer.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPPowerSave.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPPowerSave.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPSetMaster.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPSetMaster.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPSourceSwitch.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPSourceSwitch.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPSources.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPSources.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPStats.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPStats.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPTiling.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPTiling.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPVideoWall.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameV2IPVideoWall.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameVolume.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameVolume.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameVolumeDown.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameVolumeDown.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameVolumeSet.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameVolumeSet.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameVolumeUp.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/FrameVolumeUp.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/LinkConfig.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/LinkConfig.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/Multiviewer.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/Multiviewer.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/Svd.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/Svd.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/V2IPConfig.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/V2IPConfig.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/V2IPStats.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/V2IPStats.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/__init__.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/__init__.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/proto/svd.csv +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/py.typed +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/remote/Bay.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/remote/Bay.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/remote/ConnectionAsync.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/remote/ConnectionAsync.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/remote/Link.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/remote/Link.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/remote/Remote.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/remote/Remote.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/remote/State.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/remote/State.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/remote/V2IP.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/mx_remote/remote/V2IP.pyi +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/pyproject.toml +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/audio_ir.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/capture.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/e2e.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/entry.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/handlers.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/handlers_cover.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/hello.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/idempotent.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/lengths.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/merge.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/newops.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/offsets.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/proto_stamp.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/protogate.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/rcsettings.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/roundtrip.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/run.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/smoke.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/sources.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/stats.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/structs.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/txresult.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/unknowns.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/v2ipscaling.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/v2ipsubject.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/vdetails.py +0 -0
- {mx_remote-5.9.0 → mx_remote-5.9.1}/tests/wirefix.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: mx_remote
|
|
3
|
-
Version: 5.9.
|
|
3
|
+
Version: 5.9.1
|
|
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://opdenkamp.github.io/mx-remote/
|
|
@@ -2275,8 +2275,8 @@ class DeviceBase(ABC):
|
|
|
2275
2275
|
'''merge one frame of this device's V2IP source list into the cached list'''
|
|
2276
2276
|
|
|
2277
2277
|
@abstractmethod
|
|
2278
|
-
def
|
|
2279
|
-
'''internal callback:
|
|
2278
|
+
def note_link_config(self) -> None:
|
|
2279
|
+
'''internal callback: a page of the link configuration was received'''
|
|
2280
2280
|
|
|
2281
2281
|
@abstractmethod
|
|
2282
2282
|
def note_bay_config(self) -> None:
|
|
@@ -916,7 +916,7 @@ class DeviceBase(ABC):
|
|
|
916
916
|
@abstractmethod
|
|
917
917
|
def merge_v2ip_sources(self, first: int, total: int, page: V2IPStreamSourcesList) -> None: ...
|
|
918
918
|
@abstractmethod
|
|
919
|
-
def
|
|
919
|
+
def note_link_config(self) -> None: ...
|
|
920
920
|
@abstractmethod
|
|
921
921
|
def note_bay_config(self) -> None: ...
|
|
922
922
|
@abstractmethod
|
|
@@ -14,7 +14,7 @@ Default communication modes:
|
|
|
14
14
|
"""
|
|
15
15
|
import os
|
|
16
16
|
|
|
17
|
-
VERSION = '5.9.
|
|
17
|
+
VERSION = '5.9.1'
|
|
18
18
|
__version__ = VERSION
|
|
19
19
|
|
|
20
20
|
MX_BCAST_UDP_PORT = 8811
|
|
@@ -37,6 +37,14 @@ re-drawn after every hello that is sent, so devices announcing on the same mesh
|
|
|
37
37
|
not converge into synchronised bursts.
|
|
38
38
|
"""
|
|
39
39
|
|
|
40
|
+
MXR_CONFIG_TIMEOUT = 15
|
|
41
|
+
"""How long a device is given to finish describing itself, in seconds.
|
|
42
|
+
|
|
43
|
+
Past this a device is reported as fully described without its link
|
|
44
|
+
configuration, and keeps being asked for the rest. It never stands in for the
|
|
45
|
+
bay configuration - see ``Device.has_bays``.
|
|
46
|
+
"""
|
|
47
|
+
|
|
40
48
|
V2IP_UDP_PORT_VIDEO = 50020
|
|
41
49
|
"""Default destination UDP port for a V2IP video stream (firmware ``V2IP_UDP_PORT_VIDEO``)."""
|
|
42
50
|
|
|
@@ -44,11 +44,7 @@ class FrameLinks(FrameBase):
|
|
|
44
44
|
return
|
|
45
45
|
for link in self.links:
|
|
46
46
|
link.process()
|
|
47
|
-
|
|
48
|
-
# this client has not seen is dropped rather than held, which is
|
|
49
|
-
# what a device does with the same frame, and the device re-sends.
|
|
50
|
-
if (link.remote_bay is not None):
|
|
51
|
-
dev.note_link_record(link.remote_port)
|
|
47
|
+
dev.note_link_config()
|
|
52
48
|
|
|
53
49
|
def __str__(self) -> str:
|
|
54
50
|
return f"{self.remote_device} links config page: {len(self.links)} links"
|
|
@@ -51,6 +51,7 @@ from ..proto.Multiviewer import (
|
|
|
51
51
|
MultiviewerITCMode,
|
|
52
52
|
MultiviewerHDCPMode,
|
|
53
53
|
)
|
|
54
|
+
from ..const import MXR_CONFIG_TIMEOUT
|
|
54
55
|
from ..Uid import MxrDeviceUid
|
|
55
56
|
from typing import Any, Callable
|
|
56
57
|
from ..compat import override
|
|
@@ -76,7 +77,7 @@ class Device(DeviceBase):
|
|
|
76
77
|
self._registry = registry
|
|
77
78
|
self._hello = hello
|
|
78
79
|
self._temperatures:SystemTemperature = SystemTemperature([])
|
|
79
|
-
self.
|
|
80
|
+
self._link_config_received = False
|
|
80
81
|
self._bay_config_received = False
|
|
81
82
|
self._last_ping = datetime.now()
|
|
82
83
|
self._online = True
|
|
@@ -200,17 +201,11 @@ class Device(DeviceBase):
|
|
|
200
201
|
self.call_callbacks()
|
|
201
202
|
|
|
202
203
|
@override
|
|
203
|
-
def
|
|
204
|
-
'''Note that
|
|
205
|
-
|
|
206
|
-
A set of ports rather than a count, because a device re-sends its
|
|
207
|
-
configuration: counting records would let one page repeated stand in for
|
|
208
|
-
the pages behind it, completing a list with bays in it that have never
|
|
209
|
-
reported a link.
|
|
210
|
-
'''
|
|
211
|
-
if (port in self._link_records):
|
|
204
|
+
def note_link_config(self) -> None:
|
|
205
|
+
'''Note that the device has reported its link configuration.'''
|
|
206
|
+
if self._link_config_received:
|
|
212
207
|
return
|
|
213
|
-
self.
|
|
208
|
+
self._link_config_received = True
|
|
214
209
|
self._check_config_complete()
|
|
215
210
|
|
|
216
211
|
@override
|
|
@@ -346,14 +341,21 @@ class Device(DeviceBase):
|
|
|
346
341
|
return not self.need_link_config
|
|
347
342
|
|
|
348
343
|
def check_configuration_complete_timeout(self) -> bool:
|
|
344
|
+
'''Whether this device's configuration is still on course.
|
|
345
|
+
|
|
346
|
+
Called on every pass of the probe loop, which is what gives the window
|
|
347
|
+
in need_link_config a moment to expire in: a device that stops waiting
|
|
348
|
+
for its links is announced from here, since no frame need arrive for
|
|
349
|
+
that to become true.
|
|
350
|
+
|
|
351
|
+
False asks for another discover - something the device owes has not
|
|
352
|
+
arrived and waiting has not produced it.
|
|
353
|
+
'''
|
|
354
|
+
self._check_config_complete()
|
|
349
355
|
if self.configuration_complete:
|
|
350
356
|
# info received
|
|
351
357
|
return True
|
|
352
|
-
|
|
353
|
-
# configuration incomplete after 15 seconds
|
|
354
|
-
return False
|
|
355
|
-
# waiting for the timeout to pass
|
|
356
|
-
return True
|
|
358
|
+
return ((time.time() - self._hello_received) <= MXR_CONFIG_TIMEOUT)
|
|
357
359
|
|
|
358
360
|
@property
|
|
359
361
|
def protocol(self) -> int:
|
|
@@ -486,6 +488,13 @@ class Device(DeviceBase):
|
|
|
486
488
|
For a V2IP device this is half the answer on its own: its bays include
|
|
487
489
|
ones that live on other devices, which arrive on a frame of their own
|
|
488
490
|
that configuration_complete requires separately.
|
|
491
|
+
|
|
492
|
+
Unlike the link configuration, waiting for this never times out. A bay
|
|
493
|
+
is what a caller names things after, and a name it assigns before this
|
|
494
|
+
frame arrives is one it assigned to a placeholder - persisted, reused
|
|
495
|
+
from then on, and undone only by editing whatever holds it. So a device
|
|
496
|
+
that has not sent its bays is reported as undescribed for as long as
|
|
497
|
+
that lasts.
|
|
489
498
|
'''
|
|
490
499
|
return self._bay_config_received
|
|
491
500
|
|
|
@@ -630,15 +639,33 @@ class Device(DeviceBase):
|
|
|
630
639
|
|
|
631
640
|
@property
|
|
632
641
|
def need_link_config(self) -> bool:
|
|
633
|
-
'''Whether the device
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
642
|
+
'''Whether the device has yet to report its link configuration.
|
|
643
|
+
|
|
644
|
+
That it reported at all is the whole of what can be established, and no
|
|
645
|
+
count of bays stands in for it. Two reasons, either enough on its own:
|
|
646
|
+
|
|
647
|
+
A link record describes one of the sender's own ports. Most of a V2IP
|
|
648
|
+
device's bays are proxies for streams that live on other devices, and
|
|
649
|
+
those own no record - a 14-bay transceiver reports two, for its local
|
|
650
|
+
input and its local output.
|
|
651
|
+
|
|
652
|
+
A list longer than one payload is cut to what fits rather than continued.
|
|
653
|
+
An 18-bay amplifier reports 17 records in one page and sends no second
|
|
654
|
+
one, so even a device whose bays are all its own never accounts for the
|
|
655
|
+
last of them.
|
|
656
|
+
|
|
657
|
+
Waiting for it ends after MXR_CONFIG_TIMEOUT, and the device is still
|
|
658
|
+
asked for the rest. Nothing a caller has already built needs revising
|
|
659
|
+
when the records do arrive - a link is reported per bay and read on
|
|
660
|
+
access, so an unreported one reads as no link, which is what a link
|
|
661
|
+
coming up later looks like anyway. A withheld frame therefore costs a
|
|
662
|
+
consumer that window rather than leaving the device permanently
|
|
663
|
+
undescribed, which is a state nothing on the wire distinguishes from a
|
|
664
|
+
device that has no bays to offer.
|
|
639
665
|
'''
|
|
640
666
|
if (self.is_amp or self.is_video_matrix or self.is_audio_matrix or self.is_v2ip):
|
|
641
|
-
return
|
|
667
|
+
return (not self._link_config_received) \
|
|
668
|
+
and ((time.time() - self._hello_received) <= MXR_CONFIG_TIMEOUT)
|
|
642
669
|
return False
|
|
643
670
|
|
|
644
671
|
@override
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from ..Interface import AmpDolbySettings as AmpDolbySettings, AudioChangeSource as AudioChangeSource, AudioEndpoint as AudioEndpoint, AudioEndpoints as AudioEndpoints, AudioLinks as AudioLinks, BayBase as BayBase, DeviceBase as DeviceBase, DeviceRegistry as DeviceRegistry, DeviceStatus as DeviceStatus, DeviceV2IPDetails as DeviceV2IPDetails, DeviceV2IPScalingSettings as DeviceV2IPScalingSettings, DeviceV2IPSink as DeviceV2IPSink, Multiviewer as Multiviewer, MxrCallbacks as MxrCallbacks, SystemTemperature as SystemTemperature, V2IPOutputMode as V2IPOutputMode, V2IPScalingSettings as V2IPScalingSettings, V2IPStreamSources as V2IPStreamSources, V2IPStreamSourcesList as V2IPStreamSourcesList
|
|
2
2
|
from ..Uid import MxrDeviceUid as MxrDeviceUid
|
|
3
3
|
from ..compat import override as override
|
|
4
|
+
from ..const import MXR_CONFIG_TIMEOUT as MXR_CONFIG_TIMEOUT
|
|
4
5
|
from ..proto.BayConfig import BayConfig as BayConfig
|
|
5
6
|
from ..proto.Constants import DeviceFeature as DeviceFeature, MXR_SCALING_FLAG_AUTO_SCALING as MXR_SCALING_FLAG_AUTO_SCALING, MXR_SCALING_FLAG_MODE_VALID as MXR_SCALING_FLAG_MODE_VALID, MXR_SCALING_FLAG_OPTIONS_VALID as MXR_SCALING_FLAG_OPTIONS_VALID, MxrSignalType as MxrSignalType, V2IPFpgaFeature as V2IPFpgaFeature
|
|
6
7
|
from ..proto.FrameBase import FrameBase as FrameBase
|
|
@@ -49,7 +50,7 @@ class Device(DeviceBase):
|
|
|
49
50
|
def status_message(self) -> str: ...
|
|
50
51
|
def check_online(self) -> None: ...
|
|
51
52
|
@override
|
|
52
|
-
def
|
|
53
|
+
def note_link_config(self) -> None: ...
|
|
53
54
|
@override
|
|
54
55
|
def note_bay_config(self) -> None: ...
|
|
55
56
|
@property
|
|
@@ -0,0 +1,218 @@
|
|
|
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
|
+
'''When a device counts as fully described, and what each half of that rests on.
|
|
8
|
+
|
|
9
|
+
Both halves are paged lists with nothing on the wire marking the last page: no
|
|
10
|
+
count, no index, no terminating frame. So neither can be tested by comparing
|
|
11
|
+
what arrived against what is still to come, and each rests on the list having
|
|
12
|
+
been sent at all.
|
|
13
|
+
|
|
14
|
+
The bays additionally need the primary list specifically, which every unit sends
|
|
15
|
+
whatever else it sends; the secondary one does not stand in for it.
|
|
16
|
+
|
|
17
|
+
Only the links stop being waited for. What a device withholds is reported the
|
|
18
|
+
same way as what it does not have, so a wait with no end hides a fault rather
|
|
19
|
+
than reporting one - but a bay is what a caller names things after, and a name
|
|
20
|
+
assigned to a placeholder outlives the frame that would have corrected it. So
|
|
21
|
+
the window below releases the links, and is checked against the two halves it
|
|
22
|
+
must not release.
|
|
23
|
+
|
|
24
|
+
The two device shapes below are the geometry of real units, and each shows why a
|
|
25
|
+
count of bays is not the number of link records to expect. Both are built here
|
|
26
|
+
from frames rather than from mock bays, because the bay a record names is looked
|
|
27
|
+
up in the cache the bay frames fill: a test that sets the bays directly never
|
|
28
|
+
exercises the path that decides whether a record counts.
|
|
29
|
+
'''
|
|
30
|
+
|
|
31
|
+
import logging, os, struct, sys, time
|
|
32
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
33
|
+
logging.disable(logging.CRITICAL)
|
|
34
|
+
import mx_remote
|
|
35
|
+
from mx_remote.const import MXR_CONFIG_TIMEOUT
|
|
36
|
+
from mx_remote.Uid import MxrDeviceUid
|
|
37
|
+
from mx_remote.proto.Factory import create_mxr_frame
|
|
38
|
+
|
|
39
|
+
class Watching(mx_remote.MxrCallbacks):
|
|
40
|
+
def __init__(self, sink):
|
|
41
|
+
self.sink = sink
|
|
42
|
+
def on_device_config_complete(self, dev):
|
|
43
|
+
self.sink.append(dev)
|
|
44
|
+
|
|
45
|
+
completed = []
|
|
46
|
+
ADDR = ('192.0.2.9', 8812)
|
|
47
|
+
mx = mx_remote.Remote(open_connection=False, callbacks=Watching(completed))
|
|
48
|
+
mx._uid = bytes(range(100, 116))
|
|
49
|
+
|
|
50
|
+
# device features
|
|
51
|
+
VIDEO_ROUTING, AUDIO_ROUTING, VOLUME_CONTROL = (1 << 5), (1 << 6), (1 << 7)
|
|
52
|
+
V2IP_SOURCE, V2IP_SINK = (1 << 3), (1 << 4)
|
|
53
|
+
# bay features
|
|
54
|
+
HDMI_OUT, HDMI_IN = (1 << 0), (1 << 1)
|
|
55
|
+
V2IP_SOURCE_REMOTE, V2IP_SINK_REMOTE = (1 << 13), (1 << 14)
|
|
56
|
+
|
|
57
|
+
def nm(s, sz=16):
|
|
58
|
+
b = s.encode('ascii')[:sz]
|
|
59
|
+
return b + bytes(sz - len(b))
|
|
60
|
+
def rx(uid, op, pl=b''):
|
|
61
|
+
mx.process_frame(time.time(), create_mxr_frame(uid, op, pl), ADDR)
|
|
62
|
+
def hello(uid, name, serial, features):
|
|
63
|
+
rx(uid, 0x00, struct.pack('<H', 0x28) + nm(name) + nm(serial) + nm('4.7.9')
|
|
64
|
+
+ struct.pack('<I', features))
|
|
65
|
+
return mx.get_by_uid(MxrDeviceUid(uid))
|
|
66
|
+
def bay(port, mode, num, name, features=0):
|
|
67
|
+
bayfeat = features | (HDMI_IN if mode == 0 else HDMI_OUT)
|
|
68
|
+
return bytes([port, mode, num, 0, 0]) + nm(name) + nm(name) + nm('1080p') \
|
|
69
|
+
+ struct.pack('<I', 0) + struct.pack('<I', bayfeat)
|
|
70
|
+
def link(port, serial, baynm):
|
|
71
|
+
return bytes([port, 0]) + nm(serial) + nm(baynm) + struct.pack('<I', 1)
|
|
72
|
+
|
|
73
|
+
MATRIX = bytes(range(1, 17))
|
|
74
|
+
dev = hello(MATRIX, 'MX-1', 'P8SN12345678', VIDEO_ROUTING | AUDIO_ROUTING)
|
|
75
|
+
assert dev is not None and dev.is_video_matrix
|
|
76
|
+
assert not dev.configuration_complete, 'a hello describes no bays'
|
|
77
|
+
print('complete : a hello on its own is not a configuration')
|
|
78
|
+
|
|
79
|
+
# ---- the primary bay list, and only that one
|
|
80
|
+
# The secondary list registers the same bays, so after it the device knows every
|
|
81
|
+
# bay it will ever hear about and still has not heard the list that says so.
|
|
82
|
+
rx(MATRIX, 0x23, bay(0, 0, 0, 'In 1') + bay(1, 0, 1, 'In 2') + bay(2, 1, 0, 'Out 1'))
|
|
83
|
+
assert len(dev.bays) == 3, dev.bays
|
|
84
|
+
assert not dev.has_bays, 'the secondary list does not stand in for the primary one'
|
|
85
|
+
assert not dev.configuration_complete
|
|
86
|
+
|
|
87
|
+
rx(MATRIX, 0x02, bay(0, 0, 0, 'In 1') + bay(1, 0, 1, 'In 2'))
|
|
88
|
+
assert dev.has_bays, 'the primary list is what completes the bays'
|
|
89
|
+
assert len(dev.bays) == 3, 'a page is merged into the cache, never replaces it'
|
|
90
|
+
print('complete : the bays need the primary list, whatever else arrived')
|
|
91
|
+
|
|
92
|
+
# ---- the links, reported
|
|
93
|
+
assert dev.need_link_config, 'no link configuration has arrived'
|
|
94
|
+
assert not dev.configuration_complete
|
|
95
|
+
assert not completed, 'nothing may be announced complete before the links are in'
|
|
96
|
+
|
|
97
|
+
rx(MATRIX, 0x03, link(0, 'P8SN00000001', 'Out 1'))
|
|
98
|
+
assert not dev.need_link_config, 'the device has reported its links'
|
|
99
|
+
assert dev.configuration_complete
|
|
100
|
+
print('complete : the links need the list, and the list to have been sent')
|
|
101
|
+
|
|
102
|
+
# ---- announced once, at that moment
|
|
103
|
+
assert completed == [dev], completed
|
|
104
|
+
rx(MATRIX, 0x02, bay(0, 0, 0, 'In 1') + bay(1, 0, 1, 'In 2'))
|
|
105
|
+
rx(MATRIX, 0x03, link(0, 'P8SN00000001', 'Out 1'))
|
|
106
|
+
assert completed == [dev], 'a re-sent configuration must not announce completion again'
|
|
107
|
+
print('complete : announced once, when the last of it arrived')
|
|
108
|
+
|
|
109
|
+
# ---- a V2IP transceiver: 14 bays, two of them its own
|
|
110
|
+
# Its remaining bays are proxies for streams on other devices. A link record
|
|
111
|
+
# describes one of the sender's own ports, so those twelve own no record and a
|
|
112
|
+
# count of bays is one no page will ever reach.
|
|
113
|
+
ONEIP = bytes(range(17, 33))
|
|
114
|
+
tz = hello(ONEIP, 'OneIP', 'P9SN66662814', V2IP_SOURCE | V2IP_SINK | VIDEO_ROUTING)
|
|
115
|
+
assert tz is not None and tz.is_v2ip
|
|
116
|
+
bays = bay(0, 0, 0, 'Input 1')
|
|
117
|
+
for port in range(1, 13):
|
|
118
|
+
bays += bay(port, 0, port, f'Input {port + 1}', V2IP_SOURCE_REMOTE)
|
|
119
|
+
bays += bay(16, 1, 0, 'Output 1')
|
|
120
|
+
rx(ONEIP, 0x02, bays)
|
|
121
|
+
assert len(tz.bays) == 14, tz.bays
|
|
122
|
+
assert tz.nb_inputs + tz.nb_outputs == 14, 'every bay is an input or an output here'
|
|
123
|
+
|
|
124
|
+
# Its V2IP source list, which completion needs for a V2IP device in its own right.
|
|
125
|
+
rx(ONEIP, 0x26, bytes(40 * 13))
|
|
126
|
+
assert tz.v2ip_sources is not None and len(tz.v2ip_sources) == 13
|
|
127
|
+
|
|
128
|
+
assert tz.need_link_config and not tz.configuration_complete
|
|
129
|
+
rx(ONEIP, 0x03, link(0, 'P9SN66662903', 'Output 1') + link(16, 'P9SN66662903', 'Input 1'))
|
|
130
|
+
assert not tz.need_link_config, 'two records are this device\'s whole list'
|
|
131
|
+
assert tz.configuration_complete, 'a bay that owns no link record must not gate completion'
|
|
132
|
+
assert completed == [dev, tz], completed
|
|
133
|
+
print('complete : a V2IP device reports links for its own two bays, not its fourteen')
|
|
134
|
+
|
|
135
|
+
# ---- an amplifier: 18 bays, 17 records, no second page
|
|
136
|
+
# The list is cut to what fits one payload rather than continued, so the last
|
|
137
|
+
# bay is never accounted for on any device.
|
|
138
|
+
AMP = bytes(range(33, 49))
|
|
139
|
+
amp = hello(AMP, 'PROAMP8', 'P9SN66667000', AUDIO_ROUTING | VOLUME_CONTROL)
|
|
140
|
+
assert amp is not None and amp.is_amp
|
|
141
|
+
bays = b''.join(bay(port, 0, port, f'Input {port + 1}') for port in range(9)) \
|
|
142
|
+
+ b''.join(bay(9 + num, 1, num, f'Output {num + 1}') for num in range(9))
|
|
143
|
+
rx(AMP, 0x02, bays)
|
|
144
|
+
assert len(amp.bays) == 18, amp.bays
|
|
145
|
+
|
|
146
|
+
records = b''.join(link(port, 'P9SN66662814', 'Output 1') for port in range(17))
|
|
147
|
+
assert len(records) == 646, 'the page a ProAmp8 sends, to the byte'
|
|
148
|
+
rx(AMP, 0x03, records)
|
|
149
|
+
assert not amp.need_link_config, 'the page the device sent is the list it has'
|
|
150
|
+
assert amp.configuration_complete, 'a bay left out of a cut list must not gate completion'
|
|
151
|
+
assert completed == [dev, tz, amp], completed
|
|
152
|
+
print('complete : a cut list is the whole of what a device reports')
|
|
153
|
+
|
|
154
|
+
# ---- waiting for the links ends, and for nothing else
|
|
155
|
+
# Wind the clock back rather than sleeping, and drive it through the call the
|
|
156
|
+
# probe loop makes: no frame arrives when a window expires, so that call is the
|
|
157
|
+
# only thing that can announce it.
|
|
158
|
+
LATE = bytes(range(49, 65))
|
|
159
|
+
late = hello(LATE, 'MX-2', 'P8SN22222222', VIDEO_ROUTING | AUDIO_ROUTING)
|
|
160
|
+
assert late.need_link_config, 'inside the window the links are still awaited'
|
|
161
|
+
late._hello_received -= (MXR_CONFIG_TIMEOUT + 1)
|
|
162
|
+
assert not late.need_link_config, 'past it they are not'
|
|
163
|
+
|
|
164
|
+
rx(LATE, 0x23, bay(0, 0, 0, 'In 1'))
|
|
165
|
+
assert not late.has_bays and not late.configuration_complete, \
|
|
166
|
+
'the window must not stand in for the primary bay list'
|
|
167
|
+
assert not late.check_configuration_complete_timeout(), 'a device still owing bays is still asked'
|
|
168
|
+
assert late not in completed, 'and nothing may name a bay it has not described'
|
|
169
|
+
|
|
170
|
+
rx(LATE, 0x02, bay(0, 0, 0, 'In 1'))
|
|
171
|
+
assert late.configuration_complete, 'the bays arrived, and the links are no longer awaited'
|
|
172
|
+
assert late.check_configuration_complete_timeout()
|
|
173
|
+
assert completed == [dev, tz, amp, late], completed
|
|
174
|
+
print('complete : the links stop being waited for, the bays never do')
|
|
175
|
+
|
|
176
|
+
# A V2IP device owes its source list in its own right, which the window does not
|
|
177
|
+
# release either.
|
|
178
|
+
LATEV2IP = bytes(range(65, 81))
|
|
179
|
+
vl = hello(LATEV2IP, 'OneIP-2', 'P9SN66662905', V2IP_SOURCE | V2IP_SINK)
|
|
180
|
+
vl._hello_received -= (MXR_CONFIG_TIMEOUT + 1)
|
|
181
|
+
rx(LATEV2IP, 0x02, bay(0, 0, 0, 'Input 1') + bay(16, 1, 0, 'Output 1'))
|
|
182
|
+
assert vl.has_bays and not vl.need_link_config
|
|
183
|
+
assert not vl.configuration_complete, 'the window must not stand in for the V2IP source list'
|
|
184
|
+
assert not vl.check_configuration_complete_timeout()
|
|
185
|
+
assert vl not in completed
|
|
186
|
+
|
|
187
|
+
rx(LATEV2IP, 0x26, bytes(40 * 2))
|
|
188
|
+
assert vl.configuration_complete and vl.check_configuration_complete_timeout()
|
|
189
|
+
assert completed == [dev, tz, amp, late, vl], completed
|
|
190
|
+
print('complete : nor does it stand in for a V2IP source list')
|
|
191
|
+
|
|
192
|
+
# Announced by the expiry alone, driven the way the runtime drives it. No frame
|
|
193
|
+
# arrives when a window closes, so the probe loop is the only thing that can
|
|
194
|
+
# notice, and the call it makes there is the whole of the announcement path.
|
|
195
|
+
stuck_seen = []
|
|
196
|
+
runtime = mx_remote.Remote(open_connection=False, callbacks=Watching(stuck_seen))
|
|
197
|
+
runtime._uid = bytes(range(100, 116))
|
|
198
|
+
runtime.transmit = lambda data: len(data) # no socket; the loop's sends are not the subject
|
|
199
|
+
|
|
200
|
+
STUCK = bytes(range(81, 97))
|
|
201
|
+
def rx_runtime(op, pl):
|
|
202
|
+
runtime.process_frame(time.time(), create_mxr_frame(STUCK, op, pl), ADDR)
|
|
203
|
+
rx_runtime(0x00, struct.pack('<H', 0x28) + nm('MX-3') + nm('P8SN33333333') + nm('4.7.9')
|
|
204
|
+
+ struct.pack('<I', VIDEO_ROUTING | AUDIO_ROUTING))
|
|
205
|
+
stuck = runtime.get_by_uid(MxrDeviceUid(STUCK))
|
|
206
|
+
rx_runtime(0x02, bay(0, 0, 0, 'In 1'))
|
|
207
|
+
|
|
208
|
+
runtime._probe_once()
|
|
209
|
+
assert not stuck.configuration_complete
|
|
210
|
+
assert stuck_seen == [], 'inside the window the loop announces nothing'
|
|
211
|
+
|
|
212
|
+
stuck._hello_received -= (MXR_CONFIG_TIMEOUT + 1)
|
|
213
|
+
runtime._probe_once()
|
|
214
|
+
assert stuck_seen == [stuck], 'the loop announces a device no further frame will complete'
|
|
215
|
+
print('complete : announced from the probe loop, on the expiry alone')
|
|
216
|
+
|
|
217
|
+
print()
|
|
218
|
+
print('ALL OK')
|
|
@@ -1,105 +0,0 @@
|
|
|
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
|
-
'''When a device counts as fully described, and what each half of that rests on.
|
|
8
|
-
|
|
9
|
-
Both halves are paged lists with nothing on the wire marking the last page: no
|
|
10
|
-
count, no index, no terminating frame. So neither can be tested by comparing
|
|
11
|
-
what arrived against what is still to come, and each needs a test of its own
|
|
12
|
-
shape.
|
|
13
|
-
|
|
14
|
-
The bays: that the primary list was sent at all, which every unit sends whatever
|
|
15
|
-
else it sends. The links: one record per bay, so a record for every input and
|
|
16
|
-
output is the whole list.
|
|
17
|
-
|
|
18
|
-
A device re-sends its configuration, so every assertion below has to survive the
|
|
19
|
-
same frame twice. That is what makes the link half a set of ports rather than a
|
|
20
|
-
count - one page repeated would otherwise stand in for the pages behind it.
|
|
21
|
-
'''
|
|
22
|
-
|
|
23
|
-
import logging, os, struct, sys, time
|
|
24
|
-
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
25
|
-
logging.disable(logging.CRITICAL)
|
|
26
|
-
import mx_remote
|
|
27
|
-
from mx_remote.Uid import MxrDeviceUid
|
|
28
|
-
from mx_remote.proto.Factory import create_mxr_frame
|
|
29
|
-
|
|
30
|
-
completed = []
|
|
31
|
-
class Watching(mx_remote.MxrCallbacks):
|
|
32
|
-
def on_device_config_complete(self, dev):
|
|
33
|
-
completed.append(dev)
|
|
34
|
-
|
|
35
|
-
UID = bytes(range(1, 17))
|
|
36
|
-
ADDR = ('192.0.2.9', 8812)
|
|
37
|
-
mx = mx_remote.Remote(open_connection=False, callbacks=Watching())
|
|
38
|
-
mx._uid = bytes(range(100, 116))
|
|
39
|
-
|
|
40
|
-
def nm(s, sz=16):
|
|
41
|
-
b = s.encode('ascii')[:sz]
|
|
42
|
-
return b + bytes(sz - len(b))
|
|
43
|
-
def rx(op, pl=b''):
|
|
44
|
-
mx.process_frame(time.time(), create_mxr_frame(UID, op, pl), ADDR)
|
|
45
|
-
def bay(port, mode, num, name):
|
|
46
|
-
return bytes([port, mode, num, 0, 0]) + nm(name) + nm(name) + nm('1080p') \
|
|
47
|
-
+ struct.pack('<I', 0) + struct.pack('<I', (1 << 1) if mode == 0 else (1 << 0))
|
|
48
|
-
def link(port, serial, baynm):
|
|
49
|
-
return bytes([port, 0]) + nm(serial) + nm(baynm) + struct.pack('<I', 1)
|
|
50
|
-
|
|
51
|
-
# A video matrix, so it is a device that reports links at all.
|
|
52
|
-
FEAT = (1 << 5) | (1 << 6)
|
|
53
|
-
rx(0x00, struct.pack('<H', 0x28) + nm('MX-1') + nm('P8SN12345678') + nm('4.7.9')
|
|
54
|
-
+ struct.pack('<I', FEAT))
|
|
55
|
-
dev = mx.get_by_uid(MxrDeviceUid(UID))
|
|
56
|
-
assert dev is not None and dev.is_video_matrix
|
|
57
|
-
assert not dev.configuration_complete, 'a hello describes no bays'
|
|
58
|
-
print('complete : a hello on its own is not a configuration')
|
|
59
|
-
|
|
60
|
-
# ---- the primary bay list, and only that one
|
|
61
|
-
# The secondary list registers the same bays, so after it the device knows every
|
|
62
|
-
# bay it will ever hear about and still has not heard the list that says so.
|
|
63
|
-
rx(0x23, bay(0, 0, 0, 'In 1') + bay(1, 0, 1, 'In 2') + bay(2, 1, 0, 'Out 1'))
|
|
64
|
-
assert len(dev.bays) == 3, dev.bays
|
|
65
|
-
assert not dev.has_bays, 'the secondary list does not stand in for the primary one'
|
|
66
|
-
assert not dev.configuration_complete
|
|
67
|
-
|
|
68
|
-
rx(0x02, bay(0, 0, 0, 'In 1') + bay(1, 0, 1, 'In 2'))
|
|
69
|
-
assert dev.has_bays, 'the primary list is what completes the bays'
|
|
70
|
-
assert len(dev.bays) == 3, 'a page is merged into the cache, never replaces it'
|
|
71
|
-
print('complete : the bays need the primary list, whatever else arrived')
|
|
72
|
-
|
|
73
|
-
# ---- one link record per bay
|
|
74
|
-
assert dev.need_link_config, 'no record has arrived'
|
|
75
|
-
assert not dev.configuration_complete
|
|
76
|
-
|
|
77
|
-
# The same page three times. A count would reach the three records this device
|
|
78
|
-
# owes; the ports say one bay has reported and two have not.
|
|
79
|
-
for _ in range(3):
|
|
80
|
-
rx(0x03, link(0, 'P8SN00000001', 'Out 1'))
|
|
81
|
-
assert dev.need_link_config, 'a repeated page must not stand in for the pages behind it'
|
|
82
|
-
|
|
83
|
-
# A record for a bay this device never advertised is not one of the three.
|
|
84
|
-
rx(0x03, link(9, 'P8SN00000001', 'Out 1'))
|
|
85
|
-
assert dev.need_link_config, 'a record on an unknown bay counts for nothing'
|
|
86
|
-
|
|
87
|
-
rx(0x03, link(1, 'P8SN00000002', 'Out 2'))
|
|
88
|
-
assert dev.need_link_config, 'two of three bays have reported'
|
|
89
|
-
assert not dev.configuration_complete
|
|
90
|
-
assert not completed, 'nothing may be announced complete while a record is missing'
|
|
91
|
-
|
|
92
|
-
rx(0x03, link(2, 'P8SN00000003', 'In 1'))
|
|
93
|
-
assert not dev.need_link_config, 'every bay has reported its link record'
|
|
94
|
-
assert dev.configuration_complete
|
|
95
|
-
print('complete : one record per bay, counted per bay and not per page')
|
|
96
|
-
|
|
97
|
-
# ---- announced once, at that moment
|
|
98
|
-
assert completed == [dev], completed
|
|
99
|
-
rx(0x02, bay(0, 0, 0, 'In 1') + bay(1, 0, 1, 'In 2'))
|
|
100
|
-
rx(0x03, link(0, 'P8SN00000001', 'Out 1'))
|
|
101
|
-
assert completed == [dev], 'a re-sent configuration must not announce completion again'
|
|
102
|
-
print('complete : announced once, when the last of it arrived')
|
|
103
|
-
|
|
104
|
-
print()
|
|
105
|
-
print('ALL OK')
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|