mx-remote 9.2.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.2.0 → mx_remote-9.4.0}/PKG-INFO +1 -1
- {mx_remote-9.2.0 → mx_remote-9.4.0}/docs/api.md +17 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/docs/callbacks.md +25 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/docs/oneip.md +14 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/Interface.py +90 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/Interface.pyi +27 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/const.py +1 -1
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/Constants.py +30 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/Constants.pyi +10 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/Factory.py +3 -0
- mx_remote-9.4.0/mx_remote/proto/FrameThirdParty.py +50 -0
- mx_remote-9.4.0/mx_remote/proto/FrameThirdParty.pyi +15 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPSinkSdp.py +56 -4
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPSinkSdp.pyi +15 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/remote/Device.py +102 -4
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/remote/Device.pyi +8 -2
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/remote/Remote.py +20 -2
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/remote/Remote.pyi +4 -2
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/handlers.py +1 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/run.py +1 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/smoke.py +1 -0
- mx_remote-9.4.0/tests/thirdparty.py +146 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/unit.py +30 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/v2ipsdp.py +80 -2
- {mx_remote-9.2.0 → mx_remote-9.4.0}/.gitignore +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/AGENTS.md +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/CLAUDE.md +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/LICENSE +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/README.md +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/docs/README.md +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/docs/_static/brand.css +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/docs/_static/favicon.svg +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/docs/_static/logo-dark.svg +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/docs/_static/logo.svg +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/docs/audio.md +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/docs/conf.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/docs/configuration.md +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/docs/devices-and-bays.md +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/docs/diagnostics.md +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/docs/index.md +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/docs/multiviewer.md +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/docs/requirements.txt +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/docs/routing.md +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/hatch_build.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/Uid.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/Uid.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/__init__.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/__init__.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/compat.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/compat.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/const.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/deprecated.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/deprecated.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/main.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/main.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/BayConfig.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/BayConfig.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/Data.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/Data.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/Factory.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameAmpDolbySettings.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameAmpDolbySettings.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameAmpZoneSettings.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameAmpZoneSettings.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameAudioClip.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameAudioClip.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameAudioSetRoute.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameAudioSetRoute.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameBase.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameBase.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameBayConfig.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameBayConfig.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameBayConfigSecondary.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameBayConfigSecondary.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameBayContent.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameBayContent.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameBayHide.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameBayHide.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameBayStatus.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameBayStatus.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameConnectStatus.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameConnectStatus.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameDebug.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameDebug.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameDiscover.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameDiscover.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameEDID.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameEDID.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameEDIDProfile.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameEDIDProfile.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameFactoryReset.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameFactoryReset.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameFilterStatus.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameFilterStatus.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameFirmwareVersion.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameFirmwareVersion.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameHeader.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameHeader.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameHello.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameHello.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameLinks.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameLinks.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameMeshOperation.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameMeshOperation.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameMirrorStatus.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameMirrorStatus.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameMonitoringPulse.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameMonitoringPulse.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameNetworkStatus.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameNetworkStatus.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FramePing.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FramePing.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FramePowerChange.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FramePowerChange.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameRCAction.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameRCAction.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameRCIr.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameRCIr.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameRCKey.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameRCKey.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameRCSettings.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameRCSettings.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameReboot.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameReboot.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameRoutingChange.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameRoutingChange.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSetInstaller.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSetInstaller.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSetName.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSetName.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSetRoute.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSetRoute.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSetupStatus.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSetupStatus.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSignalStatus.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSignalStatus.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSignalStatusNew.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSignalStatusNew.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSysTemperature.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSysTemperature.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSystemStatus.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameSystemStatus.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameTXRCAction.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameTXRCAction.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameTXRCKey.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameTXRCKey.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameTime.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameTime.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameTimeZone.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameTimeZone.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameTopology.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameTopology.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameTxIR.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameTxIR.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameUnitCommand.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameUnitCommand.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameUnitStatus.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameUnitStatus.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameUpgradeFPGA.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameUpgradeFPGA.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPAudio.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPAudio.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPBayMapping.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPBayMapping.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPDetectBays.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPDetectBays.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPLink.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPLink.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPMultiviewer.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPMultiviewer.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPOsdSync.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPOsdSync.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPPowerSave.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPPowerSave.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPPrivacy.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPPrivacy.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPSetMaster.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPSetMaster.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPSettingsAll.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPSettingsAll.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPSourceSwitch.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPSourceSwitch.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPSources.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPSources.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPStats.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPStats.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPTestcard.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPTestcard.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPTiling.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPTiling.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPVideoWall.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameV2IPVideoWall.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameVolume.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameVolume.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameVolumeDown.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameVolumeDown.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameVolumeSet.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameVolumeSet.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameVolumeUp.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/FrameVolumeUp.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/LinkConfig.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/LinkConfig.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/Multiviewer.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/Multiviewer.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/Svd.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/Svd.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/V2IPConfig.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/V2IPConfig.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/V2IPStats.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/V2IPStats.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/__init__.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/__init__.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/proto/svd.csv +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/py.typed +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/remote/Bay.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/remote/Bay.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/remote/ConnectionAsync.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/remote/ConnectionAsync.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/remote/Link.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/remote/Link.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/remote/OsdSync.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/remote/OsdSync.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/remote/State.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/remote/State.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/remote/V2IP.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/mx_remote/remote/V2IP.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/pyproject.toml +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/audio_ir.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/audiolock.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/baycontent.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/capture.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/complete.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/deprecated.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/downmix.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/e2e.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/entry.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/handlers_cover.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/hello.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/idempotent.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/lengths.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/merge.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/mesh.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/meshbays.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/newops.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/offline.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/offsets.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/osdmessage.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/osdsync.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/ping.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/privacy.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/proto_stamp.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/protogate.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/rcsettings.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/roundtrip.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/routes.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/sources.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/staterequest.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/stats.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/structs.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/txresult.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/uid.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/unknowns.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/v2ipptp.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/v2ipscaling.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/v2ipsettings.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/v2ipsubject.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/v2iptestcard.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/v2ipvlan.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.4.0}/tests/vdetails.py +0 -0
- {mx_remote-9.2.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/
|
|
@@ -288,6 +288,23 @@ How a source unit encrypts its streams, as it reports it.
|
|
|
288
288
|
:members:
|
|
289
289
|
```
|
|
290
290
|
|
|
291
|
+
## Application messages
|
|
292
|
+
|
|
293
|
+
Messages applications exchange over the mesh under their own vendor id.
|
|
294
|
+
|
|
295
|
+
```{eval-rst}
|
|
296
|
+
.. autoclass:: mx_remote.ThirdPartyMessage
|
|
297
|
+
:members:
|
|
298
|
+
|
|
299
|
+
.. autoclass:: mx_remote.ThirdPartyVendor
|
|
300
|
+
:members:
|
|
301
|
+
|
|
302
|
+
.. autoclass:: mx_remote.proto.Constants.ThirdPartyVendorFlag
|
|
303
|
+
:members:
|
|
304
|
+
|
|
305
|
+
.. autodata:: mx_remote.proto.Constants.THIRD_PARTY_PAYLOAD_MAX
|
|
306
|
+
```
|
|
307
|
+
|
|
291
308
|
## OneIP test card
|
|
292
309
|
|
|
293
310
|
A sink's test pattern, tone and lip-sync flash, and the ranges it accepts.
|
|
@@ -67,6 +67,7 @@ Available callback methods:
|
|
|
67
67
|
| `on_rc_type_changed` | remote control type changed |
|
|
68
68
|
| `on_amp_zone_settings_changed` | amplifier zone settings changed |
|
|
69
69
|
| `on_amp_dolby_settings_changed` | amplifier Dolby settings changed |
|
|
70
|
+
| `on_third_party_message` | another application sent a message under its vendor id |
|
|
70
71
|
|
|
71
72
|
You can also register per-device and per-bay callbacks:
|
|
72
73
|
|
|
@@ -85,6 +86,30 @@ device.unregister_callback(on_device_changed)
|
|
|
85
86
|
bay.unregister_callback(on_bay_changed)
|
|
86
87
|
```
|
|
87
88
|
|
|
89
|
+
## Application messages
|
|
90
|
+
|
|
91
|
+
Applications built on this library can message each other over the mesh. A
|
|
92
|
+
message carries a vendor id, then up to `THIRD_PARTY_PAYLOAD_MAX` (1444) bytes
|
|
93
|
+
that only the application reads. The vendor id is the vendor's IEEE OUI, or an
|
|
94
|
+
identifier it chose, such as three letters:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from mx_remote import ThirdPartyVendor
|
|
98
|
+
|
|
99
|
+
ODK = ThirdPartyVendor.private(b'ODK')
|
|
100
|
+
|
|
101
|
+
await mx.send_third_party(ODK, b'hello') # False when over 1444 bytes
|
|
102
|
+
|
|
103
|
+
class MyCallbacks(mx_remote.MxrCallbacks):
|
|
104
|
+
def on_third_party_message(self, dev, message):
|
|
105
|
+
if message.vendor.same_vendor(ODK):
|
|
106
|
+
print(f"{dev} says {message.payload!r}")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Every other client that has heard this one announce itself receives the
|
|
110
|
+
message; this client does not. No unit reads it, and nothing acknowledges it,
|
|
111
|
+
so an application that needs to know a message arrived has its peers answer.
|
|
112
|
+
|
|
88
113
|
---
|
|
89
114
|
|
|
90
115
|
[Documentation index](README.md) | [Project README](https://github.com/opdenkamp/mx-remote#readme)
|
|
@@ -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
|
|
@@ -2601,6 +2601,68 @@ class OneIPPrivacy:
|
|
|
2601
2601
|
def __str__(self) -> str:
|
|
2602
2602
|
return f"video {self.video}, audio {self.audio}, anc {self.anc}"
|
|
2603
2603
|
|
|
2604
|
+
@dataclass(frozen=True)
|
|
2605
|
+
class ThirdPartyVendor:
|
|
2606
|
+
"""
|
|
2607
|
+
Whose message a third-party message is: a flag byte, then three bytes
|
|
2608
|
+
naming the vendor.
|
|
2609
|
+
|
|
2610
|
+
The three bytes are the vendor's IEEE OUI, in the order it is written, or
|
|
2611
|
+
with ThirdPartyVendorFlag.NOT_OUI set an identifier of the vendor's own
|
|
2612
|
+
choosing, such as three ASCII letters. The flag keeps the two spaces apart,
|
|
2613
|
+
so a chosen identifier never collides with a registered OUI.
|
|
2614
|
+
"""
|
|
2615
|
+
flags: ThirdPartyVendorFlag
|
|
2616
|
+
"""The flag byte, at offset 0."""
|
|
2617
|
+
id: bytes
|
|
2618
|
+
"""The vendor's three identifying bytes, at offsets 1 to 3."""
|
|
2619
|
+
|
|
2620
|
+
def __post_init__(self) -> None:
|
|
2621
|
+
if (len(self.id) != 3):
|
|
2622
|
+
raise ValueError(f"a vendor id is 3 bytes, not {len(self.id)}")
|
|
2623
|
+
|
|
2624
|
+
@classmethod
|
|
2625
|
+
def oui(cls, oui:bytes) -> 'ThirdPartyVendor':
|
|
2626
|
+
"""A vendor named by its IEEE OUI."""
|
|
2627
|
+
return cls(flags=ThirdPartyVendorFlag(0), id=bytes(oui))
|
|
2628
|
+
|
|
2629
|
+
@classmethod
|
|
2630
|
+
def private(cls, id:bytes) -> 'ThirdPartyVendor':
|
|
2631
|
+
"""A vendor named by an identifier it chose, rather than an OUI."""
|
|
2632
|
+
return cls(flags=ThirdPartyVendorFlag.NOT_OUI, id=bytes(id))
|
|
2633
|
+
|
|
2634
|
+
def same_vendor(self, other:'ThirdPartyVendor') -> bool:
|
|
2635
|
+
"""Whether other names the same vendor.
|
|
2636
|
+
|
|
2637
|
+
Only ThirdPartyVendorFlag.NOT_OUI and the three identifying bytes say
|
|
2638
|
+
who the vendor is, so the other flag bits are left out."""
|
|
2639
|
+
return ((ThirdPartyVendorFlag.NOT_OUI in self.flags) == (ThirdPartyVendorFlag.NOT_OUI in other.flags)) \
|
|
2640
|
+
and (self.id == other.id)
|
|
2641
|
+
|
|
2642
|
+
def __str__(self) -> str:
|
|
2643
|
+
if (ThirdPartyVendorFlag.NOT_OUI in self.flags):
|
|
2644
|
+
return f"vendor {self.id!r}"
|
|
2645
|
+
return f"vendor OUI {self.id.hex(':')}"
|
|
2646
|
+
|
|
2647
|
+
@dataclass(frozen=True)
|
|
2648
|
+
class ThirdPartyMessage:
|
|
2649
|
+
"""
|
|
2650
|
+
A message an application sent to every device, under its own vendor id.
|
|
2651
|
+
|
|
2652
|
+
The library reads only the vendor id. What follows is the application's,
|
|
2653
|
+
and arrives exactly as it was sent.
|
|
2654
|
+
"""
|
|
2655
|
+
vendor: ThirdPartyVendor
|
|
2656
|
+
"""Which application's message this is. The library checks none: each
|
|
2657
|
+
application ignores every message whose vendor is not its own."""
|
|
2658
|
+
payload: bytes
|
|
2659
|
+
"""The application's bytes, at most THIRD_PARTY_PAYLOAD_MAX."""
|
|
2660
|
+
address: str|None
|
|
2661
|
+
"""Where the datagram came from."""
|
|
2662
|
+
|
|
2663
|
+
def __str__(self) -> str:
|
|
2664
|
+
return f"{self.vendor}: {len(self.payload)} bytes"
|
|
2665
|
+
|
|
2604
2666
|
@dataclass(frozen=True)
|
|
2605
2667
|
class OneIPTestTone:
|
|
2606
2668
|
"""A OneIP sink's test tone."""
|
|
@@ -3293,6 +3355,15 @@ class DeviceBase(ABC):
|
|
|
3293
3355
|
async def confirm_unit_network(self) -> bool:
|
|
3294
3356
|
'''keep the unit's pending IP configuration'''
|
|
3295
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
|
+
|
|
3296
3367
|
@property
|
|
3297
3368
|
@abstractmethod
|
|
3298
3369
|
def oneip_downmix(self) -> OneIPDownmix|None:
|
|
@@ -3678,6 +3749,18 @@ class DeviceBase(ABC):
|
|
|
3678
3749
|
async def request_oneip_sink_sdp(self) -> bool:
|
|
3679
3750
|
'''ask this OneIP output what it plays from a session description now'''
|
|
3680
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
|
+
|
|
3681
3764
|
@abstractmethod
|
|
3682
3765
|
async def set_oneip_test_pattern(self, pattern:OneIPTestPattern, colour:int=0) -> bool:
|
|
3683
3766
|
'''show a test pattern on this sink's output'''
|
|
@@ -4370,6 +4453,13 @@ class MxrCallbacks:
|
|
|
4370
4453
|
_LOGGER.debug(f"{device} dolby settings changed")
|
|
4371
4454
|
self.on_device_update(device)
|
|
4372
4455
|
|
|
4456
|
+
def on_third_party_message(self, dev:DeviceBase, message:ThirdPartyMessage) -> None:
|
|
4457
|
+
''' called when the application on 'dev' sent a message under its own
|
|
4458
|
+
vendor id. A message from a sender this client has not yet heard
|
|
4459
|
+
announce itself is dropped, as every frame is, and this client never
|
|
4460
|
+
receives its own. '''
|
|
4461
|
+
pass
|
|
4462
|
+
|
|
4373
4463
|
# The V2IP spelling of a name here, from before the rename to OneIP.
|
|
4374
4464
|
from .deprecated import alias_members as _alias_members, module_getattr as _module_getattr
|
|
4375
4465
|
# Installed through globals() so the generated stubs carry no __getattr__, which
|
|
@@ -930,6 +930,23 @@ class OneIPPrivacy:
|
|
|
930
930
|
audio: OneIPPrivacyStream
|
|
931
931
|
anc: OneIPPrivacyStream
|
|
932
932
|
|
|
933
|
+
@dataclass(frozen=True)
|
|
934
|
+
class ThirdPartyVendor:
|
|
935
|
+
flags: ThirdPartyVendorFlag
|
|
936
|
+
id: bytes
|
|
937
|
+
def __post_init__(self) -> None: ...
|
|
938
|
+
@classmethod
|
|
939
|
+
def oui(cls, oui: bytes) -> ThirdPartyVendor: ...
|
|
940
|
+
@classmethod
|
|
941
|
+
def private(cls, id: bytes) -> ThirdPartyVendor: ...
|
|
942
|
+
def same_vendor(self, other: ThirdPartyVendor) -> bool: ...
|
|
943
|
+
|
|
944
|
+
@dataclass(frozen=True)
|
|
945
|
+
class ThirdPartyMessage:
|
|
946
|
+
vendor: ThirdPartyVendor
|
|
947
|
+
payload: bytes
|
|
948
|
+
address: str | None
|
|
949
|
+
|
|
933
950
|
@dataclass(frozen=True)
|
|
934
951
|
class OneIPTestTone:
|
|
935
952
|
mode: OneIPToneMode | int = ...
|
|
@@ -1193,6 +1210,10 @@ class DeviceBase(ABC):
|
|
|
1193
1210
|
async def set_unit_network(self, network: UnitNetConfig) -> bool: ...
|
|
1194
1211
|
@abstractmethod
|
|
1195
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: ...
|
|
1196
1217
|
@property
|
|
1197
1218
|
@abstractmethod
|
|
1198
1219
|
def oneip_downmix(self) -> OneIPDownmix | None: ...
|
|
@@ -1376,6 +1397,11 @@ class DeviceBase(ABC):
|
|
|
1376
1397
|
async def set_oneip_sdp_screen(self, screen: int) -> bool: ...
|
|
1377
1398
|
@abstractmethod
|
|
1378
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: ...
|
|
1379
1405
|
@abstractmethod
|
|
1380
1406
|
async def set_oneip_test_pattern(self, pattern: OneIPTestPattern, colour: int = 0) -> bool: ...
|
|
1381
1407
|
@abstractmethod
|
|
@@ -1620,3 +1646,4 @@ class MxrCallbacks:
|
|
|
1620
1646
|
def on_rc_type_changed(self, bay: BayBase, rc_type: RCType | None) -> None: ...
|
|
1621
1647
|
def on_amp_zone_settings_changed(self, bay: BayBase, settings: AmpZoneSettings) -> None: ...
|
|
1622
1648
|
def on_amp_dolby_settings_changed(self, device: DeviceBase, settings: AmpDolbySettings) -> None: ...
|
|
1649
|
+
def on_third_party_message(self, dev: DeviceBase, message: ThirdPartyMessage) -> None: ...
|
|
@@ -123,6 +123,9 @@ MXR_OPCODE_VERSIONS: dict[int, int] = {
|
|
|
123
123
|
0x52: 0x2E, # V2IP_SINK_SDP
|
|
124
124
|
0x53: 0x2F, # V2IP_OSD_SYNC
|
|
125
125
|
0x54: 0x30, # V2IP_PRIVACY
|
|
126
|
+
# The lowest stamp, so that every receiver's ceiling passes it. No device
|
|
127
|
+
# decodes the payload, so there is no layout for a stamp to announce.
|
|
128
|
+
0x55: 0x01, # THIRD_PARTY
|
|
126
129
|
}
|
|
127
130
|
"""Per-opcode minimum compatible protocol version, mirroring the firmware's
|
|
128
131
|
own opcode table. Transmitters stamp the
|
|
@@ -681,6 +684,11 @@ class UnitFlag(IntFlag):
|
|
|
681
684
|
'''The boot count is reported.'''
|
|
682
685
|
FAN_SPEED = (1 << 15)
|
|
683
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".'''
|
|
684
692
|
|
|
685
693
|
class UnitCommandKind(IntEnum):
|
|
686
694
|
'''What a unit command asks of its unit.'''
|
|
@@ -692,6 +700,8 @@ class UnitCommandKind(IntEnum):
|
|
|
692
700
|
'''Change the IP configuration.'''
|
|
693
701
|
NET_CONFIRM = 3
|
|
694
702
|
'''Keep a pending IP configuration.'''
|
|
703
|
+
LOCATE = 4
|
|
704
|
+
'''Start or stop locating the unit.'''
|
|
695
705
|
|
|
696
706
|
class UnitCommandFlag(IntFlag):
|
|
697
707
|
'''The flags of a unit command.'''
|
|
@@ -700,6 +710,11 @@ class UnitCommandFlag(IntFlag):
|
|
|
700
710
|
already.'''
|
|
701
711
|
DHCP = (1 << 1)
|
|
702
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.'''
|
|
703
718
|
|
|
704
719
|
UNIT_MAX_TEMPERATURES = 6
|
|
705
720
|
"""The most temperature sensors a unit reports."""
|
|
@@ -822,6 +837,21 @@ ONEIP_VLAN_PORT_SFP = 0
|
|
|
822
837
|
ONEIP_VLAN_ID_MAX = 4094
|
|
823
838
|
"""The highest VLAN id: 0 means untagged, and 4095 is reserved by IEEE 802.1Q."""
|
|
824
839
|
|
|
840
|
+
class ThirdPartyVendorFlag(IntFlag):
|
|
841
|
+
'''The first byte of a third-party vendor id, ahead of its three
|
|
842
|
+
identifying bytes. Bits without a name are kept as they arrived.'''
|
|
843
|
+
NOT_OUI = (1 << 0)
|
|
844
|
+
'''The three bytes are an identifier the vendor chose, rather than its
|
|
845
|
+
IEEE OUI.'''
|
|
846
|
+
|
|
847
|
+
THIRD_PARTY_PAYLOAD_MAX = 1444
|
|
848
|
+
"""The largest payload a third-party message carries.
|
|
849
|
+
|
|
850
|
+
A frame this size fills one 1500-byte Ethernet frame exactly: 20 bytes of IPv4
|
|
851
|
+
header, 8 of UDP, the 24-byte frame header and the 4-byte vendor id leave 1444.
|
|
852
|
+
A larger send is refused rather than fragmented, because a lost fragment loses
|
|
853
|
+
the whole datagram and nothing on these paths retries."""
|
|
854
|
+
|
|
825
855
|
BAY_FEATURE_DOLBY_IN_POS = 24
|
|
826
856
|
|
|
827
857
|
class BayFeaturesMask(IntFlag):
|
|
@@ -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
|
|
|
@@ -340,6 +345,11 @@ class OneIPVlanFlag(IntFlag):
|
|
|
340
345
|
ONEIP_VLAN_PORTS: int
|
|
341
346
|
ONEIP_VLAN_PORT_SFP: int
|
|
342
347
|
ONEIP_VLAN_ID_MAX: int
|
|
348
|
+
|
|
349
|
+
class ThirdPartyVendorFlag(IntFlag):
|
|
350
|
+
NOT_OUI: Incomplete
|
|
351
|
+
|
|
352
|
+
THIRD_PARTY_PAYLOAD_MAX: int
|
|
343
353
|
BAY_FEATURE_DOLBY_IN_POS: int
|
|
344
354
|
|
|
345
355
|
class BayFeaturesMask(IntFlag):
|
|
@@ -249,6 +249,9 @@ def _mxr_frame_factory(hdr:FrameHeader, timestamp:float) -> FrameBase|None:
|
|
|
249
249
|
if hdr.opcode == 0x54:
|
|
250
250
|
from .FrameV2IPPrivacy import FrameV2IPPrivacy
|
|
251
251
|
return FrameV2IPPrivacy(header=hdr, timestamp=timestamp)
|
|
252
|
+
if hdr.opcode == 0x55:
|
|
253
|
+
from .FrameThirdParty import FrameThirdParty
|
|
254
|
+
return FrameThirdParty(header=hdr, timestamp=timestamp)
|
|
252
255
|
if hdr.opcode == 0x50:
|
|
253
256
|
from .FrameUnitCommand import FrameUnitCommand
|
|
254
257
|
return FrameUnitCommand(header=hdr, timestamp=timestamp)
|
|
@@ -0,0 +1,50 @@
|
|
|
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 THIRD_PARTY (opcode 0x55): messages applications
|
|
8
|
+
exchange over the mesh.'''
|
|
9
|
+
|
|
10
|
+
from functools import cached_property
|
|
11
|
+
from ..compat import override
|
|
12
|
+
from .Constants import ThirdPartyVendorFlag
|
|
13
|
+
from .FrameBase import FrameBase
|
|
14
|
+
from ..Interface import DeviceRegistry, ThirdPartyMessage, ThirdPartyVendor
|
|
15
|
+
|
|
16
|
+
# The flag byte at 0 and the three identifying bytes at 1, then the
|
|
17
|
+
# application's bytes to the end of the frame.
|
|
18
|
+
VENDOR_SIZE = 4
|
|
19
|
+
|
|
20
|
+
class FrameThirdParty(FrameBase):
|
|
21
|
+
'''An application's own message, under its vendor id.'''
|
|
22
|
+
@staticmethod
|
|
23
|
+
def construct(mxr:DeviceRegistry, vendor:ThirdPartyVendor, payload:bytes) -> FrameBase|None:
|
|
24
|
+
'''A message to every device: the vendor's flag byte and three
|
|
25
|
+
identifying bytes, then payload as it was given.'''
|
|
26
|
+
data = bytes([int(vendor.flags)]) + vendor.id + bytes(payload)
|
|
27
|
+
return FrameBase.construct_base(mxr=mxr, opcode=0x55, payload=data)
|
|
28
|
+
|
|
29
|
+
@cached_property
|
|
30
|
+
def message(self) -> ThirdPartyMessage|None:
|
|
31
|
+
'''The message, whatever its vendor: telling them apart is the
|
|
32
|
+
application's. None for a payload too short for the vendor id.
|
|
33
|
+
Everything after the id is the application's, so the length is a
|
|
34
|
+
minimum and nothing past it is read.'''
|
|
35
|
+
p = self.payload or b''
|
|
36
|
+
if (len(p) < VENDOR_SIZE):
|
|
37
|
+
return None
|
|
38
|
+
vendor = ThirdPartyVendor(flags=ThirdPartyVendorFlag(p[0]), id=bytes(p[1:VENDOR_SIZE]))
|
|
39
|
+
return ThirdPartyMessage(vendor=vendor, payload=bytes(p[VENDOR_SIZE:]), address=self.address)
|
|
40
|
+
|
|
41
|
+
@override
|
|
42
|
+
def process(self) -> None:
|
|
43
|
+
'''Hand the message to the callbacks, as sent by the device it came from.'''
|
|
44
|
+
if ((message := self.message) is not None) and ((dev := self.remote_device) is not None):
|
|
45
|
+
dev.callbacks.on_third_party_message(dev, message)
|
|
46
|
+
|
|
47
|
+
def __str__(self) -> str:
|
|
48
|
+
if (self.message is None):
|
|
49
|
+
return "third-party message <dropped>"
|
|
50
|
+
return f"third-party message from {self.message}"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from ..Interface import DeviceRegistry as DeviceRegistry, ThirdPartyMessage as ThirdPartyMessage, ThirdPartyVendor as ThirdPartyVendor
|
|
2
|
+
from ..compat import override as override
|
|
3
|
+
from .Constants import ThirdPartyVendorFlag as ThirdPartyVendorFlag
|
|
4
|
+
from .FrameBase import FrameBase as FrameBase
|
|
5
|
+
from functools import cached_property
|
|
6
|
+
|
|
7
|
+
VENDOR_SIZE: int
|
|
8
|
+
|
|
9
|
+
class FrameThirdParty(FrameBase):
|
|
10
|
+
@staticmethod
|
|
11
|
+
def construct(mxr: DeviceRegistry, vendor: ThirdPartyVendor, payload: bytes) -> FrameBase | None: ...
|
|
12
|
+
@cached_property
|
|
13
|
+
def message(self) -> ThirdPartyMessage | None: ...
|
|
14
|
+
@override
|
|
15
|
+
def process(self) -> None: ...
|
|
@@ -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: ...
|