mx-remote 8.1.0__tar.gz → 9.0.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-8.1.0 → mx_remote-9.0.0}/PKG-INFO +1 -1
- {mx_remote-8.1.0 → mx_remote-9.0.0}/docs/api.md +19 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/docs/oneip.md +39 -5
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/Interface.py +87 -13
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/Interface.pyi +21 -1
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/const.py +1 -1
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/Constants.py +34 -3
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/Constants.pyi +15 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.py +25 -8
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPOsdSync.py +45 -12
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPOsdSync.pyi +10 -2
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/remote/Device.py +81 -8
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/remote/Device.pyi +9 -2
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/remote/OsdSync.py +1 -11
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/remote/Remote.py +8 -7
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/remote/Remote.pyi +1 -1
- mx_remote-9.0.0/tests/osdmessage.py +114 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/osdsync.py +12 -13
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/run.py +1 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/v2ipsettings.py +49 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/.gitignore +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/AGENTS.md +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/CLAUDE.md +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/LICENSE +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/README.md +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/docs/README.md +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/docs/_static/brand.css +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/docs/_static/favicon.svg +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/docs/_static/logo-dark.svg +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/docs/_static/logo.svg +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/docs/audio.md +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/docs/callbacks.md +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/docs/conf.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/docs/configuration.md +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/docs/devices-and-bays.md +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/docs/diagnostics.md +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/docs/index.md +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/docs/multiviewer.md +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/docs/requirements.txt +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/docs/routing.md +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/hatch_build.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/Uid.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/Uid.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/__init__.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/__init__.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/compat.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/compat.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/const.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/deprecated.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/deprecated.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/main.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/main.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/BayConfig.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/BayConfig.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/Data.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/Data.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/Factory.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/Factory.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameAmpDolbySettings.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameAmpDolbySettings.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameAmpZoneSettings.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameAmpZoneSettings.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameAudioClip.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameAudioClip.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameAudioSetRoute.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameAudioSetRoute.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameBase.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameBase.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameBayConfig.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameBayConfig.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameBayConfigSecondary.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameBayConfigSecondary.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameBayContent.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameBayContent.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameBayHide.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameBayHide.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameBayStatus.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameBayStatus.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameConnectStatus.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameConnectStatus.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameDebug.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameDebug.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameDiscover.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameDiscover.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameEDID.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameEDID.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameEDIDProfile.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameEDIDProfile.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameFactoryReset.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameFactoryReset.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameFilterStatus.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameFilterStatus.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameFirmwareVersion.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameFirmwareVersion.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameHeader.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameHeader.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameHello.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameHello.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameLinks.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameLinks.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameMeshOperation.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameMeshOperation.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameMirrorStatus.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameMirrorStatus.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameMonitoringPulse.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameMonitoringPulse.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameNetworkStatus.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameNetworkStatus.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FramePing.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FramePing.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FramePowerChange.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FramePowerChange.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameRCAction.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameRCAction.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameRCIr.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameRCIr.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameRCKey.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameRCKey.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameRCSettings.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameRCSettings.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameReboot.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameReboot.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameRoutingChange.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameRoutingChange.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameSetInstaller.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameSetInstaller.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameSetName.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameSetName.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameSetRoute.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameSetRoute.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameSetupStatus.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameSetupStatus.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameSignalStatus.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameSignalStatus.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameSignalStatusNew.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameSignalStatusNew.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameSysTemperature.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameSysTemperature.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameSystemStatus.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameSystemStatus.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameTXRCAction.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameTXRCAction.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameTXRCKey.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameTXRCKey.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameTime.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameTime.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameTimeZone.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameTimeZone.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameTopology.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameTopology.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameTxIR.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameTxIR.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameUnitCommand.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameUnitCommand.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameUnitStatus.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameUnitStatus.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameUpgradeFPGA.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameUpgradeFPGA.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPAudio.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPAudio.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPBayMapping.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPBayMapping.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPDetectBays.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPDetectBays.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPLink.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPLink.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPMultiviewer.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPMultiviewer.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPPowerSave.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPPowerSave.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPPrivacy.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPPrivacy.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPSetMaster.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPSetMaster.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPSettingsAll.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPSettingsAll.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPSinkSdp.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPSinkSdp.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPSourceSwitch.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPSourceSwitch.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPSources.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPSources.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPStats.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPStats.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPTestcard.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPTestcard.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPTiling.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPTiling.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPVideoWall.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPVideoWall.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameVolume.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameVolume.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameVolumeDown.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameVolumeDown.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameVolumeSet.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameVolumeSet.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameVolumeUp.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/FrameVolumeUp.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/LinkConfig.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/LinkConfig.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/Multiviewer.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/Multiviewer.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/Svd.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/Svd.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/V2IPConfig.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/V2IPConfig.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/V2IPStats.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/V2IPStats.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/__init__.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/__init__.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/proto/svd.csv +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/py.typed +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/remote/Bay.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/remote/Bay.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/remote/ConnectionAsync.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/remote/ConnectionAsync.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/remote/Link.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/remote/Link.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/remote/OsdSync.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/remote/State.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/remote/State.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/remote/V2IP.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/mx_remote/remote/V2IP.pyi +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/pyproject.toml +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/audio_ir.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/audiolock.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/baycontent.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/capture.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/complete.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/deprecated.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/e2e.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/entry.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/handlers.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/handlers_cover.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/hello.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/idempotent.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/lengths.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/merge.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/mesh.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/meshbays.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/newops.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/offline.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/offsets.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/ping.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/privacy.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/proto_stamp.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/protogate.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/rcsettings.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/roundtrip.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/routes.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/smoke.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/sources.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/staterequest.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/stats.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/structs.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/txresult.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/unit.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/unknowns.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/v2ipptp.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/v2ipscaling.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/v2ipsdp.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/v2ipsubject.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/v2iptestcard.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/v2ipvlan.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/vdetails.py +0 -0
- {mx_remote-8.1.0 → mx_remote-9.0.0}/tests/wirefix.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: mx_remote
|
|
3
|
-
Version:
|
|
3
|
+
Version: 9.0.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/
|
|
@@ -85,6 +85,23 @@ place, backgrounds and logo images.
|
|
|
85
85
|
.. autodata:: mx_remote.proto.Constants.OSD_SYNC_PERCENT_MAX
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
+
## Overlay messages
|
|
89
|
+
|
|
90
|
+
The message on a unit's output, which the overlay sync opcode carries too.
|
|
91
|
+
|
|
92
|
+
```{eval-rst}
|
|
93
|
+
.. autoclass:: mx_remote.OsdMessage
|
|
94
|
+
:members:
|
|
95
|
+
|
|
96
|
+
.. autoclass:: mx_remote.proto.Constants.OsdMessageFlag
|
|
97
|
+
:members:
|
|
98
|
+
|
|
99
|
+
.. autoclass:: mx_remote.proto.Constants.OsdMessagePriority
|
|
100
|
+
:members:
|
|
101
|
+
|
|
102
|
+
.. autodata:: mx_remote.proto.Constants.OSD_MESSAGE_TEXT_MAX
|
|
103
|
+
```
|
|
104
|
+
|
|
88
105
|
## Device identity
|
|
89
106
|
|
|
90
107
|
```{eval-rst}
|
|
@@ -180,6 +197,8 @@ state only the device reports.
|
|
|
180
197
|
|
|
181
198
|
.. autodata:: mx_remote.proto.Constants.ONEIP_STREAM_TTL_DEFAULT
|
|
182
199
|
|
|
200
|
+
.. autodata:: mx_remote.proto.Constants.ONEIP_SINK_ROTATION_MAX
|
|
201
|
+
|
|
183
202
|
.. autodata:: mx_remote.proto.Constants.ONEIP_PTP_DOMAIN_MAX
|
|
184
203
|
```
|
|
185
204
|
|
|
@@ -288,10 +288,23 @@ same block, behind `SAP_EXPIRE` and `SAP_INTERVAL`, and are set the same way;
|
|
|
288
288
|
`SAP_LISTEN` and `SAP_ANNOUNCE` are switches like the other on/off settings.
|
|
289
289
|
A device with an encoder reports its streams' TTL there too, behind
|
|
290
290
|
`STREAM_TTL`; a TTL of 0 is refused, as a device changes nothing for one.
|
|
291
|
+
A device with an HDMI output reports the angle its output is turned by,
|
|
292
|
+
clockwise, behind `SINK_ROTATION`, with the step its video processor turns by.
|
|
293
|
+
It takes 0 or a multiple of that step up to `ONEIP_SINK_ROTATION_MAX`, so any
|
|
294
|
+
other angle is refused before it is sent, and the step is never written.
|
|
295
|
+
`set_all_oneip_device_settings` can only check the range; each device judges
|
|
296
|
+
the step.
|
|
297
|
+
|
|
298
|
+
```python
|
|
299
|
+
m = settings.ptp_messages
|
|
300
|
+
print(m.sink_rotation, m.sink_rotation_step) # e.g. 0 and 90
|
|
301
|
+
await device.set_oneip_ptp_messages(OneIPDeviceSetting.SINK_ROTATION,
|
|
302
|
+
OneIPPtpMessages(sink_rotation=180))
|
|
303
|
+
```
|
|
291
304
|
|
|
292
305
|
Only the values behind the bits named are sent. A DSCP above `ONEIP_DSCP_MAX` is
|
|
293
306
|
refused, and not cached from another controller's write. A write carrying one of
|
|
294
|
-
these runs on to 224 bytes, or 232 with the TTL; every other write keeps the 176
|
|
307
|
+
these runs on to 224 bytes, or 232 with the TTL or the rotation; every other write keeps the 176
|
|
295
308
|
every device knows.
|
|
296
309
|
|
|
297
310
|
What the device's clock is actually doing is its PTP state, which only the
|
|
@@ -412,13 +425,34 @@ await mx.clear_osd_sync() # stop answering for it
|
|
|
412
425
|
Each unit asks for the settings it lacks and takes only those, so this client
|
|
413
426
|
holds the content and answers until it is replaced or cleared, announcing
|
|
414
427
|
itself meanwhile as the mesh's overlay sync manager: a unit waking from power
|
|
415
|
-
save then takes this content rather than the controller's.
|
|
416
|
-
still answers a unit that asks for settings without naming whose sync they are
|
|
417
|
-
from, as an older overlay module does, with its own sync, which replaces this
|
|
418
|
-
client's on that unit. Logo images are
|
|
428
|
+
save then takes this content rather than the controller's. Logo images are
|
|
419
429
|
served over HTTP from this client, on `logo_port` or a free port. Content a unit
|
|
420
430
|
would refuse is refused before anything is sent; see `send_osd_sync`.
|
|
421
431
|
|
|
432
|
+
### Overlay messages
|
|
433
|
+
|
|
434
|
+
A unit shows a message on its output, over the picture, until it restarts. The
|
|
435
|
+
text is markup of at most `OSD_MESSAGE_TEXT_MAX` bytes with no control
|
|
436
|
+
character, shown with a logo: 0 the built-in one, -1 none, 1 to 5 a slot. Its
|
|
437
|
+
priority places it against the unit's own messages, such as Locate Device.
|
|
438
|
+
|
|
439
|
+
```python
|
|
440
|
+
from mx_remote.proto.Constants import OsdMessagePriority
|
|
441
|
+
|
|
442
|
+
await sink.show_osd_message('Back at <b>3</b>', logo=-1, priority=OsdMessagePriority.TOP)
|
|
443
|
+
await sink.request_osd_message() # the unit answers with what it shows
|
|
444
|
+
await sink.clear_osd_message()
|
|
445
|
+
|
|
446
|
+
print(sink.osd_message) # None until the unit has answered
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
A unit takes a message only from management, which this client announces
|
|
450
|
+
itself as, and answers every write and request with the message it then has;
|
|
451
|
+
`on_osd_message_changed` reports a change. The answer carries
|
|
452
|
+
`OsdMessageFlag.REFUSED` when the unit did not take the write, as when its
|
|
453
|
+
video processor has no message layer. A text, logo or priority a unit would
|
|
454
|
+
refuse is refused before it is sent, as is a device that is not a OneIP output.
|
|
455
|
+
|
|
422
456
|
### Stream encryption
|
|
423
457
|
|
|
424
458
|
A source unit from protocol 0x30 encrypts its video, audio and ancillary
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
######################################################
|
|
7
7
|
|
|
8
8
|
from abc import ABC, abstractmethod
|
|
9
|
-
from dataclasses import dataclass
|
|
9
|
+
from dataclasses import dataclass, replace
|
|
10
10
|
from datetime import datetime, timezone
|
|
11
11
|
from enum import IntEnum
|
|
12
12
|
import time
|
|
@@ -2215,8 +2215,8 @@ def _wire_enum(cls:Any, value:int) -> Any:
|
|
|
2215
2215
|
@dataclass(frozen=True)
|
|
2216
2216
|
class OneIPPtpMessages:
|
|
2217
2217
|
"""
|
|
2218
|
-
How a OneIP device paces its PTP messages, its SAP timings
|
|
2219
|
-
TTL, which a frame carries in the same block. Each value is behind its own bit of
|
|
2218
|
+
How a OneIP device paces its PTP messages, its SAP timings, its streams'
|
|
2219
|
+
TTL and its output's rotation, which a frame carries in the same block. Each value is behind its own bit of
|
|
2220
2220
|
OneIPDeviceSettings.valid, one of ONEIP_DEVICE_SETTINGS_BLOCK.
|
|
2221
2221
|
|
|
2222
2222
|
Intervals are base-2 logarithms of seconds, so -3 is 125 ms and 1 is 2 s.
|
|
@@ -2241,6 +2241,19 @@ class OneIPPtpMessages:
|
|
|
2241
2241
|
"""The TTL of the device's streams, 1 to 255, behind STREAM_TTL. A device
|
|
2242
2242
|
whose video processor cannot change it keeps the setting and sends
|
|
2243
2243
|
ONEIP_STREAM_TTL_DEFAULT."""
|
|
2244
|
+
sink_rotation: int = 0
|
|
2245
|
+
"""The degrees the device's HDMI output is turned by, clockwise, at most
|
|
2246
|
+
ONEIP_SINK_ROTATION_MAX, behind SINK_ROTATION."""
|
|
2247
|
+
sink_rotation_step: int = 0
|
|
2248
|
+
"""The step the output turns by: the device takes only an angle that is a
|
|
2249
|
+
multiple of it, and 0 alone when it is 0. Only the device reports it,
|
|
2250
|
+
behind the same bit."""
|
|
2251
|
+
|
|
2252
|
+
def takes_sink_rotation(self, degrees:int) -> bool:
|
|
2253
|
+
"""Whether a device reporting this step takes degrees as its output's
|
|
2254
|
+
rotation."""
|
|
2255
|
+
return (degrees == 0) or ((0 < degrees <= ONEIP_SINK_ROTATION_MAX) and (self.sink_rotation_step != 0)
|
|
2256
|
+
and (degrees % self.sink_rotation_step == 0))
|
|
2244
2257
|
|
|
2245
2258
|
def merge(self, other:'OneIPPtpMessages', which:OneIPDeviceSetting) -> 'OneIPPtpMessages':
|
|
2246
2259
|
"""The values behind the bits of which copied from other, the rest kept."""
|
|
@@ -2253,7 +2266,9 @@ class OneIPPtpMessages:
|
|
|
2253
2266
|
dscp=(other if (S.PTP_DSCP in which) else self).dscp,
|
|
2254
2267
|
sap_expire_min=(other if (S.SAP_EXPIRE in which) else self).sap_expire_min,
|
|
2255
2268
|
sap_interval_s=(other if (S.SAP_INTERVAL in which) else self).sap_interval_s,
|
|
2256
|
-
stream_ttl=(other if (S.STREAM_TTL in which) else self).stream_ttl
|
|
2269
|
+
stream_ttl=(other if (S.STREAM_TTL in which) else self).stream_ttl,
|
|
2270
|
+
sink_rotation=(other if (S.SINK_ROTATION in which) else self).sink_rotation,
|
|
2271
|
+
sink_rotation_step=(other if (S.SINK_ROTATION in which) else self).sink_rotation_step)
|
|
2257
2272
|
|
|
2258
2273
|
def describe(self, valid:OneIPDeviceSetting) -> list[str]:
|
|
2259
2274
|
"""The values behind the bits of valid, one "name=value" each."""
|
|
@@ -2266,7 +2281,8 @@ class OneIPPtpMessages:
|
|
|
2266
2281
|
(S.PTP_DSCP, 'ptp_dscp', self.dscp),
|
|
2267
2282
|
(S.SAP_EXPIRE, 'sap_expire_min', self.sap_expire_min),
|
|
2268
2283
|
(S.SAP_INTERVAL, 'sap_interval_s', self.sap_interval_s),
|
|
2269
|
-
(S.STREAM_TTL, 'stream_ttl', self.stream_ttl)
|
|
2284
|
+
(S.STREAM_TTL, 'stream_ttl', self.stream_ttl),
|
|
2285
|
+
(S.SINK_ROTATION, 'sink_rotation', self.sink_rotation))
|
|
2270
2286
|
if (bit in valid)]
|
|
2271
2287
|
|
|
2272
2288
|
@dataclass(frozen=True)
|
|
@@ -2317,8 +2333,7 @@ class OsdSyncRequest:
|
|
|
2317
2333
|
missing: bytes
|
|
2318
2334
|
"""The settings it lacks, bit i standing for setting i, 16 bytes."""
|
|
2319
2335
|
source: MxrDeviceUid
|
|
2320
|
-
"""Who sent the sync it asks about. Empty when it asks for a new sync
|
|
2321
|
-
from a unit whose module does not name the source."""
|
|
2336
|
+
"""Who sent the sync it asks about. Empty when it asks for a new sync."""
|
|
2322
2337
|
|
|
2323
2338
|
def is_missing(self, index:int) -> bool:
|
|
2324
2339
|
"""Whether the unit lacks setting index."""
|
|
@@ -2394,6 +2409,28 @@ class OsdSyncReport:
|
|
|
2394
2409
|
reason: str
|
|
2395
2410
|
"""Why it failed or refused any."""
|
|
2396
2411
|
|
|
2412
|
+
@dataclass(frozen=True)
|
|
2413
|
+
class OsdMessage:
|
|
2414
|
+
"""
|
|
2415
|
+
The message on a unit's output, as the unit last reported it. The unit
|
|
2416
|
+
holds it until it restarts.
|
|
2417
|
+
"""
|
|
2418
|
+
flags: OsdMessageFlag
|
|
2419
|
+
"""Whether a message is set and shown, whether the unit can show one, and
|
|
2420
|
+
whether it refused the write it answers. Without SET the other fields are
|
|
2421
|
+
empty."""
|
|
2422
|
+
logo: int
|
|
2423
|
+
"""The logo shown with it: 0 the built-in logo, -1 none, 1 to 5 a slot."""
|
|
2424
|
+
priority: OsdMessagePriority|int
|
|
2425
|
+
"""Where it stands against the unit's own messages."""
|
|
2426
|
+
text: str
|
|
2427
|
+
"""Its markup."""
|
|
2428
|
+
|
|
2429
|
+
def __str__(self) -> str:
|
|
2430
|
+
if (OsdMessageFlag.SET not in self.flags):
|
|
2431
|
+
return f"none{' (refused)' if (OsdMessageFlag.REFUSED in self.flags) else ''}"
|
|
2432
|
+
return f"'{self.text}' logo {self.logo} {self.flags!r}"
|
|
2433
|
+
|
|
2397
2434
|
@dataclass(frozen=True)
|
|
2398
2435
|
class OsdSyncEntry:
|
|
2399
2436
|
"""One setting of an overlay sync this client sends."""
|
|
@@ -2767,16 +2804,25 @@ class OneIPDeviceSettings:
|
|
|
2767
2804
|
else previous._power_save_level),
|
|
2768
2805
|
ptp_messages=previous._ptp_messages.merge(self._ptp_messages, valid))
|
|
2769
2806
|
|
|
2770
|
-
def as_applied_to(self, reported:OneIPDeviceSetting) -> 'OneIPDeviceSettings':
|
|
2807
|
+
def as_applied_to(self, reported:'OneIPDeviceSettings|OneIPDeviceSetting|None') -> 'OneIPDeviceSettings':
|
|
2771
2808
|
"""
|
|
2772
|
-
This block limited to what a device takes from a
|
|
2809
|
+
This block limited to what a device that reported reported takes from a
|
|
2810
|
+
write about it.
|
|
2773
2811
|
|
|
2774
2812
|
A device applies a setting only if it has it, a profile, PTP mode, PTP
|
|
2775
2813
|
domain, power save level or PTP DSCP only within its range, a stream
|
|
2776
|
-
TTL only when it is not 0,
|
|
2777
|
-
itself.
|
|
2814
|
+
TTL only when it is not 0, a rotation only by its own step, and never
|
|
2815
|
+
what only it can report about itself. The step stays the device's.
|
|
2816
|
+
|
|
2817
|
+
reported may be only the bits the device reported, which says nothing
|
|
2818
|
+
of its step, so a rotation other than 0 is then left out.
|
|
2778
2819
|
"""
|
|
2779
|
-
|
|
2820
|
+
if (reported is None):
|
|
2821
|
+
reported = OneIPDeviceSettings()
|
|
2822
|
+
elif not isinstance(reported, OneIPDeviceSettings):
|
|
2823
|
+
reported = OneIPDeviceSettings(valid=OneIPDeviceSetting(reported))
|
|
2824
|
+
own = reported._ptp_messages
|
|
2825
|
+
valid = (self._valid & reported._valid) & ~ONEIP_DEVICE_SETTINGS_REPORTED_ONLY
|
|
2780
2826
|
if not (0 <= self._ir_profile < ONEIP_IR_PROFILE_MAX):
|
|
2781
2827
|
valid &= ~OneIPDeviceSetting.IR_PROFILE
|
|
2782
2828
|
if not (ONEIP_IR_PROFILE_NOT_SET <= self._ir_profile_sink < ONEIP_IR_PROFILE_MAX):
|
|
@@ -2791,13 +2837,16 @@ class OneIPDeviceSettings:
|
|
|
2791
2837
|
valid &= ~OneIPDeviceSetting.PTP_DSCP
|
|
2792
2838
|
if (self._ptp_messages.stream_ttl == 0):
|
|
2793
2839
|
valid &= ~OneIPDeviceSetting.STREAM_TTL
|
|
2840
|
+
if not own.takes_sink_rotation(self._ptp_messages.sink_rotation):
|
|
2841
|
+
valid &= ~OneIPDeviceSetting.SINK_ROTATION
|
|
2794
2842
|
return OneIPDeviceSettings(valid=valid, flags=self._flags, ir_profiles=self._ir_profiles,
|
|
2795
2843
|
ir_profile=self._ir_profile, ir_profile_sink=self._ir_profile_sink,
|
|
2796
2844
|
auto_power_save=self._auto_power_save, power_save=self._power_save,
|
|
2797
2845
|
ptp_mode=self._ptp_mode, ptp_domain=self._ptp_domain,
|
|
2798
2846
|
ptp_priority1=self._ptp_priority1,
|
|
2799
2847
|
power_save_level=self._power_save_level,
|
|
2800
|
-
ptp_messages=self._ptp_messages
|
|
2848
|
+
ptp_messages=replace(self._ptp_messages,
|
|
2849
|
+
sink_rotation_step=own.sink_rotation_step))
|
|
2801
2850
|
|
|
2802
2851
|
def __eq__(self, other:Any) -> bool:
|
|
2803
2852
|
if not isinstance(other, OneIPDeviceSettings):
|
|
@@ -3505,6 +3554,26 @@ class DeviceBase(ABC):
|
|
|
3505
3554
|
def osd_sync_report(self) -> OsdSyncReport|None:
|
|
3506
3555
|
'''This unit's last report on taking an overlay sync, None until it has.'''
|
|
3507
3556
|
|
|
3557
|
+
@property
|
|
3558
|
+
@abstractmethod
|
|
3559
|
+
def osd_message(self) -> OsdMessage|None:
|
|
3560
|
+
'''The message on this unit's output, as it last reported it to this
|
|
3561
|
+
client, None until it has. A unit reports it only when asked, or when
|
|
3562
|
+
this client writes it.'''
|
|
3563
|
+
|
|
3564
|
+
@abstractmethod
|
|
3565
|
+
async def request_osd_message(self) -> bool:
|
|
3566
|
+
'''ask this unit for the message on its output'''
|
|
3567
|
+
|
|
3568
|
+
@abstractmethod
|
|
3569
|
+
async def show_osd_message(self, text:str, logo:int=-1,
|
|
3570
|
+
priority:OsdMessagePriority=OsdMessagePriority.NORMAL) -> bool:
|
|
3571
|
+
'''show a message on this unit's output, in place of any it has'''
|
|
3572
|
+
|
|
3573
|
+
@abstractmethod
|
|
3574
|
+
async def clear_osd_message(self) -> bool:
|
|
3575
|
+
'''take the message down from this unit's output'''
|
|
3576
|
+
|
|
3508
3577
|
@property
|
|
3509
3578
|
@abstractmethod
|
|
3510
3579
|
def oneip_sink_sdp(self) -> OneIPSinkSdp|None:
|
|
@@ -4120,6 +4189,11 @@ class MxrCallbacks:
|
|
|
4120
4189
|
_LOGGER.debug(f"{dev} overlay sync {report.seq}: {report.state!r} {report.reason}")
|
|
4121
4190
|
self.on_device_update(dev)
|
|
4122
4191
|
|
|
4192
|
+
def on_osd_message_changed(self, dev:DeviceBase, message:OsdMessage) -> None:
|
|
4193
|
+
''' called when unit 'dev' reported something different about the message on its output '''
|
|
4194
|
+
_LOGGER.debug(f"{dev} overlay message: {message}")
|
|
4195
|
+
self.on_device_update(dev)
|
|
4196
|
+
|
|
4123
4197
|
def on_osd_sync_requested(self, dev:DeviceBase, request:OsdSyncRequest) -> None:
|
|
4124
4198
|
''' called when 'dev' asked for an overlay sync, or for settings of one '''
|
|
4125
4199
|
pass
|
|
@@ -787,6 +787,9 @@ class OneIPPtpMessages:
|
|
|
787
787
|
sap_expire_min: int = ...
|
|
788
788
|
sap_interval_s: int = ...
|
|
789
789
|
stream_ttl: int = ...
|
|
790
|
+
sink_rotation: int = ...
|
|
791
|
+
sink_rotation_step: int = ...
|
|
792
|
+
def takes_sink_rotation(self, degrees: int) -> bool: ...
|
|
790
793
|
def merge(self, other: OneIPPtpMessages, which: OneIPDeviceSetting) -> OneIPPtpMessages: ...
|
|
791
794
|
def describe(self, valid: OneIPDeviceSetting) -> list[str]: ...
|
|
792
795
|
|
|
@@ -853,6 +856,13 @@ class OsdSyncReport:
|
|
|
853
856
|
refused: int
|
|
854
857
|
reason: str
|
|
855
858
|
|
|
859
|
+
@dataclass(frozen=True)
|
|
860
|
+
class OsdMessage:
|
|
861
|
+
flags: OsdMessageFlag
|
|
862
|
+
logo: int
|
|
863
|
+
priority: OsdMessagePriority | int
|
|
864
|
+
text: str
|
|
865
|
+
|
|
856
866
|
@dataclass(frozen=True)
|
|
857
867
|
class OsdSyncEntry:
|
|
858
868
|
value: OsdSyncValue
|
|
@@ -968,7 +978,7 @@ class OneIPDeviceSettings:
|
|
|
968
978
|
@property
|
|
969
979
|
def ptp_messages(self) -> OneIPPtpMessages | None: ...
|
|
970
980
|
def merge(self, previous: OneIPDeviceSettings | None) -> OneIPDeviceSettings: ...
|
|
971
|
-
def as_applied_to(self, reported: OneIPDeviceSetting) -> OneIPDeviceSettings: ...
|
|
981
|
+
def as_applied_to(self, reported: OneIPDeviceSettings | OneIPDeviceSetting | None) -> OneIPDeviceSettings: ...
|
|
972
982
|
def __eq__(self, other: Any) -> bool: ...
|
|
973
983
|
def __ne__(self, other: Any) -> bool: ...
|
|
974
984
|
|
|
@@ -1324,6 +1334,15 @@ class DeviceBase(ABC):
|
|
|
1324
1334
|
def osd_sync_report(self) -> OsdSyncReport | None: ...
|
|
1325
1335
|
@property
|
|
1326
1336
|
@abstractmethod
|
|
1337
|
+
def osd_message(self) -> OsdMessage | None: ...
|
|
1338
|
+
@abstractmethod
|
|
1339
|
+
async def request_osd_message(self) -> bool: ...
|
|
1340
|
+
@abstractmethod
|
|
1341
|
+
async def show_osd_message(self, text: str, logo: int = -1, priority: OsdMessagePriority = ...) -> bool: ...
|
|
1342
|
+
@abstractmethod
|
|
1343
|
+
async def clear_osd_message(self) -> bool: ...
|
|
1344
|
+
@property
|
|
1345
|
+
@abstractmethod
|
|
1327
1346
|
def oneip_sink_sdp(self) -> OneIPSinkSdp | None: ...
|
|
1328
1347
|
@property
|
|
1329
1348
|
@abstractmethod
|
|
@@ -1561,6 +1580,7 @@ class MxrCallbacks:
|
|
|
1561
1580
|
def on_status_hdbt_connected_changed(self, bay: BayBase, val: bool) -> None: ...
|
|
1562
1581
|
def on_osd_sync(self, dev: DeviceBase, sync: OsdSync) -> None: ...
|
|
1563
1582
|
def on_osd_sync_report(self, dev: DeviceBase, report: OsdSyncReport) -> None: ...
|
|
1583
|
+
def on_osd_message_changed(self, dev: DeviceBase, message: OsdMessage) -> None: ...
|
|
1564
1584
|
def on_osd_sync_requested(self, dev: DeviceBase, request: OsdSyncRequest) -> None: ...
|
|
1565
1585
|
def on_osd_sync_setting(self, dev: DeviceBase, setting: OsdSyncSetting) -> None: ...
|
|
1566
1586
|
def on_bay_content_changed(self, bay: BayBase, content: BayContent) -> None: ...
|
|
@@ -379,6 +379,9 @@ class OneIPDeviceSetting(IntFlag):
|
|
|
379
379
|
CLOUD = (1 << 30)
|
|
380
380
|
'''Connects to the cloud service. A device that does not report it
|
|
381
381
|
connects regardless.'''
|
|
382
|
+
SINK_ROTATION = (1 << 31)
|
|
383
|
+
'''The angle the device's HDMI output is turned by. Reported only by a
|
|
384
|
+
device with an HDMI output.'''
|
|
382
385
|
|
|
383
386
|
ONEIP_DEVICE_SETTING_SWITCHES = OneIPDeviceSetting(0xFF) | OneIPDeviceSetting.AES67 \
|
|
384
387
|
| OneIPDeviceSetting.SAP_LISTEN | OneIPDeviceSetting.SAP_ANNOUNCE | OneIPDeviceSetting.POWER_SAVE_DISABLED \
|
|
@@ -398,14 +401,17 @@ ONEIP_DEVICE_SETTINGS_PTP_MESSAGES = OneIPDeviceSetting.PTP_ANNOUNCE | OneIPDevi
|
|
|
398
401
|
"""The PTP message settings."""
|
|
399
402
|
|
|
400
403
|
ONEIP_DEVICE_SETTINGS_BLOCK = ONEIP_DEVICE_SETTINGS_PTP_MESSAGES | OneIPDeviceSetting.SAP_EXPIRE \
|
|
401
|
-
| OneIPDeviceSetting.SAP_INTERVAL | OneIPDeviceSetting.STREAM_TTL
|
|
404
|
+
| OneIPDeviceSetting.SAP_INTERVAL | OneIPDeviceSetting.STREAM_TTL | OneIPDeviceSetting.SINK_ROTATION
|
|
402
405
|
"""The settings whose values a frame carries in a block of their own behind the
|
|
403
|
-
rest, OneIPPtpMessages: the PTP message settings, the two SAP values
|
|
404
|
-
stream TTL."""
|
|
406
|
+
rest, OneIPPtpMessages: the PTP message settings, the two SAP values, the
|
|
407
|
+
stream TTL and the output's rotation."""
|
|
405
408
|
|
|
406
409
|
ONEIP_STREAM_TTL_DEFAULT = 64
|
|
407
410
|
"""The TTL a OneIP device's streams carry until it is set."""
|
|
408
411
|
|
|
412
|
+
ONEIP_SINK_ROTATION_MAX = 359
|
|
413
|
+
"""The largest angle a OneIP device's HDMI output is turned by, in degrees."""
|
|
414
|
+
|
|
409
415
|
class OneIPPtpMode(IntEnum):
|
|
410
416
|
'''How a OneIP device takes part in PTP.'''
|
|
411
417
|
OFF = 0
|
|
@@ -434,6 +440,31 @@ class OsdSyncWhat(IntFlag):
|
|
|
434
440
|
LOGO_SLOTS = (1 << 3)
|
|
435
441
|
'''The logo images in the four slots.'''
|
|
436
442
|
|
|
443
|
+
class OsdMessageFlag(IntFlag):
|
|
444
|
+
'''What a unit says of the message on its output.'''
|
|
445
|
+
SET = (1 << 0)
|
|
446
|
+
'''A message is set. In a write: show it, rather than take it down.'''
|
|
447
|
+
SHOWN = (1 << 1)
|
|
448
|
+
'''The output shows it, rather than another message ahead of it.'''
|
|
449
|
+
SUPPORTED = (1 << 2)
|
|
450
|
+
'''The unit's video processor composes the message layer.'''
|
|
451
|
+
REFUSED = (1 << 3)
|
|
452
|
+
'''The unit did not take the write it answers, and reports the message it
|
|
453
|
+
kept.'''
|
|
454
|
+
|
|
455
|
+
class OsdMessagePriority(IntEnum):
|
|
456
|
+
'''Where a message stands against the unit's own messages: its upgrade,
|
|
457
|
+
Locate Device and encrypted source messages.'''
|
|
458
|
+
LOW = 0
|
|
459
|
+
'''After them.'''
|
|
460
|
+
NORMAL = 1
|
|
461
|
+
'''Between them.'''
|
|
462
|
+
TOP = 2
|
|
463
|
+
'''Before them.'''
|
|
464
|
+
|
|
465
|
+
OSD_MESSAGE_TEXT_MAX = 255
|
|
466
|
+
"""The longest message text a unit takes, in bytes."""
|
|
467
|
+
|
|
437
468
|
class OsdSyncState(IntEnum):
|
|
438
469
|
'''How far a unit has got with taking an overlay sync.'''
|
|
439
470
|
TAKING = 0
|
|
@@ -109,6 +109,7 @@ class OneIPDeviceSetting(IntFlag):
|
|
|
109
109
|
POWER_SAVE_DISABLED: Incomplete
|
|
110
110
|
STREAM_TTL: Incomplete
|
|
111
111
|
CLOUD: Incomplete
|
|
112
|
+
SINK_ROTATION: Incomplete
|
|
112
113
|
|
|
113
114
|
ONEIP_DEVICE_SETTING_SWITCHES: Incomplete
|
|
114
115
|
ONEIP_DEVICE_SETTINGS_REPORTED_ONLY: Incomplete
|
|
@@ -116,6 +117,7 @@ ONEIP_DEVICE_SETTINGS_PTP: Incomplete
|
|
|
116
117
|
ONEIP_DEVICE_SETTINGS_PTP_MESSAGES: Incomplete
|
|
117
118
|
ONEIP_DEVICE_SETTINGS_BLOCK: Incomplete
|
|
118
119
|
ONEIP_STREAM_TTL_DEFAULT: int
|
|
120
|
+
ONEIP_SINK_ROTATION_MAX: int
|
|
119
121
|
|
|
120
122
|
class OneIPPtpMode(IntEnum):
|
|
121
123
|
OFF: int
|
|
@@ -132,6 +134,19 @@ class OsdSyncWhat(IntFlag):
|
|
|
132
134
|
BACKGROUNDS: Incomplete
|
|
133
135
|
LOGO_SLOTS: Incomplete
|
|
134
136
|
|
|
137
|
+
class OsdMessageFlag(IntFlag):
|
|
138
|
+
SET: Incomplete
|
|
139
|
+
SHOWN: Incomplete
|
|
140
|
+
SUPPORTED: Incomplete
|
|
141
|
+
REFUSED: Incomplete
|
|
142
|
+
|
|
143
|
+
class OsdMessagePriority(IntEnum):
|
|
144
|
+
LOW: int
|
|
145
|
+
NORMAL: int
|
|
146
|
+
TOP: int
|
|
147
|
+
|
|
148
|
+
OSD_MESSAGE_TEXT_MAX: int
|
|
149
|
+
|
|
135
150
|
class OsdSyncState(IntEnum):
|
|
136
151
|
TAKING: int
|
|
137
152
|
DONE: int
|
|
@@ -133,7 +133,9 @@ SETTINGS_BLOCK_OFFSET = _PTP_STATE_OFFSET + _PTP_STATE_SIZE
|
|
|
133
133
|
state.'''
|
|
134
134
|
SETTINGS_BLOCK_SIZE = 8
|
|
135
135
|
_STREAM_TTL_AT = 8
|
|
136
|
-
|
|
136
|
+
_SINK_ROTATION_AT = 10
|
|
137
|
+
_SINK_ROTATION_STEP_AT = 12
|
|
138
|
+
_SETTINGS_BLOCK_LONG_SIZE = 16
|
|
137
139
|
|
|
138
140
|
def carries_settings_block(settings:OneIPDeviceSettings) -> bool:
|
|
139
141
|
'''Whether settings carry a value only the block of their own can hold.'''
|
|
@@ -141,14 +143,24 @@ def carries_settings_block(settings:OneIPDeviceSettings) -> bool:
|
|
|
141
143
|
|
|
142
144
|
def own_settings_block(settings:OneIPDeviceSettings) -> bytes:
|
|
143
145
|
'''The block the settings in ONEIP_DEVICE_SETTINGS_BLOCK travel in: 8
|
|
144
|
-
bytes, or 16
|
|
146
|
+
bytes, or 16 when the settings carry the stream TTL, at 8, or the output's
|
|
147
|
+
rotation, a u16 at 10. Only a device that has either reads past 8, so a
|
|
148
|
+
frame without them keeps the length every device knows. The rotation's
|
|
149
|
+
step at 12 is the device's to report, and is left 0.'''
|
|
145
150
|
m = settings.ptp_messages or OneIPPtpMessages()
|
|
146
151
|
block = struct.pack('<bbbBBBH', m.log_announce_interval, m.log_sync_interval,
|
|
147
152
|
m.log_delay_req_interval, m.announce_timeout, m.dscp, m.sap_expire_min,
|
|
148
153
|
m.sap_interval_s)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
154
|
+
ttl = (OneIPDeviceSetting.STREAM_TTL in settings.valid)
|
|
155
|
+
rotation = (OneIPDeviceSetting.SINK_ROTATION in settings.valid)
|
|
156
|
+
if not (ttl or rotation):
|
|
157
|
+
return block
|
|
158
|
+
tail = bytearray(_SETTINGS_BLOCK_LONG_SIZE - len(block))
|
|
159
|
+
if ttl:
|
|
160
|
+
tail[_STREAM_TTL_AT - len(block)] = m.stream_ttl
|
|
161
|
+
if rotation:
|
|
162
|
+
struct.pack_into('<H', tail, _SINK_ROTATION_AT - len(block), m.sink_rotation)
|
|
163
|
+
return block + bytes(tail)
|
|
152
164
|
|
|
153
165
|
def parse_own_settings_block(block:bytes, valid:OneIPDeviceSetting) -> tuple[OneIPPtpMessages, OneIPDeviceSetting]:
|
|
154
166
|
'''The block the settings in ONEIP_DEVICE_SETTINGS_BLOCK travel in, and
|
|
@@ -158,10 +170,16 @@ def parse_own_settings_block(block:bytes, valid:OneIPDeviceSetting) -> tuple[One
|
|
|
158
170
|
if (len(block) <= _STREAM_TTL_AT):
|
|
159
171
|
valid &= ~OneIPDeviceSetting.STREAM_TTL
|
|
160
172
|
ttl = block[_STREAM_TTL_AT] if (len(block) > _STREAM_TTL_AT) else 0
|
|
173
|
+
rotation, step = 0, 0
|
|
174
|
+
if (len(block) >= _SINK_ROTATION_STEP_AT + 2):
|
|
175
|
+
rotation, step = struct.unpack_from('<HH', block, _SINK_ROTATION_AT)
|
|
176
|
+
else:
|
|
177
|
+
valid &= ~OneIPDeviceSetting.SINK_ROTATION
|
|
161
178
|
announce, sync, delay_req, timeout, dscp, expire, interval = struct.unpack_from('<bbbBBBH', block)
|
|
162
179
|
return (OneIPPtpMessages(log_announce_interval=announce, log_sync_interval=sync,
|
|
163
180
|
log_delay_req_interval=delay_req, announce_timeout=timeout, dscp=dscp,
|
|
164
|
-
sap_expire_min=expire, sap_interval_s=interval, stream_ttl=ttl
|
|
181
|
+
sap_expire_min=expire, sap_interval_s=interval, stream_ttl=ttl,
|
|
182
|
+
sink_rotation=rotation, sink_rotation_step=step), valid)
|
|
165
183
|
|
|
166
184
|
def settings_block(settings:OneIPDeviceSettings) -> bytes:
|
|
167
185
|
'''The 48-byte device settings block.
|
|
@@ -520,8 +538,7 @@ class FrameV2IPDeviceConfiguration(FrameBase):
|
|
|
520
538
|
frame = parse_settings_block(self.payload[_SETTINGS_OFFSET:], self.payload[SETTINGS_BLOCK_OFFSET:])
|
|
521
539
|
if self.target_self:
|
|
522
540
|
return frame
|
|
523
|
-
|
|
524
|
-
return frame.as_applied_to(reported.valid if (reported is not None) else OneIPDeviceSetting(0))
|
|
541
|
+
return frame.as_applied_to(dev.oneip_settings)
|
|
525
542
|
|
|
526
543
|
@cached_property
|
|
527
544
|
def vlan(self) -> OneIPVlan|None:
|
|
@@ -17,12 +17,14 @@ sync carries the same seq, which the source numbers on its own.
|
|
|
17
17
|
from functools import cached_property
|
|
18
18
|
import ipaddress
|
|
19
19
|
import struct
|
|
20
|
+
from typing import Any
|
|
20
21
|
import zlib
|
|
21
22
|
from ..compat import override
|
|
22
23
|
from .Constants import (OSD_SYNC_RECORDS_MAX, OSD_SYNC_SLOTS, DeviceFeature, OsdCaptionAlign,
|
|
23
|
-
OsdCaptionFormat, OsdSyncSize, OsdSyncState,
|
|
24
|
+
OsdCaptionFormat, OsdMessageFlag, OsdMessagePriority, OsdSyncSize, OsdSyncState,
|
|
25
|
+
OsdSyncWhat)
|
|
24
26
|
from .FrameBase import FrameBase
|
|
25
|
-
from ..Interface import (DeviceRegistry, OsdCaption, OsdLogo, OsdSync, OsdSyncEntry, OsdSyncReport,
|
|
27
|
+
from ..Interface import (DeviceRegistry, OsdCaption, OsdLogo, OsdMessage, OsdSync, OsdSyncEntry, OsdSyncReport,
|
|
26
28
|
OsdSyncRequest, OsdSyncSetting, OsdSyncSlot, OsdSyncValue, _wire_enum)
|
|
27
29
|
from ..Uid import MxrDeviceUid
|
|
28
30
|
|
|
@@ -32,13 +34,16 @@ from ..Uid import MxrDeviceUid
|
|
|
32
34
|
# sync what at 20, u16 HTTP port at 22, the source's IPv4 address at 24,
|
|
33
35
|
# u16 records at 28, then four 12-byte logo slots from 32 (u32
|
|
34
36
|
# bytes, u32 CRC-32, u8 percent): 80 bytes
|
|
35
|
-
# request what at 20, the 16-byte missing-settings bitmap at 22, then
|
|
36
|
-
#
|
|
37
|
-
# not send: 38 or 54 bytes
|
|
37
|
+
# request what at 20, the 16-byte missing-settings bitmap at 22, then the
|
|
38
|
+
# sync's source at 38: 54 bytes
|
|
38
39
|
# setting u16 index at 20, u16 records at 22, kind at 24, size at 25, u16
|
|
39
40
|
# layers at 26, u16 set at 28, then the body at 30
|
|
40
41
|
# report state at 20, u16 written at 22, u16 refused at 24, a 64-byte
|
|
41
42
|
# reason at 26: 90 bytes
|
|
43
|
+
# message flags at 20, logo at 21, priority at 22, a reserved byte, u16 text
|
|
44
|
+
# length at 24, then the text, unterminated, with a seq of 0: a
|
|
45
|
+
# request for the message on the unit's output, a write of one, or
|
|
46
|
+
# the unit's answer to either
|
|
42
47
|
OSD_SYNC_VERSION = 2
|
|
43
48
|
'''The payload version every frame opens with; a unit drops any other.'''
|
|
44
49
|
_HEADER_SIZE = 20
|
|
@@ -47,14 +52,18 @@ OSD_KIND_SYNC = 0
|
|
|
47
52
|
OSD_KIND_REQUEST = 1
|
|
48
53
|
OSD_KIND_SETTING = 2
|
|
49
54
|
OSD_KIND_REPORT = 3
|
|
55
|
+
OSD_KIND_MESSAGE_REQUEST = 4
|
|
56
|
+
OSD_KIND_MESSAGE_SET = 5
|
|
57
|
+
OSD_KIND_MESSAGE_STATE = 6
|
|
50
58
|
|
|
51
59
|
_SYNC_SIZE = 80
|
|
52
60
|
_SLOTS_AT = 32
|
|
53
61
|
_SLOT_SIZE = 12
|
|
54
|
-
_REQUEST_SIZE =
|
|
62
|
+
_REQUEST_SIZE = 54
|
|
55
63
|
_REQUEST_SOURCE_AT = 38
|
|
56
64
|
_SETTING_BODY_AT = 30
|
|
57
65
|
_REPORT_SIZE = 90
|
|
66
|
+
_MESSAGE_SIZE = 26
|
|
58
67
|
|
|
59
68
|
OSD_SETTING_CAPTION = 0
|
|
60
69
|
OSD_SETTING_LOGO = 1
|
|
@@ -114,6 +123,15 @@ class FrameV2IPOsdSync(FrameBase):
|
|
|
114
123
|
p += struct.pack('<I', value)
|
|
115
124
|
return FrameBase.construct_base(mxr=mxr, opcode=0x53, payload=p, target=to)
|
|
116
125
|
|
|
126
|
+
@staticmethod
|
|
127
|
+
def construct_message(mxr:DeviceRegistry, target:Any, target_uid:MxrDeviceUid, kind:int,
|
|
128
|
+
flags:OsdMessageFlag=OsdMessageFlag(0), logo:int=0,
|
|
129
|
+
priority:OsdMessagePriority=OsdMessagePriority.LOW, text:bytes=b'') -> FrameBase|None:
|
|
130
|
+
'''A message frame of kind for the output of target_uid.'''
|
|
131
|
+
p = (_header(kind, 0, target_uid) + struct.pack('<BbBxH', int(flags), logo, int(priority), len(text))
|
|
132
|
+
+ text)
|
|
133
|
+
return FrameBase.construct_base(target=target, mxr=mxr, opcode=0x53, payload=p)
|
|
134
|
+
|
|
117
135
|
@cached_property
|
|
118
136
|
def kind(self) -> int|None:
|
|
119
137
|
'''The kind, None for a frame a unit drops: shorter than its header, or
|
|
@@ -168,15 +186,14 @@ class FrameV2IPOsdSync(FrameBase):
|
|
|
168
186
|
def request(self) -> OsdSyncRequest|None:
|
|
169
187
|
'''The request, None for any other kind or one no unit takes. A unit
|
|
170
188
|
asks about itself; management may also ask for a new sync for another
|
|
171
|
-
unit.
|
|
172
|
-
as units on an older module send, reads its source as empty.'''
|
|
189
|
+
unit.'''
|
|
173
190
|
p = self.payload or b''
|
|
174
191
|
if (self.kind != OSD_KIND_REQUEST) or (len(p) < _REQUEST_SIZE):
|
|
175
192
|
return None
|
|
176
193
|
target, seq = self.target, self.seq or 0
|
|
177
194
|
if (target != self.remote_id) and not ((seq == 0) and self.from_management):
|
|
178
195
|
return None
|
|
179
|
-
source = MxrDeviceUid(p[_REQUEST_SOURCE_AT:_REQUEST_SOURCE_AT + 16]
|
|
196
|
+
source = MxrDeviceUid(p[_REQUEST_SOURCE_AT:_REQUEST_SOURCE_AT + 16])
|
|
180
197
|
return OsdSyncRequest(target=target, seq=seq, what=OsdSyncWhat(p[20]), missing=bytes(p[22:38]),
|
|
181
198
|
source=source)
|
|
182
199
|
|
|
@@ -223,29 +240,45 @@ class FrameV2IPOsdSync(FrameBase):
|
|
|
223
240
|
return OsdSyncReport(seq=self.seq or 0, state=_wire_enum(OsdSyncState, p[20]), written=written,
|
|
224
241
|
refused=refused, reason=_cstr(p[26:_REPORT_SIZE]))
|
|
225
242
|
|
|
243
|
+
@cached_property
|
|
244
|
+
def message(self) -> OsdMessage|None:
|
|
245
|
+
'''The message a unit reports on its own output, None for any other
|
|
246
|
+
kind, one about another unit, or one short of the text it counts.'''
|
|
247
|
+
p = self.payload or b''
|
|
248
|
+
if (self.kind != OSD_KIND_MESSAGE_STATE) or (len(p) < _MESSAGE_SIZE) or (self.target != self.remote_id):
|
|
249
|
+
return None
|
|
250
|
+
flags, logo, priority, length = struct.unpack_from('<BbBxH', p, 20)
|
|
251
|
+
text = p[_MESSAGE_SIZE:]
|
|
252
|
+
if (len(text) < length):
|
|
253
|
+
return None
|
|
254
|
+
return OsdMessage(flags=OsdMessageFlag(flags), logo=logo, priority=_wire_enum(OsdMessagePriority, priority),
|
|
255
|
+
text=text[:length].decode('utf-8', errors='replace'))
|
|
256
|
+
|
|
226
257
|
@override
|
|
227
258
|
def process(self) -> None:
|
|
228
259
|
'''Record a sync and a report against their sender, and hand a request
|
|
229
260
|
and a setting to the callbacks. A request this client may answer goes
|
|
230
261
|
to the registry: one asking for a new sync, or one naming this client
|
|
231
|
-
as the source
|
|
262
|
+
as the source.'''
|
|
232
263
|
if ((dev := self.remote_device) is None):
|
|
233
264
|
return
|
|
234
265
|
if ((sync := self.sync) is not None):
|
|
235
266
|
dev.on_mxr_update(sync)
|
|
236
267
|
elif ((report := self.report) is not None):
|
|
237
268
|
dev.on_mxr_update(report)
|
|
269
|
+
elif ((message := self.message) is not None):
|
|
270
|
+
dev.on_mxr_update(message)
|
|
238
271
|
elif ((setting := self.setting) is not None):
|
|
239
272
|
dev.callbacks.on_osd_sync_setting(dev, setting)
|
|
240
273
|
elif ((request := self.request) is not None):
|
|
241
274
|
dev.callbacks.on_osd_sync_requested(dev, request)
|
|
242
|
-
if (request.seq == 0) or
|
|
275
|
+
if (request.seq == 0) or (request.source == self.mxr.uid):
|
|
243
276
|
self.mxr.on_mxr_update(request)
|
|
244
277
|
|
|
245
278
|
def __str__(self) -> str:
|
|
246
279
|
if (self.kind is None):
|
|
247
280
|
return "overlay sync <dropped>"
|
|
248
|
-
for item in (self.sync, self.request, self.setting, self.report):
|
|
281
|
+
for item in (self.sync, self.request, self.setting, self.report, self.message):
|
|
249
282
|
if (item is not None):
|
|
250
283
|
return f"overlay sync {item}"
|
|
251
284
|
return f"overlay sync kind {self.kind} seq {self.seq} not taken"
|