mx-remote 5.3.0__tar.gz → 5.5.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-5.3.0/CLAUDE.md → mx_remote-5.5.0/AGENTS.md +92 -4
- mx_remote-5.5.0/CLAUDE.md +4 -0
- mx_remote-5.5.0/PKG-INFO +133 -0
- mx_remote-5.5.0/README.md +101 -0
- mx_remote-5.5.0/docs/README.md +14 -0
- mx_remote-5.5.0/docs/_static/brand.css +34 -0
- mx_remote-5.5.0/docs/_static/favicon.svg +3 -0
- mx_remote-5.5.0/docs/_static/logo-dark.svg +8 -0
- mx_remote-5.5.0/docs/_static/logo.svg +8 -0
- mx_remote-5.5.0/docs/api.md +59 -0
- mx_remote-5.5.0/docs/audio.md +36 -0
- mx_remote-5.5.0/docs/callbacks.md +90 -0
- mx_remote-5.5.0/docs/conf.py +102 -0
- mx_remote-5.5.0/docs/configuration.md +133 -0
- mx_remote-5.5.0/docs/devices-and-bays.md +47 -0
- mx_remote-5.5.0/docs/diagnostics.md +89 -0
- mx_remote-5.5.0/docs/index.md +34 -0
- mx_remote-5.5.0/docs/multiviewer.md +52 -0
- mx_remote-5.5.0/docs/oneip.md +43 -0
- mx_remote-5.5.0/docs/requirements.txt +3 -0
- mx_remote-5.5.0/docs/routing.md +24 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/Interface.py +66 -2
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/Interface.pyi +4 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/const.py +1 -1
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/Constants.py +8 -9
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/Data.py +17 -2
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/Data.pyi +2 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/Factory.py +16 -10
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameBayConfig.py +1 -1
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameFactoryReset.py +1 -1
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameFirmwareVersion.py +2 -1
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameHeader.py +8 -2
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameLinks.py +1 -1
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameMeshOperation.py +43 -2
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameMeshOperation.pyi +2 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameNetworkStatus.py +36 -3
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameNetworkStatus.pyi +8 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameRCAction.py +26 -3
- mx_remote-5.5.0/mx_remote/proto/FrameRCIr.py +140 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameRCIr.pyi +4 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameRCKey.py +27 -4
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameSetName.py +10 -1
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameTxIR.py +55 -8
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameTxIR.pyi +4 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPAudio.py +1 -1
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPMultiviewer.py +96 -35
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPMultiviewer.pyi +1 -1
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPVideoWall.py +2 -3
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameVolumeSet.py +47 -20
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameVolumeSet.pyi +1 -1
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/Multiviewer.py +39 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/Multiviewer.pyi +9 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/V2IPConfig.py +27 -1
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/V2IPConfig.pyi +5 -1
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/V2IPStats.py +5 -4
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/remote/Bay.py +5 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/remote/ConnectionAsync.py +2 -2
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/remote/Device.py +3 -1
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/remote/Remote.py +41 -4
- {mx_remote-5.3.0 → mx_remote-5.5.0}/pyproject.toml +3 -1
- {mx_remote-5.3.0 → mx_remote-5.5.0}/tests/capture.py +2 -3
- {mx_remote-5.3.0 → mx_remote-5.5.0}/tests/hello.py +27 -2
- mx_remote-5.5.0/tests/idempotent.py +173 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/tests/offsets.py +16 -3
- {mx_remote-5.3.0 → mx_remote-5.5.0}/tests/proto_stamp.py +7 -12
- {mx_remote-5.3.0 → mx_remote-5.5.0}/tests/run.py +5 -1
- mx_remote-5.5.0/tests/wirefix.py +588 -0
- mx_remote-5.3.0/PKG-INFO +0 -558
- mx_remote-5.3.0/README.md +0 -526
- mx_remote-5.3.0/mx_remote/proto/FrameRCIr.py +0 -85
- {mx_remote-5.3.0 → mx_remote-5.5.0}/.gitignore +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/LICENSE +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/hatch_build.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/Uid.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/Uid.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/__init__.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/__init__.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/const.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/main.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/main.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/BayConfig.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/BayConfig.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/Constants.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/Factory.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameAmpDolbySettings.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameAmpDolbySettings.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameAmpZoneSettings.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameAmpZoneSettings.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameAudioClip.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameAudioClip.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameAudioSetRoute.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameAudioSetRoute.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameBase.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameBase.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameBayConfig.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameBayConfigSecondary.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameBayConfigSecondary.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameBayHide.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameBayHide.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameBayStatus.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameBayStatus.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameConnectStatus.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameConnectStatus.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameDebug.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameDebug.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameDiscover.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameDiscover.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameEDID.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameEDID.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameEDIDProfile.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameEDIDProfile.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameFactoryReset.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameFilterStatus.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameFilterStatus.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameFirmwareVersion.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameHeader.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameHello.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameHello.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameLinks.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameMirrorStatus.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameMirrorStatus.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameMonitoringPulse.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameMonitoringPulse.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FramePowerChange.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FramePowerChange.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameRCAction.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameRCKey.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameRCSettings.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameRCSettings.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameReboot.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameReboot.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameRoutingChange.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameRoutingChange.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameSetInstaller.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameSetInstaller.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameSetName.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameSetRoute.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameSetRoute.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameSetupStatus.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameSetupStatus.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameSignalStatus.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameSignalStatus.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameSignalStatusNew.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameSignalStatusNew.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameSysTemperature.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameSysTemperature.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameSystemStatus.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameSystemStatus.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameTXRCAction.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameTXRCAction.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameTXRCKey.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameTXRCKey.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameTopology.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameTopology.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameUpgradeFPGA.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameUpgradeFPGA.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPAudio.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPBayMapping.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPBayMapping.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPDetectBays.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPDetectBays.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPLink.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPLink.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPPowerSave.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPPowerSave.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPSetMaster.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPSetMaster.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPSourceSwitch.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPSourceSwitch.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPSources.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPSources.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPStats.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPStats.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPTiling.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPTiling.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameV2IPVideoWall.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameVolume.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameVolume.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameVolumeDown.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameVolumeDown.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameVolumeUp.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/FrameVolumeUp.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/LinkConfig.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/LinkConfig.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/Svd.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/Svd.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/V2IPStats.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/__init__.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/__init__.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/proto/svd.csv +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/py.typed +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/remote/Bay.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/remote/ConnectionAsync.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/remote/Device.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/remote/Link.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/remote/Link.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/remote/Remote.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/remote/State.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/remote/State.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/remote/V2IP.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/mx_remote/remote/V2IP.pyi +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/tests/audio_ir.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/tests/e2e.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/tests/entry.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/tests/handlers.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/tests/handlers_cover.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/tests/merge.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/tests/newops.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/tests/protogate.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/tests/rcsettings.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/tests/roundtrip.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/tests/smoke.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/tests/sources.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/tests/stats.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/tests/structs.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/tests/txresult.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/tests/unknowns.py +0 -0
- {mx_remote-5.3.0 → mx_remote-5.5.0}/tests/vdetails.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# AGENTS.md
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Guidance for coding agents working in this repository.
|
|
4
4
|
|
|
5
5
|
## What this is
|
|
6
6
|
|
|
@@ -83,6 +83,21 @@ reads it via regex, so bump it there only. Release commits follow the convention
|
|
|
83
83
|
force-includes them in the wheel (they are gitignored). `mx_remote` ships as typed
|
|
84
84
|
(`py.typed`). If stubgen produces nothing the build fails by design.
|
|
85
85
|
|
|
86
|
+
### Documentation
|
|
87
|
+
`docs/` is both the GitHub-readable guide set and the Sphinx source for
|
|
88
|
+
[the published site](https://opdenkamp.github.io/mx-remote/), which
|
|
89
|
+
`.github/workflows/docs.yml` rebuilds on every push to master. Build it the way
|
|
90
|
+
the workflow does:
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
pip install . -r docs/requirements.txt
|
|
94
|
+
sphinx-build -b html -W --keep-going docs site
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Autodoc emits reStructuredText, so a directive belongs in an ```` ```{eval-rst} ````
|
|
98
|
+
block. Outside one it renders as its own source and the build still reports
|
|
99
|
+
success — the workflow greps the output for that.
|
|
100
|
+
|
|
86
101
|
## Architecture
|
|
87
102
|
|
|
88
103
|
Three layers, decoupled by abstract base classes:
|
|
@@ -105,7 +120,7 @@ Three layers, decoupled by abstract base classes:
|
|
|
105
120
|
- `Device` (`DeviceBase`), `Bay` (`BayBase`) — live device/port state.
|
|
106
121
|
- `ConnectionAsync` — `asyncio.DatagramProtocol` UDP transport (multicast/broadcast).
|
|
107
122
|
- `State` — dispatches events to registered `MxrCallbacks`.
|
|
108
|
-
- `V2IP`, `Link
|
|
123
|
+
- `V2IP`, `Link` — OneIP streaming and virtual bay links.
|
|
109
124
|
|
|
110
125
|
### Receive data flow
|
|
111
126
|
`ConnectionAsync` datagram → `Remote.on_datagram_received` → `Remote.process_frame` →
|
|
@@ -131,5 +146,78 @@ Built by `proto/Factory.py::create_mxr_frame`, decoded by `proto/FrameHeader.py`
|
|
|
131
146
|
When adding or editing a `Frame*` class, byte layouts **must match the MatrixOS firmware C
|
|
132
147
|
structs** — verify against the firmware source rather than inferring from samples. Adding a
|
|
133
148
|
new opcode requires both the `Frame*` class and a dispatch entry in
|
|
134
|
-
`Factory.py::_mxr_frame_factory`.
|
|
149
|
+
`Factory.py::_mxr_frame_factory`.
|
|
150
|
+
|
|
151
|
+
Get sizes and offsets from a compiler, not by adding up field widths. Two things
|
|
152
|
+
defeat reading:
|
|
153
|
+
|
|
154
|
+
- `mxr_uid` is `uint32_t[4]`, so it aligns to 4 and shifts every field behind it
|
|
155
|
+
in a struct that is not `PACKED`.
|
|
156
|
+
- an `ALIGN(8)` struct is as wide as its alignment rounds it to, and its tail
|
|
157
|
+
padding is part of the payload. Receivers test `len < sizeof(...)` before
|
|
158
|
+
reading a field, so a frame short by its padding is dropped without a reply —
|
|
159
|
+
which looks exactly like one that was accepted and ignored.
|
|
160
|
+
|
|
161
|
+
Paste the struct into a throwaway `.c` file with `__builtin_offsetof` and run it.
|
|
162
|
+
Note that `uint_fast16_t` is 4 bytes on the target, so model it as `uint32_t`
|
|
163
|
+
rather than letting the host pick.
|
|
164
|
+
|
|
165
|
+
Compile the struct's *own* component types, at the version you are decoding.
|
|
166
|
+
Substituting today's equivalents makes the result a reconstruction rather than a
|
|
167
|
+
measurement, and a reconstruction that happens to be right is indistinguishable
|
|
168
|
+
from one that is wrong until something else disagrees with it. Types get renamed
|
|
169
|
+
and moved between headers without their layout changing, so a name that no
|
|
170
|
+
longer exists is not evidence that the layout did change.
|
|
171
|
+
|
|
172
|
+
`PACKED` on a struct sets where its members are *placed*, not how wide they are.
|
|
173
|
+
A nested aggregate keeps its own internal padding, so a packed struct holding an
|
|
174
|
+
array of unpacked 12-byte records still spends 48 bytes on four of them — it
|
|
175
|
+
just starts them at an unaligned offset. Assuming the nesting flattens gives a
|
|
176
|
+
smaller size that looks plausible and decodes.
|
|
177
|
+
|
|
178
|
+
A protocol version outlives layout revisions. The struct at the commit that
|
|
179
|
+
introduced a version is not necessarily the struct any device sent under it, and
|
|
180
|
+
two revisions can share one version — so date a layout from the releases that
|
|
181
|
+
carried it, not from the commit that named it.
|
|
182
|
+
|
|
183
|
+
That needs both halves of the release history, which only the firmware side has.
|
|
184
|
+
The tag says what bytes a version sends; the distribution list says whether
|
|
185
|
+
anyone runs it. Tags alone invent field versions nobody has, and this protocol
|
|
186
|
+
has several — versions tagged and never uploaded are why two apparent
|
|
187
|
+
compatibility problems here turned out not to exist.
|
|
188
|
+
|
|
189
|
+
The protocol floor in `MXR_OPCODE_VERSIONS` is a delivery gate, not a layout
|
|
190
|
+
selector. A payload can widen without its floor moving, and then the stamp says
|
|
191
|
+
nothing about which layout arrived — dispatch on payload length instead.
|
|
192
|
+
`tests/wirefix.py` covers the cases where the two come apart.
|
|
193
|
+
|
|
194
|
+
## Writing down a deliberate divergence
|
|
195
|
+
|
|
196
|
+
A note recording *what the firmware does* reads as *what this library should
|
|
197
|
+
do*, and the gap between them is where every deliberate difference lives. So
|
|
198
|
+
write the divergence beside the fact, not somewhere else: "firmware ignores this
|
|
199
|
+
below 0x22" invites the next reader to delete the path that decodes it, where
|
|
200
|
+
"firmware ignores it, we decode it anyway, because those units still transmit
|
|
201
|
+
and nothing else answers them" does not.
|
|
202
|
+
|
|
203
|
+
This library is older than the firmware currently shipping and serves versions
|
|
204
|
+
that will never be upgraded, so it decodes more than any device does. That is
|
|
205
|
+
the intended relationship, not drift to be tidied up.
|
|
206
|
+
|
|
207
|
+
## Fixtures that cannot fail
|
|
208
|
+
|
|
209
|
+
A fixture can be too simple to reach the code under test. It can also be too
|
|
210
|
+
*helpful*: it reaches the code with the interesting input already removed, or it
|
|
211
|
+
supplies for free the precondition the code is supposed to establish. The second
|
|
212
|
+
kind is worse, because it occupies the slot where the real test would go and
|
|
213
|
+
reports green forever.
|
|
214
|
+
|
|
215
|
+
Ask what the setup does for the code, and whether a caller gets that for free.
|
|
216
|
+
A frame stamped at a version no device sends, a payload length chosen so that
|
|
217
|
+
one wrong branch still parses, a send exercised only against a device that had
|
|
218
|
+
already been introduced to us — each passes, and none of them can fail.
|
|
219
|
+
|
|
220
|
+
The check is mechanical: break the thing the test is for, one part at a time,
|
|
221
|
+
and watch that test go red for the right reason. An assertion that fires on a
|
|
222
|
+
sanity check placed above the subject has tested nothing. Note the proto/ and remote/ modules use tab indentation
|
|
135
223
|
while `Interface.py` and `const.py` use spaces — match the file you are editing.
|
mx_remote-5.5.0/PKG-INFO
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: mx_remote
|
|
3
|
+
Version: 5.5.0
|
|
4
|
+
Summary: Python 3 library for interfacing with MX Remote compatible devices
|
|
5
|
+
Project-URL: Homepage, https://github.com/opdenkamp/mx-remote/
|
|
6
|
+
Project-URL: Documentation, https://opdenkamp.github.io/mx-remote/
|
|
7
|
+
Project-URL: Repository, https://github.com/opdenkamp/mx-remote.git
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/opdenkamp/mx-remote/issues
|
|
9
|
+
Author-email: Lars Op den Kamp <lars@opdenkamp-it.nl>
|
|
10
|
+
Maintainer-email: Lars Op den Kamp <lars@opdenkamp-it.nl>
|
|
11
|
+
License: Copyright 2021-2026 Lars Op den Kamp <lars@opdenkamp-it.nl>
|
|
12
|
+
|
|
13
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
14
|
+
|
|
15
|
+
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
16
|
+
|
|
17
|
+
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
18
|
+
|
|
19
|
+
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
|
20
|
+
|
|
21
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Keywords: ampos,matrixos,oneip,pulse-eight
|
|
24
|
+
Classifier: Development Status :: 3 - Alpha
|
|
25
|
+
Classifier: Programming Language :: Python
|
|
26
|
+
Classifier: Topic :: System :: Networking :: Monitoring
|
|
27
|
+
Requires-Python: >=3.11
|
|
28
|
+
Requires-Dist: aiofiles>=24.1.0
|
|
29
|
+
Requires-Dist: aiohttp>=3.10.5
|
|
30
|
+
Requires-Dist: netifaces>=0.11.0
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# MX Remote - Python Client for Pulse-Eight MatrixOS devices
|
|
34
|
+
|
|
35
|
+
Python 3 library for discovering and controlling [Pulse-Eight](https://www.pulse-eight.com/)
|
|
36
|
+
AV distribution hardware over a local network: video/audio matrices, HDMI-over-IP
|
|
37
|
+
encoders/decoders, multiviewers, and audio amplifiers. Supports device discovery,
|
|
38
|
+
video/audio routing, volume control, remote-control key passthrough, HDMI-over-IP
|
|
39
|
+
streaming, multiviewer control, and more.
|
|
40
|
+
|
|
41
|
+
If you are looking to integrate Pulse-Eight **neo**, **OneIP**, or **ProAmp8** devices into
|
|
42
|
+
your own software or home-automation system, this is the library for it.
|
|
43
|
+
|
|
44
|
+
**Documentation:** [opdenkamp.github.io/mx-remote](https://opdenkamp.github.io/mx-remote/)
|
|
45
|
+
|
|
46
|
+
## What is MX Remote?
|
|
47
|
+
|
|
48
|
+
MX Remote is the network protocol these Pulse-Eight devices use to discover and control one
|
|
49
|
+
another over UDP (multicast or broadcast). All of them run the shared **MatrixOS** firmware,
|
|
50
|
+
which speaks this protocol natively. This library is a client implementation of that
|
|
51
|
+
protocol — its purpose is to expose the devices to third-party software.
|
|
52
|
+
|
|
53
|
+
## Supported devices
|
|
54
|
+
|
|
55
|
+
All devices below run MatrixOS and are controlled through the same protocol:
|
|
56
|
+
|
|
57
|
+
- **[Pulse-Eight neo](https://www.pulse-eight.com/)** — HDBaseT video/audio matrices
|
|
58
|
+
(neo:4, neo:8, neo:X, and splitters)
|
|
59
|
+
- **[Pulse-Eight OneIP](https://www.pulse-eight.com/p/248/oneip-tx)** — HDMI-over-IP
|
|
60
|
+
units: Transmitter (TX), Receiver (RX), Transceiver (TZ), and Multiviewer
|
|
61
|
+
- **[Pulse-Eight ProAmp8](https://www.pulse-eight.com/p/219/proamp-8)** — 8-zone audio amplifier with
|
|
62
|
+
Dolby support
|
|
63
|
+
|
|
64
|
+
## Requirements
|
|
65
|
+
|
|
66
|
+
- Python 3.11 or later
|
|
67
|
+
- Network access to one or more of the Pulse-Eight devices above (multicast or broadcast)
|
|
68
|
+
|
|
69
|
+
## Installation
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install .
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Quick Start
|
|
76
|
+
|
|
77
|
+
The minimum code to discover devices on the network:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
import asyncio
|
|
81
|
+
import mx_remote
|
|
82
|
+
|
|
83
|
+
async def main():
|
|
84
|
+
mx = mx_remote.Remote()
|
|
85
|
+
await mx.start_async()
|
|
86
|
+
|
|
87
|
+
# wait for devices to be discovered
|
|
88
|
+
await asyncio.sleep(5)
|
|
89
|
+
|
|
90
|
+
for uid, device in mx.remotes.items():
|
|
91
|
+
print(f"{device.serial} ({device.name}) - {device.model_name} - {device.status}")
|
|
92
|
+
for port, bay in device.bays.items():
|
|
93
|
+
print(f" {bay.bay_label} [{bay.mode}] signal={bay.signal_detected}")
|
|
94
|
+
|
|
95
|
+
await mx.close()
|
|
96
|
+
|
|
97
|
+
asyncio.run(main())
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Documentation
|
|
101
|
+
|
|
102
|
+
The guides below are published at
|
|
103
|
+
**[opdenkamp.github.io/mx-remote](https://opdenkamp.github.io/mx-remote/)** alongside an
|
|
104
|
+
API reference generated from the docstrings, and are readable here in
|
|
105
|
+
[docs/](docs/README.md).
|
|
106
|
+
|
|
107
|
+
| Page | Covers |
|
|
108
|
+
| --- | --- |
|
|
109
|
+
| [Configuration](docs/configuration.md) | Opening a connection and keeping it in step with the mesh. |
|
|
110
|
+
| [Devices and bays](docs/devices-and-bays.md) | Naming, hiding, EDID and the device registry. |
|
|
111
|
+
| [Routing](docs/routing.md) | Selecting video and audio sources for an output. |
|
|
112
|
+
| [Callbacks](docs/callbacks.md) | Reacting to state changes rather than polling for them. |
|
|
113
|
+
| [Audio](docs/audio.md) | Volume, mute and remote-control passthrough. |
|
|
114
|
+
| [OneIP and V2IP](docs/oneip.md) | Streaming endpoints, stream sources and statistics. |
|
|
115
|
+
| [Multiviewer](docs/multiviewer.md) | Layout, sources and output configuration. |
|
|
116
|
+
| [Diagnostics](docs/diagnostics.md) | Network status, the mxr console app and capture replay. |
|
|
117
|
+
|
|
118
|
+
## Other languages
|
|
119
|
+
|
|
120
|
+
The same protocol, implemented independently:
|
|
121
|
+
|
|
122
|
+
- **Go** — https://github.com/opdenkamp/mx-remote-golang
|
|
123
|
+
- **Rust** — https://github.com/opdenkamp/mx-remote-rust (also ships a C ABI, for C and C++)
|
|
124
|
+
|
|
125
|
+
Python is the oldest of the three and the one that still decodes what older
|
|
126
|
+
firmware sent: `0x06`, `0x36` and `0x47` are superseded opcodes kept because a
|
|
127
|
+
unit on older firmware still emits them. So it decodes a slightly wider set than
|
|
128
|
+
the others, and the difference is deliberate rather than drift.
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
BSD 3-Clause License. See [LICENSE](LICENSE) for details.
|
|
133
|
+
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# MX Remote - Python Client for Pulse-Eight MatrixOS devices
|
|
2
|
+
|
|
3
|
+
Python 3 library for discovering and controlling [Pulse-Eight](https://www.pulse-eight.com/)
|
|
4
|
+
AV distribution hardware over a local network: video/audio matrices, HDMI-over-IP
|
|
5
|
+
encoders/decoders, multiviewers, and audio amplifiers. Supports device discovery,
|
|
6
|
+
video/audio routing, volume control, remote-control key passthrough, HDMI-over-IP
|
|
7
|
+
streaming, multiviewer control, and more.
|
|
8
|
+
|
|
9
|
+
If you are looking to integrate Pulse-Eight **neo**, **OneIP**, or **ProAmp8** devices into
|
|
10
|
+
your own software or home-automation system, this is the library for it.
|
|
11
|
+
|
|
12
|
+
**Documentation:** [opdenkamp.github.io/mx-remote](https://opdenkamp.github.io/mx-remote/)
|
|
13
|
+
|
|
14
|
+
## What is MX Remote?
|
|
15
|
+
|
|
16
|
+
MX Remote is the network protocol these Pulse-Eight devices use to discover and control one
|
|
17
|
+
another over UDP (multicast or broadcast). All of them run the shared **MatrixOS** firmware,
|
|
18
|
+
which speaks this protocol natively. This library is a client implementation of that
|
|
19
|
+
protocol — its purpose is to expose the devices to third-party software.
|
|
20
|
+
|
|
21
|
+
## Supported devices
|
|
22
|
+
|
|
23
|
+
All devices below run MatrixOS and are controlled through the same protocol:
|
|
24
|
+
|
|
25
|
+
- **[Pulse-Eight neo](https://www.pulse-eight.com/)** — HDBaseT video/audio matrices
|
|
26
|
+
(neo:4, neo:8, neo:X, and splitters)
|
|
27
|
+
- **[Pulse-Eight OneIP](https://www.pulse-eight.com/p/248/oneip-tx)** — HDMI-over-IP
|
|
28
|
+
units: Transmitter (TX), Receiver (RX), Transceiver (TZ), and Multiviewer
|
|
29
|
+
- **[Pulse-Eight ProAmp8](https://www.pulse-eight.com/p/219/proamp-8)** — 8-zone audio amplifier with
|
|
30
|
+
Dolby support
|
|
31
|
+
|
|
32
|
+
## Requirements
|
|
33
|
+
|
|
34
|
+
- Python 3.11 or later
|
|
35
|
+
- Network access to one or more of the Pulse-Eight devices above (multicast or broadcast)
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install .
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
The minimum code to discover devices on the network:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import asyncio
|
|
49
|
+
import mx_remote
|
|
50
|
+
|
|
51
|
+
async def main():
|
|
52
|
+
mx = mx_remote.Remote()
|
|
53
|
+
await mx.start_async()
|
|
54
|
+
|
|
55
|
+
# wait for devices to be discovered
|
|
56
|
+
await asyncio.sleep(5)
|
|
57
|
+
|
|
58
|
+
for uid, device in mx.remotes.items():
|
|
59
|
+
print(f"{device.serial} ({device.name}) - {device.model_name} - {device.status}")
|
|
60
|
+
for port, bay in device.bays.items():
|
|
61
|
+
print(f" {bay.bay_label} [{bay.mode}] signal={bay.signal_detected}")
|
|
62
|
+
|
|
63
|
+
await mx.close()
|
|
64
|
+
|
|
65
|
+
asyncio.run(main())
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Documentation
|
|
69
|
+
|
|
70
|
+
The guides below are published at
|
|
71
|
+
**[opdenkamp.github.io/mx-remote](https://opdenkamp.github.io/mx-remote/)** alongside an
|
|
72
|
+
API reference generated from the docstrings, and are readable here in
|
|
73
|
+
[docs/](docs/README.md).
|
|
74
|
+
|
|
75
|
+
| Page | Covers |
|
|
76
|
+
| --- | --- |
|
|
77
|
+
| [Configuration](docs/configuration.md) | Opening a connection and keeping it in step with the mesh. |
|
|
78
|
+
| [Devices and bays](docs/devices-and-bays.md) | Naming, hiding, EDID and the device registry. |
|
|
79
|
+
| [Routing](docs/routing.md) | Selecting video and audio sources for an output. |
|
|
80
|
+
| [Callbacks](docs/callbacks.md) | Reacting to state changes rather than polling for them. |
|
|
81
|
+
| [Audio](docs/audio.md) | Volume, mute and remote-control passthrough. |
|
|
82
|
+
| [OneIP and V2IP](docs/oneip.md) | Streaming endpoints, stream sources and statistics. |
|
|
83
|
+
| [Multiviewer](docs/multiviewer.md) | Layout, sources and output configuration. |
|
|
84
|
+
| [Diagnostics](docs/diagnostics.md) | Network status, the mxr console app and capture replay. |
|
|
85
|
+
|
|
86
|
+
## Other languages
|
|
87
|
+
|
|
88
|
+
The same protocol, implemented independently:
|
|
89
|
+
|
|
90
|
+
- **Go** — https://github.com/opdenkamp/mx-remote-golang
|
|
91
|
+
- **Rust** — https://github.com/opdenkamp/mx-remote-rust (also ships a C ABI, for C and C++)
|
|
92
|
+
|
|
93
|
+
Python is the oldest of the three and the one that still decodes what older
|
|
94
|
+
firmware sent: `0x06`, `0x36` and `0x47` are superseded opcodes kept because a
|
|
95
|
+
unit on older firmware still emits them. So it decodes a slightly wider set than
|
|
96
|
+
the others, and the difference is deliberate rather than drift.
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
BSD 3-Clause License. See [LICENSE](LICENSE) for details.
|
|
101
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Documentation
|
|
2
|
+
|
|
3
|
+
The API reference for `mx_remote`. Start at the [project README](https://github.com/opdenkamp/mx-remote#readme) for installation and a quick start.
|
|
4
|
+
|
|
5
|
+
| Page | Covers |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| [Configuration](configuration.md) | Opening a connection and keeping it in step with the mesh. |
|
|
8
|
+
| [Devices and bays](devices-and-bays.md) | Naming, hiding, EDID and the device registry. |
|
|
9
|
+
| [Routing](routing.md) | Selecting video and audio sources for an output. |
|
|
10
|
+
| [Callbacks](callbacks.md) | Reacting to state changes rather than polling for them. |
|
|
11
|
+
| [Audio](audio.md) | Volume, mute and remote-control passthrough. |
|
|
12
|
+
| [OneIP and V2IP](oneip.md) | Streaming endpoints, stream sources and statistics. |
|
|
13
|
+
| [Multiviewer](multiviewer.md) | Layout, sources and output configuration. |
|
|
14
|
+
| [Diagnostics](diagnostics.md) | Network status, the mxr console app and capture replay. |
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/* Shapes Furo to the house style. The palette itself is set in conf.py, where
|
|
2
|
+
Furo wants it; this file holds what a variable cannot express. */
|
|
3
|
+
|
|
4
|
+
/* The mark is portrait, so a width bound keeps it from filling the sidebar. */
|
|
5
|
+
.sidebar-logo {
|
|
6
|
+
max-width: 4.5rem;
|
|
7
|
+
margin: 0 auto 0.5rem;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.sidebar-brand-text {
|
|
11
|
+
font-weight: 600;
|
|
12
|
+
letter-spacing: -0.025em;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
h1, h2, h3, h4 {
|
|
16
|
+
letter-spacing: -0.025em;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* Furo underlines links only on hover; the guides lean on them heavily enough
|
|
20
|
+
that a reader should see where they are while scanning. */
|
|
21
|
+
article a.reference.internal,
|
|
22
|
+
article a.reference.external {
|
|
23
|
+
text-decoration-color: var(--color-brand-content);
|
|
24
|
+
text-decoration-line: underline;
|
|
25
|
+
text-decoration-thickness: 1px;
|
|
26
|
+
text-underline-offset: 0.15em;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Signatures are the spine of the API reference: give them the brand rule so
|
|
30
|
+
they read as landmarks rather than as another grey block. */
|
|
31
|
+
dl.py > dt {
|
|
32
|
+
border-left: 3px solid var(--color-brand-content);
|
|
33
|
+
padding-left: 0.6rem;
|
|
34
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="20 0 120 190" width="120" height="190" role="img" aria-label="Op den Kamp IT Solutions">
|
|
2
|
+
<circle cx="70" cy="50" r="30" fill="none" stroke="#0588c4" stroke-width="26"/>
|
|
3
|
+
<g fill="#5aafc0">
|
|
4
|
+
<rect x="27" y="117" width="16" height="66"/>
|
|
5
|
+
<rect x="57" y="117" width="76" height="16"/>
|
|
6
|
+
<rect x="87" y="117" width="16" height="66"/>
|
|
7
|
+
</g>
|
|
8
|
+
</svg>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="20 0 120 190" width="120" height="190" role="img" aria-label="Op den Kamp IT Solutions">
|
|
2
|
+
<circle cx="70" cy="50" r="30" fill="none" stroke="#0588c4" stroke-width="26"/>
|
|
3
|
+
<g fill="#117a8b">
|
|
4
|
+
<rect x="27" y="117" width="16" height="66"/>
|
|
5
|
+
<rect x="57" y="117" width="76" height="16"/>
|
|
6
|
+
<rect x="87" y="117" width="16" height="66"/>
|
|
7
|
+
</g>
|
|
8
|
+
</svg>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# API reference
|
|
2
|
+
|
|
3
|
+
Generated from the docstrings in the package. The guides cover how these fit
|
|
4
|
+
together; this page is the exhaustive list.
|
|
5
|
+
|
|
6
|
+
Autodoc emits reStructuredText, which the markdown parser does not read, so
|
|
7
|
+
every directive below sits in an `eval-rst` block. Without it the page renders
|
|
8
|
+
the directive source as prose and the build still succeeds.
|
|
9
|
+
|
|
10
|
+
```{eval-rst}
|
|
11
|
+
.. automodule:: mx_remote
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Client
|
|
15
|
+
|
|
16
|
+
`Remote` owns the socket, the device registry and the background probe that
|
|
17
|
+
keeps both in step with the mesh.
|
|
18
|
+
|
|
19
|
+
```{eval-rst}
|
|
20
|
+
.. automodule:: mx_remote.remote.Remote
|
|
21
|
+
:members:
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Devices, bays and callbacks
|
|
25
|
+
|
|
26
|
+
A client works against these types rather than the classes implementing them:
|
|
27
|
+
they are what the registry hands out and what a callback receives.
|
|
28
|
+
|
|
29
|
+
```{eval-rst}
|
|
30
|
+
.. automodule:: mx_remote.Interface
|
|
31
|
+
:members:
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Device identity
|
|
35
|
+
|
|
36
|
+
```{eval-rst}
|
|
37
|
+
.. automodule:: mx_remote.Uid
|
|
38
|
+
:members:
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Remote-control keys and actions
|
|
42
|
+
|
|
43
|
+
The key and action codes a bay reports, and the target a key was aimed at.
|
|
44
|
+
|
|
45
|
+
```{eval-rst}
|
|
46
|
+
.. autoclass:: mx_remote.proto.Constants.RCKey
|
|
47
|
+
:members:
|
|
48
|
+
:undoc-members:
|
|
49
|
+
|
|
50
|
+
.. autoclass:: mx_remote.proto.Constants.RCAction
|
|
51
|
+
:members:
|
|
52
|
+
:undoc-members:
|
|
53
|
+
|
|
54
|
+
.. autoclass:: mx_remote.proto.Constants.RCType
|
|
55
|
+
:members:
|
|
56
|
+
:undoc-members:
|
|
57
|
+
|
|
58
|
+
.. autodata:: mx_remote.proto.Constants.MXR_PROTOCOL_VERSION
|
|
59
|
+
```
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Audio
|
|
2
|
+
|
|
3
|
+
Volume, mute and remote-control passthrough.
|
|
4
|
+
|
|
5
|
+
## Volume Control
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
bay.volume_up()
|
|
9
|
+
bay.volume_down()
|
|
10
|
+
bay.volume_set(volume=50) # set to 50%
|
|
11
|
+
bay.volume_set(volume=50, muted=False)
|
|
12
|
+
bay.mute_set(mute=True)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Remote Control
|
|
16
|
+
|
|
17
|
+
Send remote control key presses and actions:
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from mx_remote import RCKey, RCAction
|
|
21
|
+
|
|
22
|
+
# send a key press
|
|
23
|
+
await bay.send_key(RCKey.KEY_SELECT)
|
|
24
|
+
await bay.send_key(RCKey.KEY_UP)
|
|
25
|
+
|
|
26
|
+
# send a remote control action
|
|
27
|
+
await bay.tx_action(RCAction.ACTION_POWER_ON)
|
|
28
|
+
await bay.tx_action(RCAction.ACTION_POWER_OFF)
|
|
29
|
+
await bay.tx_action(RCAction.ACTION_POWER_TOGGLE)
|
|
30
|
+
await bay.tx_action(RCAction.ACTION_VOLUME_UP)
|
|
31
|
+
await bay.tx_action(RCAction.ACTION_VOLUME_DOWN)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
[Documentation index](README.md) | [Project README](https://github.com/opdenkamp/mx-remote#readme)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Callbacks
|
|
2
|
+
|
|
3
|
+
Reacting to state changes rather than polling for them.
|
|
4
|
+
|
|
5
|
+
## Callbacks
|
|
6
|
+
|
|
7
|
+
Subclass `MxrCallbacks` to receive notifications when device or bay state changes:
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
class MyCallbacks(mx_remote.MxrCallbacks):
|
|
11
|
+
def on_device_config_complete(self, dev):
|
|
12
|
+
print(f"Device ready: {dev.serial} ({dev.name})")
|
|
13
|
+
|
|
14
|
+
def on_bay_registered(self, bay):
|
|
15
|
+
print(f"Bay found: {bay.bay_label}")
|
|
16
|
+
|
|
17
|
+
def on_video_source_changed(self, bay, video_source):
|
|
18
|
+
print(f"{bay.user_name} video source -> {video_source.user_name}")
|
|
19
|
+
|
|
20
|
+
def on_audio_source_changed(self, bay, audio_source):
|
|
21
|
+
print(f"{bay.user_name} audio source -> {audio_source.user_name}")
|
|
22
|
+
|
|
23
|
+
def on_volume_changed(self, bay, volume):
|
|
24
|
+
print(f"{bay.user_name} volume: {volume}")
|
|
25
|
+
|
|
26
|
+
def on_power_changed(self, bay, power):
|
|
27
|
+
print(f"{bay.user_name} power: {power}")
|
|
28
|
+
|
|
29
|
+
def on_device_online_status_changed(self, dev, online):
|
|
30
|
+
print(f"{dev.serial} {'online' if online else 'offline'}")
|
|
31
|
+
|
|
32
|
+
mx = mx_remote.Remote(callbacks=MyCallbacks())
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Available callback methods:
|
|
36
|
+
|
|
37
|
+
| Method | Trigger |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `on_device_update` | any device property changed |
|
|
40
|
+
| `on_bay_update` | any bay property changed |
|
|
41
|
+
| `on_device_config_changed` | device configuration updated |
|
|
42
|
+
| `on_device_config_complete` | all device configuration received |
|
|
43
|
+
| `on_device_online_status_changed` | device went online/offline |
|
|
44
|
+
| `on_device_temperature_changed` | temperature readings changed |
|
|
45
|
+
| `on_bay_registered` | new bay discovered |
|
|
46
|
+
| `on_video_source_changed` | video routing changed |
|
|
47
|
+
| `on_audio_source_changed` | audio routing changed |
|
|
48
|
+
| `on_volume_changed` | volume or mute status changed |
|
|
49
|
+
| `on_power_changed` | CEC power status changed |
|
|
50
|
+
| `on_name_changed` | user-assigned bay name changed |
|
|
51
|
+
| `on_status_signal_detected_changed` | signal detect status changed |
|
|
52
|
+
| `on_status_faulty_changed` | fault status changed |
|
|
53
|
+
| `on_status_hidden_changed` | hidden status changed |
|
|
54
|
+
| `on_status_poe_powered_changed` | PoE power status changed |
|
|
55
|
+
| `on_status_hdbt_connected_changed` | HDBaseT link status changed |
|
|
56
|
+
| `on_status_signal_type_changed` | signal type changed |
|
|
57
|
+
| `on_status_hpd_detected_changed` | hotplug detect changed |
|
|
58
|
+
| `on_status_cec_detected_changed` | CEC device detected/lost |
|
|
59
|
+
| `on_status_arc_changed` | audio return channel status changed |
|
|
60
|
+
| `on_key_pressed` | remote control key press received |
|
|
61
|
+
| `on_action_received` | remote control action received |
|
|
62
|
+
| `on_bay_linked` | virtual link created |
|
|
63
|
+
| `on_bay_unlinked` | virtual link removed |
|
|
64
|
+
| `on_mirror_status_changed` | bay mirroring changed |
|
|
65
|
+
| `on_filter_status_changed` | bay filtering changed |
|
|
66
|
+
| `on_edid_profile_changed` | EDID profile changed |
|
|
67
|
+
| `on_rc_type_changed` | remote control type changed |
|
|
68
|
+
| `on_amp_zone_settings_changed` | amplifier zone settings changed |
|
|
69
|
+
| `on_amp_dolby_settings_changed` | amplifier Dolby settings changed |
|
|
70
|
+
|
|
71
|
+
You can also register per-device and per-bay callbacks:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
def on_device_changed(device):
|
|
75
|
+
print(f"{device.serial} updated")
|
|
76
|
+
|
|
77
|
+
def on_bay_changed(bay):
|
|
78
|
+
print(f"{bay.bay_label} updated")
|
|
79
|
+
|
|
80
|
+
device.register_callback(on_device_changed)
|
|
81
|
+
bay.register_callback(on_bay_changed)
|
|
82
|
+
|
|
83
|
+
# to unregister:
|
|
84
|
+
device.unregister_callback(on_device_changed)
|
|
85
|
+
bay.unregister_callback(on_bay_changed)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
[Documentation index](README.md) | [Project README](https://github.com/opdenkamp/mx-remote#readme)
|