mx-remote 9.3.0__tar.gz → 9.4.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-9.3.0 → mx_remote-9.4.0}/PKG-INFO +1 -1
- {mx_remote-9.3.0 → mx_remote-9.4.0}/docs/oneip.md +14 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/Interface.py +21 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/Interface.pyi +9 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/const.py +1 -1
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/Constants.py +12 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/Constants.pyi +5 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPSinkSdp.py +56 -4
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPSinkSdp.pyi +15 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/remote/Device.py +102 -4
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/remote/Device.pyi +8 -2
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/unit.py +30 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/v2ipsdp.py +80 -2
- {mx_remote-9.3.0 → mx_remote-9.4.0}/.gitignore +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/AGENTS.md +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/CLAUDE.md +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/LICENSE +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/README.md +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/docs/README.md +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/docs/_static/brand.css +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/docs/_static/favicon.svg +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/docs/_static/logo-dark.svg +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/docs/_static/logo.svg +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/docs/api.md +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/docs/audio.md +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/docs/callbacks.md +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/docs/conf.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/docs/configuration.md +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/docs/devices-and-bays.md +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/docs/diagnostics.md +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/docs/index.md +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/docs/multiviewer.md +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/docs/requirements.txt +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/docs/routing.md +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/hatch_build.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/Uid.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/Uid.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/__init__.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/__init__.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/compat.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/compat.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/const.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/deprecated.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/deprecated.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/main.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/main.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/BayConfig.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/BayConfig.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/Data.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/Data.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/Factory.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/Factory.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameAmpDolbySettings.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameAmpDolbySettings.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameAmpZoneSettings.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameAmpZoneSettings.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameAudioClip.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameAudioClip.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameAudioSetRoute.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameAudioSetRoute.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameBase.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameBase.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameBayConfig.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameBayConfig.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameBayConfigSecondary.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameBayConfigSecondary.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameBayContent.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameBayContent.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameBayHide.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameBayHide.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameBayStatus.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameBayStatus.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameConnectStatus.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameConnectStatus.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameDebug.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameDebug.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameDiscover.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameDiscover.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameEDID.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameEDID.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameEDIDProfile.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameEDIDProfile.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameFactoryReset.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameFactoryReset.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameFilterStatus.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameFilterStatus.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameFirmwareVersion.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameFirmwareVersion.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameHeader.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameHeader.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameHello.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameHello.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameLinks.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameLinks.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameMeshOperation.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameMeshOperation.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameMirrorStatus.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameMirrorStatus.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameMonitoringPulse.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameMonitoringPulse.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameNetworkStatus.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameNetworkStatus.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FramePing.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FramePing.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FramePowerChange.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FramePowerChange.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameRCAction.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameRCAction.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameRCIr.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameRCIr.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameRCKey.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameRCKey.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameRCSettings.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameRCSettings.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameReboot.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameReboot.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameRoutingChange.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameRoutingChange.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSetInstaller.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSetInstaller.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSetName.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSetName.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSetRoute.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSetRoute.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSetupStatus.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSetupStatus.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSignalStatus.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSignalStatus.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSignalStatusNew.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSignalStatusNew.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSysTemperature.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSysTemperature.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSystemStatus.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSystemStatus.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameTXRCAction.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameTXRCAction.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameTXRCKey.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameTXRCKey.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameThirdParty.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameThirdParty.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameTime.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameTime.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameTimeZone.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameTimeZone.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameTopology.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameTopology.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameTxIR.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameTxIR.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameUnitCommand.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameUnitCommand.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameUnitStatus.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameUnitStatus.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameUpgradeFPGA.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameUpgradeFPGA.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPAudio.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPAudio.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPBayMapping.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPBayMapping.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPDetectBays.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPDetectBays.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPLink.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPLink.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPMultiviewer.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPMultiviewer.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPOsdSync.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPOsdSync.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPPowerSave.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPPowerSave.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPPrivacy.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPPrivacy.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPSetMaster.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPSetMaster.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPSettingsAll.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPSettingsAll.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPSourceSwitch.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPSourceSwitch.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPSources.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPSources.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPStats.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPStats.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPTestcard.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPTestcard.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPTiling.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPTiling.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPVideoWall.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPVideoWall.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameVolume.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameVolume.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameVolumeDown.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameVolumeDown.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameVolumeSet.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameVolumeSet.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameVolumeUp.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/FrameVolumeUp.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/LinkConfig.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/LinkConfig.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/Multiviewer.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/Multiviewer.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/Svd.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/Svd.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/V2IPConfig.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/V2IPConfig.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/V2IPStats.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/V2IPStats.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/__init__.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/__init__.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/proto/svd.csv +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/py.typed +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/remote/Bay.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/remote/Bay.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/remote/ConnectionAsync.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/remote/ConnectionAsync.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/remote/Link.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/remote/Link.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/remote/OsdSync.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/remote/OsdSync.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/remote/Remote.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/remote/Remote.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/remote/State.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/remote/State.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/remote/V2IP.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/mx_remote/remote/V2IP.pyi +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/pyproject.toml +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/audio_ir.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/audiolock.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/baycontent.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/capture.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/complete.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/deprecated.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/downmix.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/e2e.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/entry.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/handlers.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/handlers_cover.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/hello.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/idempotent.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/lengths.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/merge.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/mesh.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/meshbays.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/newops.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/offline.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/offsets.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/osdmessage.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/osdsync.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/ping.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/privacy.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/proto_stamp.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/protogate.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/rcsettings.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/roundtrip.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/routes.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/run.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/smoke.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/sources.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/staterequest.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/stats.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/structs.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/thirdparty.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/txresult.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/uid.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/unknowns.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/v2ipptp.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/v2ipscaling.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/v2ipsettings.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/v2ipsubject.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/v2iptestcard.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/v2ipvlan.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/vdetails.py +0 -0
- {mx_remote-9.3.0 → mx_remote-9.4.0}/tests/wirefix.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: mx_remote
|
|
3
|
-
Version: 9.
|
|
3
|
+
Version: 9.4.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/
|
|
@@ -432,6 +432,14 @@ application, which this client announces itself as, and only from protocol
|
|
|
432
432
|
after its last page, so one is refused before it is sent. Nothing acknowledges
|
|
433
433
|
the pages; `oneip_sink_sdp` reads what the output reports next.
|
|
434
434
|
|
|
435
|
+
From protocol 0x31 an output also reports the description it plays, when it
|
|
436
|
+
starts a stream other than by its mesh controller's command and when asked:
|
|
437
|
+
|
|
438
|
+
```python
|
|
439
|
+
await sink.request_oneip_sink_description() # False below protocol 0x31
|
|
440
|
+
print(sink.oneip_sink_description) # None until reported, and once it plays none
|
|
441
|
+
```
|
|
442
|
+
|
|
435
443
|
### Overlay sync
|
|
436
444
|
|
|
437
445
|
A management unit can have the other units copy its overlay captions, logo
|
|
@@ -553,8 +561,14 @@ await device.set_unit_network(UnitNetConfig(
|
|
|
553
561
|
ip='10.1.2.3', netmask='255.255.255.0', gateway='10.1.2.254', dns1='10.1.2.254'))
|
|
554
562
|
await device.confirm_unit_network() # keep it, once the unit reports it
|
|
555
563
|
await device.set_unit_dhcp()
|
|
564
|
+
await device.locate_unit(timed=True) # flash its LED for 30 s; timed=False until stopped
|
|
565
|
+
await device.stop_locating_unit()
|
|
556
566
|
```
|
|
557
567
|
|
|
568
|
+
A unit is located only once its status carries `UnitFlag.LOCATE_SUPPORTED`. Its
|
|
569
|
+
board-ID LED flashes and an output shows "Locate Device"; `UnitFlag.LOCATE` in
|
|
570
|
+
the cached status says so from the moment the command is sent.
|
|
571
|
+
|
|
558
572
|
A changed IP configuration applies at once and reverts unless the mesh
|
|
559
573
|
controller confirms it in time; `status.network_pending` says whether it still
|
|
560
574
|
waits. Each command returns False without sending for a device that is not a
|
|
@@ -3355,6 +3355,15 @@ class DeviceBase(ABC):
|
|
|
3355
3355
|
async def confirm_unit_network(self) -> bool:
|
|
3356
3356
|
'''keep the unit's pending IP configuration'''
|
|
3357
3357
|
|
|
3358
|
+
@abstractmethod
|
|
3359
|
+
async def locate_unit(self, timed:bool=False) -> bool:
|
|
3360
|
+
'''flash the unit's board-ID LED and show "Locate Device" on its output,
|
|
3361
|
+
for 30 s when timed is set, until stop_locating_unit() otherwise'''
|
|
3362
|
+
|
|
3363
|
+
@abstractmethod
|
|
3364
|
+
async def stop_locating_unit(self) -> bool:
|
|
3365
|
+
'''stop locating the unit'''
|
|
3366
|
+
|
|
3358
3367
|
@property
|
|
3359
3368
|
@abstractmethod
|
|
3360
3369
|
def oneip_downmix(self) -> OneIPDownmix|None:
|
|
@@ -3740,6 +3749,18 @@ class DeviceBase(ABC):
|
|
|
3740
3749
|
async def request_oneip_sink_sdp(self) -> bool:
|
|
3741
3750
|
'''ask this OneIP output what it plays from a session description now'''
|
|
3742
3751
|
|
|
3752
|
+
@property
|
|
3753
|
+
@abstractmethod
|
|
3754
|
+
def oneip_sink_description(self) -> str|None:
|
|
3755
|
+
'''The session description this OneIP output last reported playing, None
|
|
3756
|
+
until it has and once it reports playing none. An output reports one
|
|
3757
|
+
when it starts a stream other than by its mesh controller's command,
|
|
3758
|
+
and when asked with request_oneip_sink_description().'''
|
|
3759
|
+
|
|
3760
|
+
@abstractmethod
|
|
3761
|
+
async def request_oneip_sink_description(self) -> bool:
|
|
3762
|
+
'''ask this OneIP output for the session description it plays'''
|
|
3763
|
+
|
|
3743
3764
|
@abstractmethod
|
|
3744
3765
|
async def set_oneip_test_pattern(self, pattern:OneIPTestPattern, colour:int=0) -> bool:
|
|
3745
3766
|
'''show a test pattern on this sink's output'''
|
|
@@ -1210,6 +1210,10 @@ class DeviceBase(ABC):
|
|
|
1210
1210
|
async def set_unit_network(self, network: UnitNetConfig) -> bool: ...
|
|
1211
1211
|
@abstractmethod
|
|
1212
1212
|
async def confirm_unit_network(self) -> bool: ...
|
|
1213
|
+
@abstractmethod
|
|
1214
|
+
async def locate_unit(self, timed: bool = False) -> bool: ...
|
|
1215
|
+
@abstractmethod
|
|
1216
|
+
async def stop_locating_unit(self) -> bool: ...
|
|
1213
1217
|
@property
|
|
1214
1218
|
@abstractmethod
|
|
1215
1219
|
def oneip_downmix(self) -> OneIPDownmix | None: ...
|
|
@@ -1393,6 +1397,11 @@ class DeviceBase(ABC):
|
|
|
1393
1397
|
async def set_oneip_sdp_screen(self, screen: int) -> bool: ...
|
|
1394
1398
|
@abstractmethod
|
|
1395
1399
|
async def request_oneip_sink_sdp(self) -> bool: ...
|
|
1400
|
+
@property
|
|
1401
|
+
@abstractmethod
|
|
1402
|
+
def oneip_sink_description(self) -> str | None: ...
|
|
1403
|
+
@abstractmethod
|
|
1404
|
+
async def request_oneip_sink_description(self) -> bool: ...
|
|
1396
1405
|
@abstractmethod
|
|
1397
1406
|
async def set_oneip_test_pattern(self, pattern: OneIPTestPattern, colour: int = 0) -> bool: ...
|
|
1398
1407
|
@abstractmethod
|
|
@@ -684,6 +684,11 @@ class UnitFlag(IntFlag):
|
|
|
684
684
|
'''The boot count is reported.'''
|
|
685
685
|
FAN_SPEED = (1 << 15)
|
|
686
686
|
'''The fan speed is reported.'''
|
|
687
|
+
LOCATE_SUPPORTED = (1 << 16)
|
|
688
|
+
'''The unit takes UnitCommandKind.LOCATE.'''
|
|
689
|
+
LOCATE = (1 << 17)
|
|
690
|
+
'''The unit is being located: its board-ID LED flashes, and an output shows
|
|
691
|
+
"Locate Device".'''
|
|
687
692
|
|
|
688
693
|
class UnitCommandKind(IntEnum):
|
|
689
694
|
'''What a unit command asks of its unit.'''
|
|
@@ -695,6 +700,8 @@ class UnitCommandKind(IntEnum):
|
|
|
695
700
|
'''Change the IP configuration.'''
|
|
696
701
|
NET_CONFIRM = 3
|
|
697
702
|
'''Keep a pending IP configuration.'''
|
|
703
|
+
LOCATE = 4
|
|
704
|
+
'''Start or stop locating the unit.'''
|
|
698
705
|
|
|
699
706
|
class UnitCommandFlag(IntFlag):
|
|
700
707
|
'''The flags of a unit command.'''
|
|
@@ -703,6 +710,11 @@ class UnitCommandFlag(IntFlag):
|
|
|
703
710
|
already.'''
|
|
704
711
|
DHCP = (1 << 1)
|
|
705
712
|
'''With NET_CONFIG: use DHCP, ignoring the addresses.'''
|
|
713
|
+
LOCATE_ON = (1 << 2)
|
|
714
|
+
'''With LOCATE: locate it until stopped. Neither this nor LOCATE_TIMED
|
|
715
|
+
stops it.'''
|
|
716
|
+
LOCATE_TIMED = (1 << 3)
|
|
717
|
+
'''With LOCATE: locate it for 30 s.'''
|
|
706
718
|
|
|
707
719
|
UNIT_MAX_TEMPERATURES = 6
|
|
708
720
|
"""The most temperature sensors a unit reports."""
|
|
@@ -271,16 +271,21 @@ class UnitFlag(IntFlag):
|
|
|
271
271
|
POWER_CHECK: Incomplete
|
|
272
272
|
BOOT_COUNT: Incomplete
|
|
273
273
|
FAN_SPEED: Incomplete
|
|
274
|
+
LOCATE_SUPPORTED: Incomplete
|
|
275
|
+
LOCATE: Incomplete
|
|
274
276
|
|
|
275
277
|
class UnitCommandKind(IntEnum):
|
|
276
278
|
CLEAR_STATUS: int
|
|
277
279
|
VIDEO_PROCESSOR_UPGRADE: int
|
|
278
280
|
NET_CONFIG: int
|
|
279
281
|
NET_CONFIRM: int
|
|
282
|
+
LOCATE: int
|
|
280
283
|
|
|
281
284
|
class UnitCommandFlag(IntFlag):
|
|
282
285
|
FORCE: Incomplete
|
|
283
286
|
DHCP: Incomplete
|
|
287
|
+
LOCATE_ON: Incomplete
|
|
288
|
+
LOCATE_TIMED: Incomplete
|
|
284
289
|
|
|
285
290
|
UNIT_MAX_TEMPERATURES: int
|
|
286
291
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
'''Protocol frame for V2IP_SINK_SDP (opcode 0x52): what a OneIP output plays
|
|
8
8
|
from a session description, and the commands that hand it one.'''
|
|
9
9
|
|
|
10
|
+
from dataclasses import dataclass
|
|
10
11
|
from functools import cached_property
|
|
11
12
|
import ipaddress
|
|
12
13
|
import struct
|
|
@@ -30,9 +31,11 @@ from ..Uid import MxrDeviceUid
|
|
|
30
31
|
#
|
|
31
32
|
# A page follows the header with the description's length, the page's offset
|
|
32
33
|
# into it and the page's length, all u16, two reserved bytes, and at most
|
|
33
|
-
# SDP_PAGE_LEN bytes of the description.
|
|
34
|
+
# SDP_PAGE_LEN bytes of the description. An output reports the description it
|
|
35
|
+
# plays in pages of the same shape.
|
|
34
36
|
SDP_HEADER_SIZE = 20
|
|
35
37
|
_STATUS_SIZE = 80
|
|
38
|
+
_PAGE_HEADER_SIZE = 8
|
|
36
39
|
|
|
37
40
|
SDP_STATUS = 0
|
|
38
41
|
'''The output's report.'''
|
|
@@ -44,10 +47,35 @@ SDP_SCREEN = 3
|
|
|
44
47
|
'''What the output shows while it plays audio alone, in the value byte.'''
|
|
45
48
|
SDP_REQUEST = 4
|
|
46
49
|
'''Report now.'''
|
|
50
|
+
SDP_DESCRIPTION = 5
|
|
51
|
+
'''A page of the description an output reports playing.'''
|
|
52
|
+
|
|
53
|
+
SDP_REQUEST_DESCRIPTION = (1 << 0)
|
|
54
|
+
'''The bit of a request's value that asks for the description too.'''
|
|
55
|
+
|
|
56
|
+
SDP_DESCRIPTION_PROTOCOL = 0x31
|
|
57
|
+
'''The first protocol version whose outputs report the description they play.
|
|
58
|
+
Not the stamp: that is the opcode's, and an output between the two answers a
|
|
59
|
+
request for the description with its status alone.'''
|
|
60
|
+
|
|
61
|
+
SDP_PAGE_TIMEOUT = 5
|
|
62
|
+
'''How long an output may take between two pages of one description, in
|
|
63
|
+
seconds, before the pages it sent are dropped.'''
|
|
47
64
|
|
|
48
65
|
SDP_PAGE_LEN = 1024
|
|
49
66
|
'''The most bytes of a session description one page carries.'''
|
|
50
67
|
|
|
68
|
+
@dataclass(frozen=True)
|
|
69
|
+
class SdpDescriptionPage:
|
|
70
|
+
'''A page of the description an output reports playing.'''
|
|
71
|
+
total: int
|
|
72
|
+
'''The length of the whole description.'''
|
|
73
|
+
offset: int
|
|
74
|
+
'''Where this page's text starts in it.'''
|
|
75
|
+
text: bytes
|
|
76
|
+
timestamp: float
|
|
77
|
+
'''When the page arrived.'''
|
|
78
|
+
|
|
51
79
|
def _cstr(data:bytes) -> str:
|
|
52
80
|
return data.split(b'\0', 1)[0].decode('ascii', errors='replace')
|
|
53
81
|
|
|
@@ -109,14 +137,38 @@ class FrameV2IPSinkSdp(FrameBase):
|
|
|
109
137
|
# that fills its field stops one byte short of it.
|
|
110
138
|
name=_cstr(s[24:71]), layout=_cstr(s[72:79]))
|
|
111
139
|
|
|
140
|
+
@cached_property
|
|
141
|
+
def description_page(self) -> SdpDescriptionPage|None:
|
|
142
|
+
'''A page of the description the output reports playing, None for
|
|
143
|
+
another type, for a page whose header names a device other than its
|
|
144
|
+
sender, which only the output itself reports, and for a frame too short
|
|
145
|
+
for the text its page header declares.'''
|
|
146
|
+
p = self.payload or b''
|
|
147
|
+
if (self.kind != SDP_DESCRIPTION) or (self.target_uid != self.remote_id) \
|
|
148
|
+
or (len(p) < SDP_HEADER_SIZE + _PAGE_HEADER_SIZE):
|
|
149
|
+
return None
|
|
150
|
+
total, offset, length = struct.unpack_from('<HHH', p, SDP_HEADER_SIZE)
|
|
151
|
+
start = SDP_HEADER_SIZE + _PAGE_HEADER_SIZE
|
|
152
|
+
if (len(p) < start + length):
|
|
153
|
+
return None
|
|
154
|
+
return SdpDescriptionPage(total=total, offset=offset, text=bytes(p[start:start + length]),
|
|
155
|
+
timestamp=self.timestamp)
|
|
156
|
+
|
|
112
157
|
@override
|
|
113
158
|
def process(self) -> None:
|
|
114
|
-
'''Record an output's report
|
|
115
|
-
types are commands for an output,
|
|
116
|
-
|
|
159
|
+
'''Record an output's report and the pages of its description against
|
|
160
|
+
the output that sent them. The other types are commands for an output,
|
|
161
|
+
which only that output acts on.'''
|
|
162
|
+
if ((dev := self.remote_device) is None):
|
|
163
|
+
return
|
|
164
|
+
if ((status := self.status) is not None):
|
|
117
165
|
dev.on_mxr_update(status)
|
|
166
|
+
elif ((page := self.description_page) is not None):
|
|
167
|
+
dev.on_mxr_update(page)
|
|
118
168
|
|
|
119
169
|
def __str__(self) -> str:
|
|
120
170
|
if (self.status is not None):
|
|
121
171
|
return f"OneIP sink SDP: {self.status}"
|
|
172
|
+
if ((page := self.description_page) is not None):
|
|
173
|
+
return f"OneIP sink SDP description: bytes {page.offset} to {page.offset + len(page.text)} of {page.total}"
|
|
122
174
|
return f"OneIP sink SDP command {self.kind} for {self.uid_to_user_string(self.target_uid)}"
|
|
@@ -3,6 +3,8 @@ from ..Uid import MxrDeviceUid as MxrDeviceUid
|
|
|
3
3
|
from ..compat import override as override
|
|
4
4
|
from .Constants import OneIPSdpClockMode as OneIPSdpClockMode, OneIPSdpClockState as OneIPSdpClockState, OneIPSdpEncoding as OneIPSdpEncoding, OneIPSdpFlag as OneIPSdpFlag, OneIPSdpMediaClock as OneIPSdpMediaClock, OneIPSdpRefClock as OneIPSdpRefClock
|
|
5
5
|
from .FrameBase import FrameBase as FrameBase
|
|
6
|
+
from _typeshed import Incomplete
|
|
7
|
+
from dataclasses import dataclass
|
|
6
8
|
from functools import cached_property
|
|
7
9
|
from typing import Any
|
|
8
10
|
|
|
@@ -12,8 +14,19 @@ SDP_PAGE: int
|
|
|
12
14
|
SDP_STOP: int
|
|
13
15
|
SDP_SCREEN: int
|
|
14
16
|
SDP_REQUEST: int
|
|
17
|
+
SDP_DESCRIPTION: int
|
|
18
|
+
SDP_REQUEST_DESCRIPTION: Incomplete
|
|
19
|
+
SDP_DESCRIPTION_PROTOCOL: int
|
|
20
|
+
SDP_PAGE_TIMEOUT: int
|
|
15
21
|
SDP_PAGE_LEN: int
|
|
16
22
|
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class SdpDescriptionPage:
|
|
25
|
+
total: int
|
|
26
|
+
offset: int
|
|
27
|
+
text: bytes
|
|
28
|
+
timestamp: float
|
|
29
|
+
|
|
17
30
|
class FrameV2IPSinkSdp(FrameBase):
|
|
18
31
|
@staticmethod
|
|
19
32
|
def construct_command(mxr: DeviceRegistry, target: Any, target_uid: MxrDeviceUid, kind: int, value: int = 0) -> FrameBase | None: ...
|
|
@@ -25,5 +38,7 @@ class FrameV2IPSinkSdp(FrameBase):
|
|
|
25
38
|
def kind(self) -> int | None: ...
|
|
26
39
|
@cached_property
|
|
27
40
|
def status(self) -> OneIPSinkSdp | None: ...
|
|
41
|
+
@cached_property
|
|
42
|
+
def description_page(self) -> SdpDescriptionPage | None: ...
|
|
28
43
|
@override
|
|
29
44
|
def process(self) -> None: ...
|
|
@@ -74,6 +74,7 @@ from ..const import MXR_CONFIG_TIMEOUT
|
|
|
74
74
|
from ..Uid import MxrDeviceUid
|
|
75
75
|
from typing import Any, Callable
|
|
76
76
|
from ..compat import override
|
|
77
|
+
from dataclasses import replace
|
|
77
78
|
from datetime import datetime
|
|
78
79
|
from enum import Enum
|
|
79
80
|
import logging
|
|
@@ -88,13 +89,14 @@ from ..proto.Constants import (MXR_PROTOCOL_VERSION_SLOW_HELLO, ONEIP_DOWNMIX_LF
|
|
|
88
89
|
ONEIP_IR_PROFILE_MAX, ONEIP_IR_PROFILE_NOT_SET, ONEIP_VLAN_PORT_SFP,
|
|
89
90
|
ONEIP_DEVICE_SETTINGS_BLOCK, ONEIP_DSCP_MAX,
|
|
90
91
|
ONEIP_PTP_DOMAIN_MAX, ONEIP_SDP_MAX, OneIPPowerSaveLevel, OneIPPtpFlag, OneIPPtpMode, UnitCommandFlag,
|
|
91
|
-
UnitCommandKind)
|
|
92
|
+
UnitCommandKind, UnitFlag)
|
|
92
93
|
from ..proto.FrameUnitCommand import FrameUnitCommand
|
|
93
94
|
from ..proto.FrameV2IPAudio import AUDIO_LAYOUT_SINCE, FrameV2IPAudio
|
|
94
95
|
from ..proto.FrameV2IPDeviceConfiguration import FrameV2IPDeviceConfiguration
|
|
95
96
|
from ..proto.FrameV2IPOsdSync import FrameV2IPOsdSync, OSD_KIND_MESSAGE_REQUEST, OSD_KIND_MESSAGE_SET
|
|
96
97
|
from ..proto.FrameV2IPPrivacy import FrameV2IPPrivacy
|
|
97
|
-
from ..proto.FrameV2IPSinkSdp import FrameV2IPSinkSdp,
|
|
98
|
+
from ..proto.FrameV2IPSinkSdp import (FrameV2IPSinkSdp, SdpDescriptionPage, SDP_DESCRIPTION_PROTOCOL, SDP_PAGE_TIMEOUT,
|
|
99
|
+
SDP_REQUEST, SDP_REQUEST_DESCRIPTION, SDP_SCREEN, SDP_STOP)
|
|
98
100
|
from ..proto.FrameV2IPTestcard import (FrameV2IPTestcard, TESTCARD_REQUEST, TESTCARD_SET,
|
|
99
101
|
TESTCARD_PART_PATTERN, TESTCARD_PART_TONE, TESTCARD_PART_SYNC)
|
|
100
102
|
|
|
@@ -177,6 +179,11 @@ class Device(DeviceBase):
|
|
|
177
179
|
self._unit_status:UnitStatus|None = None
|
|
178
180
|
self._v2ip_testcard:OneIPTestcard|None = None
|
|
179
181
|
self._v2ip_sink_sdp:OneIPSinkSdp|None = None
|
|
182
|
+
self._v2ip_sink_description:str|None = None
|
|
183
|
+
# The pages of a description the output is still sending: its whole
|
|
184
|
+
# length, the bytes received so far from offset 0, and when the last
|
|
185
|
+
# page arrived.
|
|
186
|
+
self._v2ip_sink_description_pages:tuple[int, bytes, float]|None = None
|
|
180
187
|
self._v2ip_privacy:OneIPPrivacy|None = None
|
|
181
188
|
self._osd_sync:OsdSync|None = None
|
|
182
189
|
self._osd_sync_report:OsdSyncReport|None = None
|
|
@@ -1076,9 +1083,18 @@ class Device(DeviceBase):
|
|
|
1076
1083
|
self.callbacks.on_osd_message_changed(self, data)
|
|
1077
1084
|
self.call_callbacks()
|
|
1078
1085
|
elif isinstance(data, OneIPSinkSdp):
|
|
1079
|
-
|
|
1080
|
-
|
|
1086
|
+
changed = (self._v2ip_sink_sdp != data)
|
|
1087
|
+
self._v2ip_sink_sdp = data
|
|
1088
|
+
if not data.active:
|
|
1089
|
+
# A description is reported when a stream starts, so one that
|
|
1090
|
+
# has stopped leaves none behind it.
|
|
1091
|
+
self._v2ip_sink_description_pages = None
|
|
1092
|
+
changed |= (self._v2ip_sink_description is not None)
|
|
1093
|
+
self._v2ip_sink_description = None
|
|
1094
|
+
if changed:
|
|
1081
1095
|
self.call_callbacks()
|
|
1096
|
+
elif isinstance(data, SdpDescriptionPage):
|
|
1097
|
+
self._on_sink_description_page(data)
|
|
1082
1098
|
elif isinstance(data, OneIPPrivacy):
|
|
1083
1099
|
if (self._v2ip_privacy != data):
|
|
1084
1100
|
self._v2ip_privacy = data
|
|
@@ -1275,6 +1291,39 @@ class Device(DeviceBase):
|
|
|
1275
1291
|
upgrade_unit_video_processor() apply.'''
|
|
1276
1292
|
return self._unit_command(UnitCommandKind.NET_CONFIRM)
|
|
1277
1293
|
|
|
1294
|
+
async def locate_unit(self, timed:bool=False) -> bool:
|
|
1295
|
+
'''Locate this unit: its board-ID LED flashes, and an output shows
|
|
1296
|
+
"Locate Device" on its display. For 30 s when timed is set, until
|
|
1297
|
+
stop_locating_unit() otherwise.
|
|
1298
|
+
|
|
1299
|
+
Refused until the unit has reported its status, and for one whose report
|
|
1300
|
+
lacks UnitFlag.LOCATE_SUPPORTED. The cached status takes UnitFlag.LOCATE
|
|
1301
|
+
at once, as the mesh controller's copy does; the unit reports its own
|
|
1302
|
+
within 2 s, and again when a timed locate ends. Otherwise as
|
|
1303
|
+
upgrade_unit_video_processor().
|
|
1304
|
+
'''
|
|
1305
|
+
return self._locate(UnitCommandFlag.LOCATE_TIMED if timed else UnitCommandFlag.LOCATE_ON)
|
|
1306
|
+
|
|
1307
|
+
async def stop_locating_unit(self) -> bool:
|
|
1308
|
+
'''Stop locating this unit. As locate_unit().'''
|
|
1309
|
+
return self._locate(UnitCommandFlag(0))
|
|
1310
|
+
|
|
1311
|
+
def _locate(self, flags:UnitCommandFlag) -> bool:
|
|
1312
|
+
'''The one send behind the locate methods.'''
|
|
1313
|
+
if ((status := self._unit_status) is None):
|
|
1314
|
+
_LOGGER.warning(f"not locating {self}: it has not reported its status")
|
|
1315
|
+
return False
|
|
1316
|
+
if (UnitFlag.LOCATE_SUPPORTED not in status.flags):
|
|
1317
|
+
_LOGGER.warning(f"not locating {self}: it cannot be located")
|
|
1318
|
+
return False
|
|
1319
|
+
if not self._unit_command(UnitCommandKind.LOCATE, flags):
|
|
1320
|
+
return False
|
|
1321
|
+
located = int(status.flags) & ~int(UnitFlag.LOCATE)
|
|
1322
|
+
if flags:
|
|
1323
|
+
located |= int(UnitFlag.LOCATE)
|
|
1324
|
+
self.on_mxr_update(replace(status, flags=UnitFlag(located)))
|
|
1325
|
+
return True
|
|
1326
|
+
|
|
1278
1327
|
def _unit_command(self, command:UnitCommandKind, flags:UnitCommandFlag=UnitCommandFlag(0),
|
|
1279
1328
|
network:UnitNetConfig|None=None) -> bool:
|
|
1280
1329
|
'''The one send behind the unit command methods.'''
|
|
@@ -1888,6 +1937,55 @@ class Device(DeviceBase):
|
|
|
1888
1937
|
play_oneip_sdp().'''
|
|
1889
1938
|
return self._send_sink_sdp_command(SDP_REQUEST, 0)
|
|
1890
1939
|
|
|
1940
|
+
@property
|
|
1941
|
+
@override
|
|
1942
|
+
def oneip_sink_description(self) -> str|None:
|
|
1943
|
+
return self._v2ip_sink_description
|
|
1944
|
+
|
|
1945
|
+
async def request_oneip_sink_description(self) -> bool:
|
|
1946
|
+
'''Ask this OneIP output what it plays from a session description and for
|
|
1947
|
+
the description itself, which oneip_sink_description then reads. An
|
|
1948
|
+
output that plays none sends its status alone.
|
|
1949
|
+
|
|
1950
|
+
Refused below protocol 0x31, where an output ignores the ask for the
|
|
1951
|
+
description. Otherwise as play_oneip_sdp().'''
|
|
1952
|
+
if (self.protocol != 0) and (self.protocol < SDP_DESCRIPTION_PROTOCOL):
|
|
1953
|
+
_LOGGER.warning(f"not asking {self} for its session description: protocol {self.protocol:#04x} "
|
|
1954
|
+
f"predates it")
|
|
1955
|
+
return False
|
|
1956
|
+
return self._send_sink_sdp_command(SDP_REQUEST, SDP_REQUEST_DESCRIPTION)
|
|
1957
|
+
|
|
1958
|
+
def _on_sink_description_page(self, page:SdpDescriptionPage) -> None:
|
|
1959
|
+
'''Put together the description this output reports playing, one page
|
|
1960
|
+
at a time, as a unit puts together the pages it is sent.
|
|
1961
|
+
|
|
1962
|
+
A page at offset 0 starts a description of 1 to ONEIP_SDP_MAX bytes.
|
|
1963
|
+
Every later page has to continue it: at the offset received so far, for
|
|
1964
|
+
the same length, within SDP_PAGE_TIMEOUT of the page before. Anything
|
|
1965
|
+
else drops the pages received, and the description is kept once its
|
|
1966
|
+
last byte is in.'''
|
|
1967
|
+
pending = self._v2ip_sink_description_pages
|
|
1968
|
+
self._v2ip_sink_description_pages = None
|
|
1969
|
+
if (page.offset == 0):
|
|
1970
|
+
if not (1 <= page.total <= ONEIP_SDP_MAX):
|
|
1971
|
+
return
|
|
1972
|
+
data = b''
|
|
1973
|
+
elif (pending is not None) and (pending[0] == page.total) and (len(pending[1]) == page.offset) \
|
|
1974
|
+
and (page.timestamp - pending[2] <= SDP_PAGE_TIMEOUT):
|
|
1975
|
+
data = pending[1]
|
|
1976
|
+
else:
|
|
1977
|
+
return
|
|
1978
|
+
if (len(data) + len(page.text) > page.total):
|
|
1979
|
+
return
|
|
1980
|
+
data += page.text
|
|
1981
|
+
if (len(data) < page.total):
|
|
1982
|
+
self._v2ip_sink_description_pages = (page.total, data, page.timestamp)
|
|
1983
|
+
return
|
|
1984
|
+
description = data.decode('utf-8', errors='replace')
|
|
1985
|
+
if (self._v2ip_sink_description != description):
|
|
1986
|
+
self._v2ip_sink_description = description
|
|
1987
|
+
self.call_callbacks()
|
|
1988
|
+
|
|
1891
1989
|
@property
|
|
1892
1990
|
@override
|
|
1893
1991
|
def oneip_privacy(self) -> OneIPPrivacy|None:
|
|
@@ -3,7 +3,7 @@ from ..Uid import MxrDeviceUid as MxrDeviceUid
|
|
|
3
3
|
from ..compat import override as override
|
|
4
4
|
from ..const import MXR_CONFIG_TIMEOUT as MXR_CONFIG_TIMEOUT
|
|
5
5
|
from ..proto.BayConfig import BayConfig as BayConfig
|
|
6
|
-
from ..proto.Constants import BayFeaturesMask as BayFeaturesMask, DeviceFeature as DeviceFeature, MXR_PROTOCOL_VERSION_SLOW_HELLO as MXR_PROTOCOL_VERSION_SLOW_HELLO, 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, ONEIP_DEVICE_SETTINGS_BLOCK as ONEIP_DEVICE_SETTINGS_BLOCK, ONEIP_DEVICE_SETTING_SWITCHES as ONEIP_DEVICE_SETTING_SWITCHES, ONEIP_DOWNMIX_LFE_MAX as ONEIP_DOWNMIX_LFE_MAX, ONEIP_DSCP_MAX as ONEIP_DSCP_MAX, ONEIP_IR_PROFILE_MAX as ONEIP_IR_PROFILE_MAX, ONEIP_IR_PROFILE_NOT_SET as ONEIP_IR_PROFILE_NOT_SET, ONEIP_PTP_DOMAIN_MAX as ONEIP_PTP_DOMAIN_MAX, ONEIP_SDP_MAX as ONEIP_SDP_MAX, ONEIP_VLAN_PORT_SFP as ONEIP_VLAN_PORT_SFP, OSD_MESSAGE_TEXT_MAX as OSD_MESSAGE_TEXT_MAX, OneIPDeviceSetting as OneIPDeviceSetting, OneIPPowerSaveLevel as OneIPPowerSaveLevel, OneIPPtpFlag as OneIPPtpFlag, OneIPPtpMode as OneIPPtpMode, OneIPTestPattern as OneIPTestPattern, OneIPToneMode as OneIPToneMode, OneIPVideoProcessorFeature as OneIPVideoProcessorFeature, OneIPVlanFlag as OneIPVlanFlag, OsdMessageFlag as OsdMessageFlag, OsdMessagePriority as OsdMessagePriority, UnitCommandFlag as UnitCommandFlag, UnitCommandKind as UnitCommandKind
|
|
6
|
+
from ..proto.Constants import BayFeaturesMask as BayFeaturesMask, DeviceFeature as DeviceFeature, MXR_PROTOCOL_VERSION_SLOW_HELLO as MXR_PROTOCOL_VERSION_SLOW_HELLO, 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, ONEIP_DEVICE_SETTINGS_BLOCK as ONEIP_DEVICE_SETTINGS_BLOCK, ONEIP_DEVICE_SETTING_SWITCHES as ONEIP_DEVICE_SETTING_SWITCHES, ONEIP_DOWNMIX_LFE_MAX as ONEIP_DOWNMIX_LFE_MAX, ONEIP_DSCP_MAX as ONEIP_DSCP_MAX, ONEIP_IR_PROFILE_MAX as ONEIP_IR_PROFILE_MAX, ONEIP_IR_PROFILE_NOT_SET as ONEIP_IR_PROFILE_NOT_SET, ONEIP_PTP_DOMAIN_MAX as ONEIP_PTP_DOMAIN_MAX, ONEIP_SDP_MAX as ONEIP_SDP_MAX, ONEIP_VLAN_PORT_SFP as ONEIP_VLAN_PORT_SFP, OSD_MESSAGE_TEXT_MAX as OSD_MESSAGE_TEXT_MAX, OneIPDeviceSetting as OneIPDeviceSetting, OneIPPowerSaveLevel as OneIPPowerSaveLevel, OneIPPtpFlag as OneIPPtpFlag, OneIPPtpMode as OneIPPtpMode, OneIPTestPattern as OneIPTestPattern, OneIPToneMode as OneIPToneMode, OneIPVideoProcessorFeature as OneIPVideoProcessorFeature, OneIPVlanFlag as OneIPVlanFlag, OsdMessageFlag as OsdMessageFlag, OsdMessagePriority as OsdMessagePriority, UnitCommandFlag as UnitCommandFlag, UnitCommandKind as UnitCommandKind, UnitFlag as UnitFlag
|
|
7
7
|
from ..proto.FrameBase import FrameBase as FrameBase
|
|
8
8
|
from ..proto.FrameFirmwareVersion import FirmwareType as FirmwareType, FirmwareVersion as FirmwareVersion
|
|
9
9
|
from ..proto.FrameHello import FrameHello as FrameHello
|
|
@@ -20,7 +20,7 @@ from ..proto.FrameV2IPDeviceConfiguration import FrameV2IPDeviceConfiguration as
|
|
|
20
20
|
from ..proto.FrameV2IPMultiviewer import FrameV2IPMultiviewer as FrameV2IPMultiviewer, V2IPMultiviewerConfig as V2IPMultiviewerConfig
|
|
21
21
|
from ..proto.FrameV2IPOsdSync import FrameV2IPOsdSync as FrameV2IPOsdSync, OSD_KIND_MESSAGE_REQUEST as OSD_KIND_MESSAGE_REQUEST, OSD_KIND_MESSAGE_SET as OSD_KIND_MESSAGE_SET
|
|
22
22
|
from ..proto.FrameV2IPPrivacy import FrameV2IPPrivacy as FrameV2IPPrivacy
|
|
23
|
-
from ..proto.FrameV2IPSinkSdp import FrameV2IPSinkSdp as FrameV2IPSinkSdp, SDP_REQUEST as SDP_REQUEST, SDP_SCREEN as SDP_SCREEN, SDP_STOP as SDP_STOP
|
|
23
|
+
from ..proto.FrameV2IPSinkSdp import FrameV2IPSinkSdp as FrameV2IPSinkSdp, SDP_DESCRIPTION_PROTOCOL as SDP_DESCRIPTION_PROTOCOL, SDP_PAGE_TIMEOUT as SDP_PAGE_TIMEOUT, SDP_REQUEST as SDP_REQUEST, SDP_REQUEST_DESCRIPTION as SDP_REQUEST_DESCRIPTION, SDP_SCREEN as SDP_SCREEN, SDP_STOP as SDP_STOP, SdpDescriptionPage as SdpDescriptionPage
|
|
24
24
|
from ..proto.FrameV2IPStats import FrameV2IPStats as FrameV2IPStats
|
|
25
25
|
from ..proto.FrameV2IPTestcard import FrameV2IPTestcard as FrameV2IPTestcard, TESTCARD_PART_PATTERN as TESTCARD_PART_PATTERN, TESTCARD_PART_SYNC as TESTCARD_PART_SYNC, TESTCARD_PART_TONE as TESTCARD_PART_TONE, TESTCARD_REQUEST as TESTCARD_REQUEST, TESTCARD_SET as TESTCARD_SET
|
|
26
26
|
from ..proto.Multiviewer import MultiviewerAspectRatio as MultiviewerAspectRatio, MultiviewerBoolSetting as MultiviewerBoolSetting, MultiviewerConfig as MultiviewerConfig, MultiviewerEDIDTemplate as MultiviewerEDIDTemplate, MultiviewerHDCPMode as MultiviewerHDCPMode, MultiviewerITCMode as MultiviewerITCMode, MultiviewerOutputMode as MultiviewerOutputMode, MultiviewerPipPosition as MultiviewerPipPosition, MultiviewerPipSize as MultiviewerPipSize, MultiviewerSource as MultiviewerSource, MultiviewerViewMode as MultiviewerViewMode
|
|
@@ -254,6 +254,8 @@ class Device(DeviceBase):
|
|
|
254
254
|
async def set_unit_dhcp(self) -> bool: ...
|
|
255
255
|
async def set_unit_network(self, network: UnitNetConfig) -> bool: ...
|
|
256
256
|
async def confirm_unit_network(self) -> bool: ...
|
|
257
|
+
async def locate_unit(self, timed: bool = False) -> bool: ...
|
|
258
|
+
async def stop_locating_unit(self) -> bool: ...
|
|
257
259
|
async def ping(self) -> bool: ...
|
|
258
260
|
async def mesh_promote(self) -> bool: ...
|
|
259
261
|
async def auto_assign_oneip_source_addresses(self) -> bool: ...
|
|
@@ -301,6 +303,10 @@ class Device(DeviceBase):
|
|
|
301
303
|
async def request_oneip_sink_sdp(self) -> bool: ...
|
|
302
304
|
@property
|
|
303
305
|
@override
|
|
306
|
+
def oneip_sink_description(self) -> str | None: ...
|
|
307
|
+
async def request_oneip_sink_description(self) -> bool: ...
|
|
308
|
+
@property
|
|
309
|
+
@override
|
|
304
310
|
def oneip_privacy(self) -> OneIPPrivacy | None: ...
|
|
305
311
|
async def request_oneip_privacy(self) -> bool: ...
|
|
306
312
|
def __eq__(self, other: Any) -> bool: ...
|
|
@@ -180,6 +180,36 @@ for bad in (UnitNetConfig('0.0.0.0', '255.255.255.0'), UnitNetConfig('239.1.2.3'
|
|
|
180
180
|
refused(unit.set_unit_network(bad), f'network [{bad}]')
|
|
181
181
|
print('refusals : old units, non-units and networks a unit cannot hold')
|
|
182
182
|
|
|
183
|
+
# A unit says it can be located in bit 16 of its status flags, and that it is
|
|
184
|
+
# being located in bit 17.
|
|
185
|
+
def report_flags(flags):
|
|
186
|
+
return struct.pack('<I', flags) + report()[4:]
|
|
187
|
+
found = hello(uid(0x23), 0x2C, UNIT)
|
|
188
|
+
rx(uid(0x23), 0x4F, report_flags(1 << 16))
|
|
189
|
+
assert found.unit_status.flags == UnitFlag.LOCATE_SUPPORTED, found.unit_status.flags
|
|
190
|
+
rx(uid(0x23), 0x4F, report_flags(1 << 17))
|
|
191
|
+
assert found.unit_status.flags == UnitFlag.LOCATE, found.unit_status.flags
|
|
192
|
+
|
|
193
|
+
# A unit is located for 30 s or until stopped, only once it has reported that it
|
|
194
|
+
# can be, and the cached status says so at once.
|
|
195
|
+
located = hello(uid(0x24), 0x2C, UNIT)
|
|
196
|
+
refused(located.locate_unit(timed=True), 'a unit that has not reported its status')
|
|
197
|
+
rx(uid(0x24), 0x4F, report_flags(int(UnitFlag.FAN_SPEED)))
|
|
198
|
+
refused(located.locate_unit(timed=True), 'a unit that cannot be located')
|
|
199
|
+
refused(located.stop_locating_unit(), 'a unit that cannot be located')
|
|
200
|
+
rx(uid(0x24), 0x4F, report_flags(int(UnitFlag.LOCATE_SUPPORTED | UnitFlag.FAN_SPEED) | (1 << 30)))
|
|
201
|
+
assert call(located.locate_unit(timed=True)) is True
|
|
202
|
+
p = command()
|
|
203
|
+
assert p[:16] == uid(0x24) and p[16:18] == bytes([4, 0x08]), p.hex()
|
|
204
|
+
assert UnitFlag.LOCATE in located.unit_status.flags, 'the cached status was not marked located'
|
|
205
|
+
assert call(located.locate_unit()) is True
|
|
206
|
+
assert command()[16:18] == bytes([4, 0x04]), 'until stopped'
|
|
207
|
+
assert call(located.stop_locating_unit()) is True
|
|
208
|
+
assert command()[16:18] == bytes([4, 0x00]), 'stop'
|
|
209
|
+
assert located.unit_status.flags == UnitFlag(int(UnitFlag.LOCATE_SUPPORTED | UnitFlag.FAN_SPEED) | (1 << 30)), \
|
|
210
|
+
f'a stop clears the flag and nothing else: {located.unit_status.flags!r}'
|
|
211
|
+
print('locate : timed, until stopped and stop, once the unit says it can be')
|
|
212
|
+
|
|
183
213
|
# -------------------------------------------------- a command seen on the wire
|
|
184
214
|
|
|
185
215
|
# Decoded for the log; a command too short to hold its fields carries none.
|
|
@@ -37,10 +37,10 @@ def name(s, sz=16):
|
|
|
37
37
|
b = s.encode('ascii')[:sz]
|
|
38
38
|
return b + bytes(sz - len(b))
|
|
39
39
|
|
|
40
|
-
def rx(sender, opcode, payload=b'', protocol=0x2E):
|
|
40
|
+
def rx(sender, opcode, payload=b'', protocol=0x2E, when=0.0):
|
|
41
41
|
frame = bytearray(create_mxr_frame(sender, opcode, payload))
|
|
42
42
|
frame[2] = protocol
|
|
43
|
-
mx.process_frame(
|
|
43
|
+
mx.process_frame(when, bytes(frame), ADDR)
|
|
44
44
|
|
|
45
45
|
def hello(sender, protocol, features):
|
|
46
46
|
rx(sender, 0x00, struct.pack('<H', protocol) + name('ONEIP') + name('P8SN%08d' % sender[0])
|
|
@@ -114,4 +114,82 @@ refused(old.request_oneip_sink_sdp(), 'an output below 0x2E')
|
|
|
114
114
|
refused(out.set_oneip_sdp_screen(256), 'a screen past the field')
|
|
115
115
|
print('refusals : nothing an output ignores is sent')
|
|
116
116
|
|
|
117
|
+
# ------------------------------------------------------------------- description
|
|
118
|
+
def page(header_uid, total, offset, text):
|
|
119
|
+
'''One page of the description an output reports playing, its reserved
|
|
120
|
+
bytes set.'''
|
|
121
|
+
return (header_uid + bytes([5, 0xA5, 0x5A, 0xC3]) + struct.pack('<HHH', total, offset, len(text))
|
|
122
|
+
+ bytes([0x96, 0x69]) + text)
|
|
123
|
+
|
|
124
|
+
# Put together from its pages and kept once its last byte is in, and a status
|
|
125
|
+
# playing nothing clears it.
|
|
126
|
+
desc = hello(uid(0x20), 0x31, DeviceFeature.ONEIP_SINK)
|
|
127
|
+
sdp = bytes(ord('a') + i % 26 for i in range(1500))
|
|
128
|
+
rx(uid(0x20), 0x52, page(uid(0x20), 1500, 0, sdp[:1024]))
|
|
129
|
+
assert desc.oneip_sink_description is None, 'kept early'
|
|
130
|
+
rx(uid(0x20), 0x52, page(uid(0x20), 1500, 1024, sdp[1024:]))
|
|
131
|
+
assert desc.oneip_sink_description == sdp.decode(), desc.oneip_sink_description
|
|
132
|
+
playing = bytearray(report(uid(0x20)))
|
|
133
|
+
rx(uid(0x20), 0x52, bytes(playing))
|
|
134
|
+
assert desc.oneip_sink_description == sdp.decode(), 'a status playing something cleared it'
|
|
135
|
+
playing[20] &= ~int(OneIPSdpFlag.ACTIVE)
|
|
136
|
+
rx(uid(0x20), 0x52, bytes(playing))
|
|
137
|
+
assert desc.oneip_sink_description is None, 'a status playing nothing left it'
|
|
138
|
+
# A status playing nothing also drops the pages of one still arriving.
|
|
139
|
+
rx(uid(0x20), 0x52, page(uid(0x20), 4, 0, b'ab'))
|
|
140
|
+
rx(uid(0x20), 0x52, bytes(playing))
|
|
141
|
+
rx(uid(0x20), 0x52, page(uid(0x20), 4, 2, b'cd'))
|
|
142
|
+
assert desc.oneip_sink_description is None, 'pages from before a stop were kept'
|
|
143
|
+
print('description : put together from its pages, and cleared by a status playing nothing')
|
|
144
|
+
|
|
145
|
+
# A page that does not continue the description drops the pages received, and a
|
|
146
|
+
# description is started only at offset 0, by the output itself, for 1 to
|
|
147
|
+
# ONEIP_SDP_MAX bytes. Each case would complete a description if the rule it
|
|
148
|
+
# breaks were not checked, so none passes on the back of another's drop.
|
|
149
|
+
desc = hello(uid(0x21), 0x31, DeviceFeature.ONEIP_SINK)
|
|
150
|
+
me = uid(0x21)
|
|
151
|
+
short = page(me, 1, 0, b'ab')[:-1]
|
|
152
|
+
for frames in ((page(me, 6, 0, b'ab'), page(me, 6, 4, b'cdef'), page(me, 6, 2, b'cdef')), # out of order
|
|
153
|
+
(page(me, 6, 0, b'ab'), page(me, 4, 2, b'cd')), # another length
|
|
154
|
+
(page(me, 4, 0, b'ab'), page(me, 4, 2, b'cde')), # past the end
|
|
155
|
+
(page(me, 1, 1, b'b'),), # not at 0
|
|
156
|
+
(short,), # text cut short
|
|
157
|
+
(page(me, ONEIP_SDP_MAX + 1, 0, b'ab'),), # too long
|
|
158
|
+
(page(me, 0, 0, b''),), # empty
|
|
159
|
+
(page(uid(0x77), 2, 0, b'ab'),), # someone else's
|
|
160
|
+
(page(me, 2, 0, b'ab')[:25],), # page header cut short
|
|
161
|
+
(me + bytes([1]) + page(me, 2, 0, b'ab')[17:],)): # a page handed to it
|
|
162
|
+
for f in frames:
|
|
163
|
+
rx(me, 0x52, f)
|
|
164
|
+
assert desc.oneip_sink_description is None, frames
|
|
165
|
+
# Restarting at 0 starts afresh.
|
|
166
|
+
for f in (page(me, 4, 0, b'xx'), page(me, 4, 0, b'ab'), page(me, 4, 2, b'cd')):
|
|
167
|
+
rx(me, 0x52, f)
|
|
168
|
+
assert desc.oneip_sink_description == 'abcd', desc.oneip_sink_description
|
|
169
|
+
longest = hello(uid(0x22), 0x31, DeviceFeature.ONEIP_SINK)
|
|
170
|
+
rx(uid(0x22), 0x52, page(uid(0x22), ONEIP_SDP_MAX, 0, b'z' * 1024))
|
|
171
|
+
rx(uid(0x22), 0x52, page(uid(0x22), ONEIP_SDP_MAX, 1024, b'z' * 1024))
|
|
172
|
+
assert longest.oneip_sink_description == 'z' * ONEIP_SDP_MAX
|
|
173
|
+
print('pages : only pages that continue a description from offset 0 are kept')
|
|
174
|
+
|
|
175
|
+
# Pages more than 5 s apart are dropped.
|
|
176
|
+
slow = hello(uid(0x23), 0x31, DeviceFeature.ONEIP_SINK)
|
|
177
|
+
rx(uid(0x23), 0x52, page(uid(0x23), 4, 0, b'ab'), when=100.0)
|
|
178
|
+
rx(uid(0x23), 0x52, page(uid(0x23), 4, 2, b'cd'), when=105.001)
|
|
179
|
+
assert slow.oneip_sink_description is None, 'a page after 5 s was kept'
|
|
180
|
+
rx(uid(0x23), 0x52, page(uid(0x23), 4, 0, b'ab'), when=100.0)
|
|
181
|
+
rx(uid(0x23), 0x52, page(uid(0x23), 4, 2, b'cd'), when=105.0)
|
|
182
|
+
assert slow.oneip_sink_description == 'abcd', slow.oneip_sink_description
|
|
183
|
+
print('timeout : pages at most 5 s apart')
|
|
184
|
+
|
|
185
|
+
# Asking for it sets bit 0 of the request's value, at the opcode's stamp, and is
|
|
186
|
+
# refused below 0x31, where an output ignores the bit.
|
|
187
|
+
sent.clear()
|
|
188
|
+
assert call(desc.request_oneip_sink_description()) is True
|
|
189
|
+
assert int.from_bytes(sent[-1][20:22], 'little') == 0x52 and sent[-1][2] == 0x2E, sent[-1][:24].hex()
|
|
190
|
+
assert sent[-1][24:] == uid(0x21) + bytes([4, 1, 0, 0]), sent[-1][24:].hex()
|
|
191
|
+
refused(hello(uid(0x24), 0x30, DeviceFeature.ONEIP_SINK).request_oneip_sink_description(), 'an output below 0x31')
|
|
192
|
+
refused(src.request_oneip_sink_description(), 'a device that is not a OneIP output')
|
|
193
|
+
print('request : bit 0 of the value, refused below 0x31')
|
|
194
|
+
|
|
117
195
|
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
|