mx-remote 6.2.0__tar.gz → 6.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-6.2.0 → mx_remote-6.3.0}/PKG-INFO +1 -1
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/const.py +1 -1
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/Constants.py +1 -0
- mx_remote-6.3.0/mx_remote/proto/FrameDiscover.py +44 -0
- mx_remote-6.3.0/mx_remote/proto/FrameDiscover.pyi +12 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/remote/ConnectionAsync.py +5 -2
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/remote/Remote.py +67 -3
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/remote/Remote.pyi +2 -2
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/run.py +1 -0
- mx_remote-6.3.0/tests/staterequest.py +119 -0
- mx_remote-6.2.0/mx_remote/proto/FrameDiscover.py +0 -25
- mx_remote-6.2.0/mx_remote/proto/FrameDiscover.pyi +0 -8
- {mx_remote-6.2.0 → mx_remote-6.3.0}/.gitignore +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/AGENTS.md +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/CLAUDE.md +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/LICENSE +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/README.md +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/docs/README.md +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/docs/_static/brand.css +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/docs/_static/favicon.svg +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/docs/_static/logo-dark.svg +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/docs/_static/logo.svg +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/docs/api.md +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/docs/audio.md +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/docs/callbacks.md +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/docs/conf.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/docs/configuration.md +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/docs/devices-and-bays.md +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/docs/diagnostics.md +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/docs/index.md +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/docs/multiviewer.md +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/docs/oneip.md +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/docs/requirements.txt +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/docs/routing.md +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/hatch_build.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/Interface.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/Interface.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/Uid.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/Uid.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/__init__.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/__init__.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/compat.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/compat.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/const.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/deprecated.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/deprecated.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/main.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/main.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/BayConfig.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/BayConfig.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/Constants.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/Data.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/Data.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/Factory.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/Factory.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameAmpDolbySettings.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameAmpDolbySettings.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameAmpZoneSettings.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameAmpZoneSettings.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameAudioClip.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameAudioClip.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameAudioSetRoute.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameAudioSetRoute.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameBase.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameBase.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameBayConfig.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameBayConfig.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameBayConfigSecondary.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameBayConfigSecondary.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameBayHide.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameBayHide.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameBayStatus.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameBayStatus.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameConnectStatus.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameConnectStatus.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameDebug.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameDebug.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameEDID.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameEDID.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameEDIDProfile.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameEDIDProfile.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameFactoryReset.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameFactoryReset.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameFilterStatus.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameFilterStatus.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameFirmwareVersion.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameFirmwareVersion.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameHeader.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameHeader.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameHello.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameHello.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameLinks.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameLinks.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameMeshOperation.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameMeshOperation.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameMirrorStatus.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameMirrorStatus.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameMonitoringPulse.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameMonitoringPulse.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameNetworkStatus.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameNetworkStatus.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FramePing.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FramePing.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FramePowerChange.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FramePowerChange.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameRCAction.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameRCAction.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameRCIr.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameRCIr.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameRCKey.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameRCKey.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameRCSettings.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameRCSettings.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameReboot.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameReboot.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameRoutingChange.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameRoutingChange.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameSetInstaller.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameSetInstaller.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameSetName.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameSetName.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameSetRoute.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameSetRoute.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameSetupStatus.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameSetupStatus.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameSignalStatus.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameSignalStatus.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameSignalStatusNew.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameSignalStatusNew.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameSysTemperature.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameSysTemperature.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameSystemStatus.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameSystemStatus.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameTXRCAction.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameTXRCAction.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameTXRCKey.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameTXRCKey.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameTime.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameTime.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameTimeZone.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameTimeZone.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameTopology.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameTopology.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameTxIR.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameTxIR.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameUpgradeFPGA.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameUpgradeFPGA.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPAudio.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPAudio.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPBayMapping.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPBayMapping.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPDetectBays.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPDetectBays.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPLink.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPLink.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPMultiviewer.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPMultiviewer.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPPowerSave.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPPowerSave.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPSetMaster.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPSetMaster.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPSettingsAll.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPSettingsAll.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPSourceSwitch.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPSourceSwitch.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPSources.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPSources.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPStats.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPStats.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPTestcard.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPTestcard.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPTiling.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPTiling.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPVideoWall.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameV2IPVideoWall.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameVolume.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameVolume.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameVolumeDown.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameVolumeDown.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameVolumeSet.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameVolumeSet.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameVolumeUp.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/FrameVolumeUp.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/LinkConfig.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/LinkConfig.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/Multiviewer.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/Multiviewer.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/Svd.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/Svd.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/V2IPConfig.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/V2IPConfig.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/V2IPStats.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/V2IPStats.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/__init__.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/__init__.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/proto/svd.csv +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/py.typed +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/remote/Bay.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/remote/Bay.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/remote/ConnectionAsync.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/remote/Device.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/remote/Device.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/remote/Link.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/remote/Link.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/remote/State.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/remote/State.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/remote/V2IP.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/mx_remote/remote/V2IP.pyi +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/pyproject.toml +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/audio_ir.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/audiolock.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/capture.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/complete.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/deprecated.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/e2e.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/entry.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/handlers.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/handlers_cover.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/hello.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/idempotent.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/lengths.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/merge.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/mesh.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/newops.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/offline.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/offsets.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/ping.py +1 -1
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/proto_stamp.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/protogate.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/rcsettings.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/roundtrip.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/smoke.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/sources.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/stats.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/structs.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/txresult.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/unknowns.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/v2ipscaling.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/v2ipsettings.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/v2ipsubject.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/v2iptestcard.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/v2ipvlan.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/vdetails.py +0 -0
- {mx_remote-6.2.0 → mx_remote-6.3.0}/tests/wirefix.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: mx_remote
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.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://opdenkamp.github.io/mx-remote/
|
|
@@ -256,6 +256,7 @@ class DeviceFeature(IntFlag):
|
|
|
256
256
|
VLAN = (1 << 28)
|
|
257
257
|
'''Tags its uplink by the VLAN configuration it reports, and takes one written to it.'''
|
|
258
258
|
BOOT_BIT = (1 << 31)
|
|
259
|
+
'''Flips on every reboot, so a peer that sees it change knows the device restarted.'''
|
|
259
260
|
|
|
260
261
|
class OneIPVideoProcessorFeature(IntFlag):
|
|
261
262
|
'''What a OneIP device's video processor supports, as the device reports it in
|
|
@@ -0,0 +1,44 @@
|
|
|
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 network device discovery.'''
|
|
8
|
+
|
|
9
|
+
import warnings
|
|
10
|
+
from functools import cached_property
|
|
11
|
+
from .FrameBase import FrameBase
|
|
12
|
+
from ..Interface import DeviceRegistry
|
|
13
|
+
from ..Uid import MxrDeviceUid
|
|
14
|
+
|
|
15
|
+
# Payload: empty to ask every device, or the 16 byte uid of the one device
|
|
16
|
+
# asked. Any other length asks every device, which is also how a receiver that
|
|
17
|
+
# predates the uid form reads the uid form.
|
|
18
|
+
|
|
19
|
+
class FrameDiscover(FrameBase):
|
|
20
|
+
'''Discovery frame that asks devices on the network to send their info.'''
|
|
21
|
+
@staticmethod
|
|
22
|
+
def construct(mxr:DeviceRegistry, target:MxrDeviceUid|None=None) -> FrameBase|None:
|
|
23
|
+
'''Build a discover asking every device, or only target.
|
|
24
|
+
|
|
25
|
+
Not refused for a target that predates the uid form: it answers as if
|
|
26
|
+
every device had been asked.'''
|
|
27
|
+
payload = bytes([]) if (target is None) else target.byte_value
|
|
28
|
+
return FrameBase.construct_base(mxr=mxr, opcode=1, protocol=1, payload=payload)
|
|
29
|
+
|
|
30
|
+
@cached_property
|
|
31
|
+
def target_uid(self) -> MxrDeviceUid|None:
|
|
32
|
+
'''The one device asked, or None when every device is.'''
|
|
33
|
+
if (self.payload is None) or (len(self.payload) != 16):
|
|
34
|
+
return None
|
|
35
|
+
return self.payload_uuid(0)
|
|
36
|
+
|
|
37
|
+
def __str__(self) -> str:
|
|
38
|
+
if (self.target_uid is None):
|
|
39
|
+
return "discover devices"
|
|
40
|
+
return f"discover {self.mxr.uid_to_user_string(self.target_uid)}"
|
|
41
|
+
|
|
42
|
+
def constructFrameDiscover(mxr:DeviceRegistry) -> FrameBase|None:
|
|
43
|
+
warnings.warn("use FrameDiscover.construct() instead", DeprecationWarning)
|
|
44
|
+
return FrameDiscover.construct(mxr=mxr)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from ..Interface import DeviceRegistry as DeviceRegistry
|
|
2
|
+
from ..Uid import MxrDeviceUid as MxrDeviceUid
|
|
3
|
+
from .FrameBase import FrameBase as FrameBase
|
|
4
|
+
from functools import cached_property
|
|
5
|
+
|
|
6
|
+
class FrameDiscover(FrameBase):
|
|
7
|
+
@staticmethod
|
|
8
|
+
def construct(mxr: DeviceRegistry, target: MxrDeviceUid | None = None) -> FrameBase | None: ...
|
|
9
|
+
@cached_property
|
|
10
|
+
def target_uid(self) -> MxrDeviceUid | None: ...
|
|
11
|
+
|
|
12
|
+
def constructFrameDiscover(mxr: DeviceRegistry) -> FrameBase | None: ...
|
|
@@ -39,8 +39,11 @@ _LOGGER = logging.getLogger(__name__)
|
|
|
39
39
|
#
|
|
40
40
|
# The answer to a discover goes to the group, is jittered so a mesh does not
|
|
41
41
|
# reply at once, and is expensive - Remote._background_probe() rate-limits it and
|
|
42
|
-
# stops once every device reports complete. After that
|
|
43
|
-
#
|
|
42
|
+
# stops once every device reports complete. After that a device repeats a frame
|
|
43
|
+
# in only a couple of periodic broadcasts after it changes, and resends
|
|
44
|
+
# everything only every few minutes. So a device first heard, or heard again
|
|
45
|
+
# after it went offline, is asked on its own with a discover whose payload is
|
|
46
|
+
# its 16-byte uid - see Remote._request_state().
|
|
44
47
|
#
|
|
45
48
|
# Signal status (0x31) is the one opcode with a targeted request form of its own,
|
|
46
49
|
# an empty payload or a 16-byte uid, which this library does not send.
|
|
@@ -23,8 +23,8 @@ from .ConnectionAsync import ConnectionAsync
|
|
|
23
23
|
from ..const import MXR_HELLO_INTERVAL_MIN, MXR_HELLO_INTERVAL_RAND, __version__
|
|
24
24
|
from .Device import Device
|
|
25
25
|
from ..Interface import ConnectionCallbacks, DeviceRegistry, MxrDeviceUid, BayLinks, BayBase, DeviceBase, MxrCallbacks, AudioEndpoint, mxr_broadcast_address, OneIPDeviceSettings, OneIPPowerSaveSchedule
|
|
26
|
-
from ..proto.Constants import MXR_PROTOCOL_VERSION
|
|
27
|
-
from ..proto.FrameDiscover import constructFrameDiscover
|
|
26
|
+
from ..proto.Constants import MXR_PROTOCOL_VERSION, DeviceFeature
|
|
27
|
+
from ..proto.FrameDiscover import FrameDiscover, constructFrameDiscover
|
|
28
28
|
from ..proto.Factory import process_mxr_frame
|
|
29
29
|
from ..proto.FrameHello import FrameHello, constructFrameHello
|
|
30
30
|
from ..proto.FramePing import FramePing
|
|
@@ -49,6 +49,42 @@ _LOGGER = logging.getLogger(__name__)
|
|
|
49
49
|
# asks one device.
|
|
50
50
|
_ACCEPT_UNANNOUNCED = (0x00, 0x01, 0x4A)
|
|
51
51
|
|
|
52
|
+
# How long a discover to every device stands in for asking any one of them, in
|
|
53
|
+
# seconds. Each device answers it with everything it holds, after a jitter of up
|
|
54
|
+
# to a few seconds, so asking one again inside this window repeats an answer
|
|
55
|
+
# already on its way.
|
|
56
|
+
_DISCOVER_COVERS = 60
|
|
57
|
+
|
|
58
|
+
# What this client had heard of a device before a frame from it: whether it had
|
|
59
|
+
# gone offline, and its reboot toggle as last announced.
|
|
60
|
+
_Heard = tuple[bool, bool]
|
|
61
|
+
|
|
62
|
+
def _heard(device:DeviceBase) -> _Heard:
|
|
63
|
+
features = device.features
|
|
64
|
+
return (not device.online,
|
|
65
|
+
(features is not None) and (DeviceFeature.BOOT_BIT in features))
|
|
66
|
+
|
|
67
|
+
def _missed_state(before:_Heard|None, device:DeviceBase) -> bool:
|
|
68
|
+
'''Whether a device just heard may hold state this client never received.
|
|
69
|
+
|
|
70
|
+
A device repeats a frame only for a short while after it changes, so
|
|
71
|
+
whatever it sent before this client first heard it, or while it was out of
|
|
72
|
+
earshot, may not come again. before is None for a device this frame
|
|
73
|
+
registered.
|
|
74
|
+
|
|
75
|
+
Not asked: a device whose reboot toggle flipped, or that is about to reboot,
|
|
76
|
+
because a booting device sends everything it holds; and a management client,
|
|
77
|
+
which holds nothing to send.
|
|
78
|
+
'''
|
|
79
|
+
features = device.features
|
|
80
|
+
if (features is None) or (DeviceFeature.MANAGER in features) \
|
|
81
|
+
or (DeviceFeature.STATUS_REBOOTING in features):
|
|
82
|
+
return False
|
|
83
|
+
if (before is None):
|
|
84
|
+
return True
|
|
85
|
+
away, boot_bit = before
|
|
86
|
+
return away and (boot_bit == (DeviceFeature.BOOT_BIT in features))
|
|
87
|
+
|
|
52
88
|
class Remote(DeviceRegistry, ConnectionCallbacks):
|
|
53
89
|
''' Main component that handles the network connections and registration of remote devices '''
|
|
54
90
|
|
|
@@ -85,6 +121,7 @@ class Remote(DeviceRegistry, ConnectionCallbacks):
|
|
|
85
121
|
self._target_ip = target_ip
|
|
86
122
|
self._port = port
|
|
87
123
|
self._discover_timeout = 0
|
|
124
|
+
self._state_request:MxrDeviceUid|None = None
|
|
88
125
|
self._addr_filter = addr_filter
|
|
89
126
|
self._svd = SvdMap()
|
|
90
127
|
if open_connection:
|
|
@@ -403,6 +440,21 @@ class Remote(DeviceRegistry, ConnectionCallbacks):
|
|
|
403
440
|
_LOGGER.debug("discovering devices")
|
|
404
441
|
return self.transmit(pkt.frame)
|
|
405
442
|
|
|
443
|
+
def _request_state(self, device:MxrDeviceUid) -> None:
|
|
444
|
+
'''Ask one device for everything it holds, with a discover naming it.
|
|
445
|
+
|
|
446
|
+
Skipped while a discover to every device is recent enough to have
|
|
447
|
+
covered it. A receiver that predates the named form answers it as a
|
|
448
|
+
discover to every device, so each one asked costs the whole mesh's state
|
|
449
|
+
on such a network.
|
|
450
|
+
'''
|
|
451
|
+
if ((time.time() - self._discover_timeout) < _DISCOVER_COVERS):
|
|
452
|
+
return
|
|
453
|
+
pkt = FrameDiscover.construct(mxr=self, target=device)
|
|
454
|
+
if (pkt is not None):
|
|
455
|
+
_LOGGER.debug(f"asking {self.uid_to_user_string(device)} for its state")
|
|
456
|
+
self.transmit(pkt.frame)
|
|
457
|
+
|
|
406
458
|
def _send_to_all(self, frame:FrameBase|None) -> bool:
|
|
407
459
|
'''Send a frame to every device, True when all of it was written.'''
|
|
408
460
|
if (frame is None):
|
|
@@ -520,8 +572,13 @@ class Remote(DeviceRegistry, ConnectionCallbacks):
|
|
|
520
572
|
self.tx_discover()
|
|
521
573
|
|
|
522
574
|
def process_frame(self, timestamp:float, data: bytes, addr: tuple[str, int]) -> None:
|
|
523
|
-
'''Decode and process a received mx_remote frame.
|
|
575
|
+
'''Decode and process a received mx_remote frame.
|
|
576
|
+
|
|
577
|
+
A device that may hold state this client missed is left in
|
|
578
|
+
_state_request rather than asked here, so replaying a capture sends
|
|
579
|
+
nothing; on_datagram_received does the asking.'''
|
|
524
580
|
proc = False
|
|
581
|
+
self._state_request = None
|
|
525
582
|
try:
|
|
526
583
|
frame = process_mxr_frame(mxr=self, timestamp=timestamp, data=data, addr=addr)
|
|
527
584
|
if (frame is not None) and (frame.header.protocol > MXR_PROTOCOL_VERSION):
|
|
@@ -557,7 +614,12 @@ class Remote(DeviceRegistry, ConnectionCallbacks):
|
|
|
557
614
|
raise
|
|
558
615
|
try:
|
|
559
616
|
if (frame is not None) and proc:
|
|
617
|
+
known = self.remotes.get(frame.remote_id)
|
|
618
|
+
before = None if (known is None) else _heard(known)
|
|
560
619
|
frame.process()
|
|
620
|
+
device = self.remotes.get(frame.remote_id)
|
|
621
|
+
if (device is not None) and _missed_state(before, device):
|
|
622
|
+
self._state_request = frame.remote_id
|
|
561
623
|
except Exception:
|
|
562
624
|
_LOGGER.warning(f"failed to process frame: {traceback.format_exc()}")
|
|
563
625
|
raise
|
|
@@ -566,6 +628,8 @@ class Remote(DeviceRegistry, ConnectionCallbacks):
|
|
|
566
628
|
'''Called when a UDP frame was received.'''
|
|
567
629
|
timestamp = time.time()
|
|
568
630
|
self.process_frame(timestamp=timestamp, data=data, addr=addr)
|
|
631
|
+
if (self._state_request is not None):
|
|
632
|
+
self._request_state(self._state_request)
|
|
569
633
|
|
|
570
634
|
@override
|
|
571
635
|
def on_mxr_update(self, data:Any) -> None:
|
|
@@ -3,10 +3,10 @@ from ..Interface import AudioEndpoint as AudioEndpoint, BayBase as BayBase, BayL
|
|
|
3
3
|
from ..Uid import MxrDeviceUid as MxrDeviceUid
|
|
4
4
|
from ..compat import override as override
|
|
5
5
|
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__
|
|
6
|
-
from ..proto.Constants import MXR_PROTOCOL_VERSION as MXR_PROTOCOL_VERSION, ONEIP_DEVICE_SETTINGS_REPORTED_ONLY as ONEIP_DEVICE_SETTINGS_REPORTED_ONLY, ONEIP_IR_PROFILE_MAX as ONEIP_IR_PROFILE_MAX, ONEIP_IR_PROFILE_NOT_SET as ONEIP_IR_PROFILE_NOT_SET, OneIPDeviceSetting as OneIPDeviceSetting
|
|
6
|
+
from ..proto.Constants import DeviceFeature as DeviceFeature, MXR_PROTOCOL_VERSION as MXR_PROTOCOL_VERSION, ONEIP_DEVICE_SETTINGS_REPORTED_ONLY as ONEIP_DEVICE_SETTINGS_REPORTED_ONLY, ONEIP_IR_PROFILE_MAX as ONEIP_IR_PROFILE_MAX, ONEIP_IR_PROFILE_NOT_SET as ONEIP_IR_PROFILE_NOT_SET, OneIPDeviceSetting as OneIPDeviceSetting
|
|
7
7
|
from ..proto.Factory import process_mxr_frame as process_mxr_frame
|
|
8
8
|
from ..proto.FrameBase import FrameBase as FrameBase
|
|
9
|
-
from ..proto.FrameDiscover import constructFrameDiscover as constructFrameDiscover
|
|
9
|
+
from ..proto.FrameDiscover import FrameDiscover as FrameDiscover, constructFrameDiscover as constructFrameDiscover
|
|
10
10
|
from ..proto.FrameHello import FrameHello as FrameHello, constructFrameHello as constructFrameHello
|
|
11
11
|
from ..proto.FramePing import FramePing as FramePing
|
|
12
12
|
from ..proto.FrameTime import FrameTime as FrameTime
|
|
@@ -52,6 +52,7 @@ SUITES = [
|
|
|
52
52
|
'v2ipvlan', # a V2IP device's VLAN configuration, read and written
|
|
53
53
|
'v2iptestcard', # a V2IP sink's test pattern, tone and lip-sync flash
|
|
54
54
|
'audiolock', # an audio endpoint's status, and locking its source
|
|
55
|
+
'staterequest', # a device that may have missed state is asked for it
|
|
55
56
|
'deprecated', # the V2IP names 5.11.0 exported still resolve, with a warning
|
|
56
57
|
]
|
|
57
58
|
|
|
@@ -0,0 +1,119 @@
|
|
|
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
|
+
'''A device that may hold state this client missed is asked for it.
|
|
8
|
+
|
|
9
|
+
A device repeats a frame in only a couple of periodic broadcasts after it
|
|
10
|
+
changes, and resends everything only every few minutes. So a device this client
|
|
11
|
+
first hears, or hears again after counting it offline, is sent a discover
|
|
12
|
+
naming it - unless its own boot or a recent discover to every device already
|
|
13
|
+
covers that.
|
|
14
|
+
'''
|
|
15
|
+
|
|
16
|
+
import os, struct, sys, time, logging
|
|
17
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
18
|
+
logging.disable(logging.CRITICAL)
|
|
19
|
+
import mx_remote
|
|
20
|
+
from mx_remote.proto.Factory import create_mxr_frame
|
|
21
|
+
from datetime import timedelta
|
|
22
|
+
|
|
23
|
+
F = mx_remote.DeviceFeature
|
|
24
|
+
ADDR = ('192.0.2.9', 8812)
|
|
25
|
+
HELLO, DISCOVER = 0x00, 0x01
|
|
26
|
+
OURS = bytes(range(0x20, 0x30))
|
|
27
|
+
|
|
28
|
+
def name(s, sz=16):
|
|
29
|
+
b = s.encode('ascii')[:sz]
|
|
30
|
+
return b + bytes(sz - len(b))
|
|
31
|
+
|
|
32
|
+
def uid(n):
|
|
33
|
+
return bytes([n]) * 16
|
|
34
|
+
|
|
35
|
+
def hello(mx, peer, features):
|
|
36
|
+
mx.on_datagram_received(create_mxr_frame(peer, HELLO, struct.pack('<H', 0x2A) + name('ONEIP')
|
|
37
|
+
+ name('SR0001') + name('1.0.0')
|
|
38
|
+
+ struct.pack('<I', int(features))), ADDR)
|
|
39
|
+
|
|
40
|
+
def silence(mx, peer):
|
|
41
|
+
'''Move the last time peer was heard far enough back to count as offline.'''
|
|
42
|
+
dev = mx.get_by_uid(mx_remote.MxrDeviceUid(peer))
|
|
43
|
+
dev._last_ping -= timedelta(seconds=600)
|
|
44
|
+
assert not dev.online
|
|
45
|
+
|
|
46
|
+
def discovers(sent):
|
|
47
|
+
'''The discovers sent, as their payloads.'''
|
|
48
|
+
return [f[24:] for f in sent if int.from_bytes(f[20:22], 'little') == DISCOVER]
|
|
49
|
+
|
|
50
|
+
def client():
|
|
51
|
+
mx = mx_remote.Remote(open_connection=False)
|
|
52
|
+
mx._uid = OURS
|
|
53
|
+
mx._hello_due = time.time() + 3600 # nothing is due on the clock
|
|
54
|
+
sent = []
|
|
55
|
+
mx.transmit = lambda data: (sent.append(data), len(data))[1]
|
|
56
|
+
return mx, sent
|
|
57
|
+
|
|
58
|
+
ROUTING = F.VIDEO_ROUTING
|
|
59
|
+
|
|
60
|
+
# ---- asked: a device first heard, and one back from offline
|
|
61
|
+
mx, sent = client()
|
|
62
|
+
peer = uid(0x21)
|
|
63
|
+
hello(mx, peer, ROUTING)
|
|
64
|
+
assert discovers(sent) == [peer], 'a device this client first heard'
|
|
65
|
+
print('asked : a device first heard is sent a discover naming it')
|
|
66
|
+
|
|
67
|
+
sent.clear()
|
|
68
|
+
hello(mx, peer, ROUTING)
|
|
69
|
+
assert discovers(sent) == [], 'a device that never went away'
|
|
70
|
+
print('asked : not again while it stays online')
|
|
71
|
+
|
|
72
|
+
silence(mx, peer)
|
|
73
|
+
hello(mx, peer, ROUTING)
|
|
74
|
+
assert discovers(sent) == [peer], 'a device back from offline'
|
|
75
|
+
print('asked : again once it comes back from offline')
|
|
76
|
+
|
|
77
|
+
# ---- not asked: a device that sends everything anyway, or holds nothing
|
|
78
|
+
mx, sent = client()
|
|
79
|
+
rebooted = uid(0x23)
|
|
80
|
+
hello(mx, rebooted, ROUTING)
|
|
81
|
+
silence(mx, rebooted)
|
|
82
|
+
sent.clear()
|
|
83
|
+
hello(mx, rebooted, ROUTING | F.BOOT_BIT)
|
|
84
|
+
assert discovers(sent) == [], 'a device whose reboot toggle flipped'
|
|
85
|
+
print('not asked : a device whose reboot toggle flipped')
|
|
86
|
+
|
|
87
|
+
rebooting = uid(0x24)
|
|
88
|
+
hello(mx, rebooting, ROUTING | F.STATUS_REBOOTING)
|
|
89
|
+
assert discovers(sent) == [], 'a device about to reboot'
|
|
90
|
+
print('not asked : a device about to reboot')
|
|
91
|
+
|
|
92
|
+
manager = uid(0x25)
|
|
93
|
+
hello(mx, manager, F.MANAGER)
|
|
94
|
+
assert discovers(sent) == [], 'a management client'
|
|
95
|
+
print('not asked : a management client')
|
|
96
|
+
|
|
97
|
+
# ---- covered: a discover to every device already asked each one
|
|
98
|
+
mx, sent = client()
|
|
99
|
+
mx.tx_discover()
|
|
100
|
+
sent.clear()
|
|
101
|
+
hello(mx, uid(0x27), ROUTING)
|
|
102
|
+
assert discovers(sent) == [], 'asked again inside the window'
|
|
103
|
+
print('covered : a discover to every device covers one heard within 60s')
|
|
104
|
+
|
|
105
|
+
mx._discover_timeout = time.time() - 61
|
|
106
|
+
hello(mx, uid(0x28), ROUTING)
|
|
107
|
+
assert discovers(sent) == [uid(0x28)], 'not asked once the window had passed'
|
|
108
|
+
print('covered : but not one heard after')
|
|
109
|
+
|
|
110
|
+
# ---- replay: processing a capture asks nothing, there is no one to ask
|
|
111
|
+
mx = mx_remote.Remote(open_connection=False)
|
|
112
|
+
mx._uid = OURS
|
|
113
|
+
mx.process_frame(time.time(), create_mxr_frame(uid(0x29), HELLO, struct.pack('<H', 0x2A) + name('ONEIP')
|
|
114
|
+
+ name('SR0001') + name('1.0.0')
|
|
115
|
+
+ struct.pack('<I', int(ROUTING))), ADDR)
|
|
116
|
+
assert mx.get_by_uid(mx_remote.MxrDeviceUid(uid(0x29))) is not None
|
|
117
|
+
print('replay : a capture registers the device without sending')
|
|
118
|
+
|
|
119
|
+
print('ALL OK')
|
|
@@ -1,25 +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
|
-
'''Protocol frame for network device discovery.'''
|
|
8
|
-
|
|
9
|
-
import warnings
|
|
10
|
-
from .FrameBase import FrameBase
|
|
11
|
-
from ..Interface import DeviceRegistry
|
|
12
|
-
|
|
13
|
-
class FrameDiscover(FrameBase):
|
|
14
|
-
'''Discovery frame that asks all devices on the network to send their info.'''
|
|
15
|
-
@staticmethod
|
|
16
|
-
def construct(mxr:DeviceRegistry) -> FrameBase|None:
|
|
17
|
-
'''Build a discovery broadcast frame for transmission.'''
|
|
18
|
-
return FrameBase.construct_base(mxr=mxr, opcode=1, protocol=1)
|
|
19
|
-
|
|
20
|
-
def __str__(self) -> str:
|
|
21
|
-
return "discover devices"
|
|
22
|
-
|
|
23
|
-
def constructFrameDiscover(mxr:DeviceRegistry) -> FrameBase|None:
|
|
24
|
-
warnings.warn("use FrameDiscover.construct() instead", DeprecationWarning)
|
|
25
|
-
return FrameDiscover.construct(mxr=mxr)
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
from ..Interface import DeviceRegistry as DeviceRegistry
|
|
2
|
-
from .FrameBase import FrameBase as FrameBase
|
|
3
|
-
|
|
4
|
-
class FrameDiscover(FrameBase):
|
|
5
|
-
@staticmethod
|
|
6
|
-
def construct(mxr: DeviceRegistry) -> FrameBase | None: ...
|
|
7
|
-
|
|
8
|
-
def constructFrameDiscover(mxr: DeviceRegistry) -> FrameBase | None: ...
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|