mx-remote 4.7.9__tar.gz → 5.1.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-4.7.9 → mx_remote-5.1.0}/CLAUDE.md +60 -4
- {mx_remote-4.7.9 → mx_remote-5.1.0}/PKG-INFO +60 -17
- {mx_remote-4.7.9 → mx_remote-5.1.0}/README.md +58 -15
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/Interface.py +356 -6
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/Interface.pyi +70 -2
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/const.py +1 -1
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/BayConfig.py +25 -5
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/BayConfig.pyi +3 -1
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/Constants.py +251 -10
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/Constants.pyi +61 -1
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/Factory.py +30 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameAmpZoneSettings.py +53 -5
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameAmpZoneSettings.pyi +2 -0
- mx_remote-5.1.0/mx_remote/proto/FrameAudioClip.py +44 -0
- mx_remote-4.7.9/mx_remote/proto/FrameRCIr.pyi → mx_remote-5.1.0/mx_remote/proto/FrameAudioClip.pyi +3 -2
- mx_remote-5.1.0/mx_remote/proto/FrameAudioSetRoute.py +55 -0
- mx_remote-5.1.0/mx_remote/proto/FrameAudioSetRoute.pyi +14 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameBase.py +62 -6
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameBase.pyi +7 -2
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameBayConfig.py +17 -10
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameBayConfigSecondary.py +13 -10
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameBayHide.py +1 -1
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameBayStatus.py +3 -3
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameBayStatus.pyi +1 -1
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameEDIDProfile.py +1 -1
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameFirmwareVersion.py +13 -8
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameFirmwareVersion.pyi +2 -2
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameHeader.py +4 -1
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameLinks.py +17 -13
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameMeshOperation.py +3 -2
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameMeshOperation.pyi +1 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameNetworkStatus.py +5 -4
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameNetworkStatus.pyi +1 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameRCAction.py +3 -6
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameRCAction.pyi +1 -1
- mx_remote-5.1.0/mx_remote/proto/FrameRCIr.py +85 -0
- mx_remote-5.1.0/mx_remote/proto/FrameRCIr.pyi +23 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameRCKey.py +2 -4
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameRCKey.pyi +1 -1
- mx_remote-5.1.0/mx_remote/proto/FrameRCSettings.py +150 -0
- mx_remote-5.1.0/mx_remote/proto/FrameRCSettings.pyi +28 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameReboot.py +1 -1
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameRoutingChange.py +5 -5
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSetName.py +1 -1
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSetRoute.py +11 -8
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSetupStatus.py +1 -1
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSignalStatusNew.py +68 -27
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSignalStatusNew.pyi +10 -9
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameTXRCAction.py +29 -11
- mx_remote-5.1.0/mx_remote/proto/FrameTXRCKey.py +71 -0
- mx_remote-5.1.0/mx_remote/proto/FrameTXRCKey.pyi +18 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameTxIR.py +15 -6
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPAudio.py +24 -7
- mx_remote-5.1.0/mx_remote/proto/FrameV2IPDetectBays.py +30 -0
- mx_remote-5.1.0/mx_remote/proto/FrameV2IPDetectBays.pyi +7 -0
- mx_remote-5.1.0/mx_remote/proto/FrameV2IPDeviceConfiguration.py +168 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.pyi +7 -2
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.py +1 -1
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPMultiviewer.py +61 -30
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPMultiviewer.pyi +6 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPSourceSwitch.py +1 -1
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPSources.py +11 -1
- mx_remote-5.1.0/mx_remote/proto/FrameV2IPVideoWall.py +155 -0
- mx_remote-5.1.0/mx_remote/proto/FrameV2IPVideoWall.pyi +32 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameVolumeSet.py +1 -1
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/LinkConfig.py +2 -2
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/V2IPConfig.py +3 -1
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/V2IPStats.py +43 -4
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/V2IPStats.pyi +4 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/Bay.py +66 -17
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/Bay.pyi +5 -2
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/ConnectionAsync.py +42 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/Device.py +66 -24
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/Device.pyi +9 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/Remote.py +13 -2
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/Remote.pyi +1 -1
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/V2IP.py +2 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/pyproject.toml +13 -0
- mx_remote-5.1.0/tests/audio_ir.py +41 -0
- mx_remote-5.1.0/tests/capture.py +51 -0
- mx_remote-5.1.0/tests/e2e.py +108 -0
- mx_remote-5.1.0/tests/handlers.py +124 -0
- mx_remote-5.1.0/tests/merge.py +112 -0
- mx_remote-5.1.0/tests/newops.py +102 -0
- mx_remote-5.1.0/tests/offsets.py +59 -0
- mx_remote-5.1.0/tests/proto_stamp.py +60 -0
- mx_remote-5.1.0/tests/protogate.py +85 -0
- mx_remote-5.1.0/tests/rcsettings.py +79 -0
- mx_remote-5.1.0/tests/run.py +59 -0
- mx_remote-5.1.0/tests/smoke.py +45 -0
- mx_remote-5.1.0/tests/sources.py +74 -0
- mx_remote-5.1.0/tests/stats.py +73 -0
- mx_remote-5.1.0/tests/structs.py +93 -0
- mx_remote-5.1.0/tests/txresult.py +198 -0
- mx_remote-5.1.0/tests/unknowns.py +58 -0
- mx_remote-5.1.0/tests/vdetails.py +86 -0
- mx_remote-4.7.9/.claude/settings.local.json +0 -13
- mx_remote-4.7.9/mx_remote/proto/FrameRCIr.py +0 -29
- mx_remote-4.7.9/mx_remote/proto/FrameRCSettings.py +0 -18
- mx_remote-4.7.9/mx_remote/proto/FrameRCSettings.pyi +0 -4
- mx_remote-4.7.9/mx_remote/proto/FrameV2IPDeviceConfiguration.py +0 -108
- {mx_remote-4.7.9 → mx_remote-5.1.0}/.gitignore +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/LICENSE +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/hatch_build.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/Uid.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/Uid.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/__init__.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/__init__.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/const.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/main.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/main.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/Data.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/Data.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/Factory.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameAmpDolbySettings.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameAmpDolbySettings.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameBayConfig.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameBayConfigSecondary.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameBayHide.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameConnectStatus.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameConnectStatus.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameDebug.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameDebug.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameDiscover.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameDiscover.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameEDID.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameEDID.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameEDIDProfile.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameFactoryReset.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameFactoryReset.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameFilterStatus.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameFilterStatus.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameHeader.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameHello.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameHello.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameLinks.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameMirrorStatus.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameMirrorStatus.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameMonitoringPulse.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameMonitoringPulse.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FramePDUState.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FramePDUState.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FramePowerChange.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FramePowerChange.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameReboot.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameRoutingChange.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSetInstaller.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSetInstaller.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSetName.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSetRoute.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSetupStatus.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSignalStatus.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSignalStatus.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSysTemperature.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSysTemperature.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSystemStatus.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSystemStatus.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameTXRCAction.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameTopology.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameTopology.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameTxIR.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameUpgradeFPGA.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameUpgradeFPGA.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPAudio.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPBayMapping.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPBayMapping.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPLink.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPLink.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPPowerSave.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPPowerSave.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPSetMaster.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPSetMaster.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPSourceSwitch.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPSources.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPStats.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPStats.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPStreamDetails.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPStreamDetails.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPTiling.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPTiling.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameVolume.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameVolume.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameVolumeDown.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameVolumeDown.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameVolumeSet.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameVolumeUp.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameVolumeUp.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/LinkConfig.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/Multiviewer.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/Multiviewer.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/PDUState.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/PDUState.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/Svd.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/Svd.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/V2IPConfig.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/__init__.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/__init__.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/svd.csv +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/py.typed +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/ConnectionAsync.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/Link.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/Link.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/P8PDU.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/P8PDU.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/State.py +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/State.pyi +0 -0
- {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/V2IP.pyi +0 -0
|
@@ -21,10 +21,57 @@ mxr # console app: discover devices, log frames live
|
|
|
21
21
|
mxr -l <local_ip> # bind to a specific interface
|
|
22
22
|
mxr -b # broadcast mode instead of multicast
|
|
23
23
|
mxr -i <capture> [-f <ip>] # parse a MatrixOS/Wireshark capture file offline
|
|
24
|
+
python tests/run.py # run every protocol test
|
|
25
|
+
python tests/run.py e2e stats # run named suites only
|
|
24
26
|
```
|
|
25
27
|
|
|
26
|
-
There is
|
|
27
|
-
|
|
28
|
+
There is no lint config in this repo.
|
|
29
|
+
|
|
30
|
+
`tests/` holds standalone scripts with no test framework behind it — each builds
|
|
31
|
+
frames, feeds them through `Remote.process_frame`, and asserts what was decoded.
|
|
32
|
+
A suite prints what it decoded and ends with `ALL OK`, so it can be run on its
|
|
33
|
+
own as well as through `run.py`.
|
|
34
|
+
|
|
35
|
+
Two things they pin that code review does not catch:
|
|
36
|
+
|
|
37
|
+
- **Struct sizes, not just field offsets.** The V2IP stats blocks are 20 and 44
|
|
38
|
+
bytes only because the firmware's `ALIGN(8)` sits where GCC ignores it. Writing
|
|
39
|
+
that declaration the other way round shifts every block after the first, and a
|
|
40
|
+
wrong split still totals 128.
|
|
41
|
+
- **Field widths.** Padding in the fixtures is filled with recognisable bytes, so
|
|
42
|
+
a field read at the right offset with the wrong width returns a wrong value. A
|
|
43
|
+
zero-filled fixture cannot catch that: the widened read returns the same
|
|
44
|
+
answer.
|
|
45
|
+
- **Which handlers run at all.** `tests/handlers.py` reports how many frame
|
|
46
|
+
`process()` methods the other suites execute and fails if one loses its
|
|
47
|
+
coverage. A handler nothing calls is invisible to every other check here,
|
|
48
|
+
since they all measure tests that run.
|
|
49
|
+
|
|
50
|
+
`tests/capture.py` holds real frames off a live mesh, with the device uids
|
|
51
|
+
replaced. Its expected values come from firmware behaviour rather than from this
|
|
52
|
+
decoder, which is the property a synthetic fixture cannot have — assert against
|
|
53
|
+
something external, or a fixture only proves the decoder agrees with itself.
|
|
54
|
+
|
|
55
|
+
Two things a test of a *command* must do, both of which look like coverage
|
|
56
|
+
when they are missing:
|
|
57
|
+
|
|
58
|
+
- **Assert both directions.** A command that returns False for an unrelated
|
|
59
|
+
reason satisfies a failure-only assertion on its own, so the success case is
|
|
60
|
+
what gives the failure case meaning.
|
|
61
|
+
- **Pick an input that can reach the send.** Every multiviewer getter reads
|
|
62
|
+
`UNKNOWN` until a config frame arrives, and each setter returns True without
|
|
63
|
+
transmitting when the value already matches — so a table built from the first
|
|
64
|
+
enum member, which is `UNKNOWN`, never transmits at all.
|
|
65
|
+
|
|
66
|
+
`tests/txresult.py` is the worked example of both.
|
|
67
|
+
|
|
68
|
+
Validate wire changes against live hardware as well, by running `mxr` or
|
|
69
|
+
replaying a capture file with `mxr -i`.
|
|
70
|
+
|
|
71
|
+
A client's uid is persisted at `~/.mxr-uid`, one file per account, and
|
|
72
|
+
`process_frame` drops any frame carrying our own uid. Two clients run from one
|
|
73
|
+
account are a single identity to the mesh and each discards everything the other
|
|
74
|
+
sends, so a second one needs its own `uid_path`.
|
|
28
75
|
|
|
29
76
|
### Versioning
|
|
30
77
|
Version is single-sourced in `mx_remote/const.py` (`VERSION = '...'`); `pyproject.toml`
|
|
@@ -67,8 +114,17 @@ Three layers, decoupled by abstract base classes:
|
|
|
67
114
|
`remote_id == self.uid` (our own echoes) are skipped.
|
|
68
115
|
|
|
69
116
|
### Frame wire format
|
|
70
|
-
|
|
71
|
-
|
|
117
|
+
A 24-byte header, then the payload:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
0 [0x50, 0x38, protocol, 0x00] "P8" magic + protocol version
|
|
121
|
+
4 sender uid 16 bytes, the device that sent this frame
|
|
122
|
+
20 opcode u16 LE
|
|
123
|
+
22 payload length u16 LE
|
|
124
|
+
24 payload
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Built by `proto/Factory.py::create_mxr_frame`, decoded by `proto/FrameHeader.py`.
|
|
72
128
|
|
|
73
129
|
## Working on the protocol
|
|
74
130
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: mx_remote
|
|
3
|
-
Version:
|
|
3
|
+
Version: 5.1.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://github.com/opdenkamp/mx-remote/
|
|
@@ -30,14 +30,39 @@ Requires-Dist: aiohttp>=3.10.5
|
|
|
30
30
|
Requires-Dist: netifaces>=0.11.0
|
|
31
31
|
Description-Content-Type: text/markdown
|
|
32
32
|
|
|
33
|
-
# MX Remote
|
|
33
|
+
# MX Remote — Pulse-Eight device interface
|
|
34
34
|
|
|
35
|
-
Python 3 library for
|
|
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
|
+
## What is MX Remote?
|
|
45
|
+
|
|
46
|
+
MX Remote is the network protocol these Pulse-Eight devices use to discover and control one
|
|
47
|
+
another over UDP (multicast or broadcast). All of them run the shared **MatrixOS** firmware,
|
|
48
|
+
which speaks this protocol natively. This library is a client implementation of that
|
|
49
|
+
protocol — its purpose is to expose the devices to third-party software.
|
|
50
|
+
|
|
51
|
+
## Supported devices
|
|
52
|
+
|
|
53
|
+
All devices below run MatrixOS and are controlled through the same protocol:
|
|
54
|
+
|
|
55
|
+
- **[Pulse-Eight neo](https://www.pulse-eight.com/)** — HDBaseT video/audio matrices
|
|
56
|
+
(neo:4, neo:8, neo:X, and splitters)
|
|
57
|
+
- **[Pulse-Eight OneIP](https://www.pulse-eight.com/p/248/oneip-tx)** — HDMI-over-IP
|
|
58
|
+
units: Transmitter (TX), Receiver (RX), Transceiver (TZ), and Multiviewer
|
|
59
|
+
- **[Pulse-Eight ProAmp8](https://www.pulse-eight.com/p/219/proamp-8)** — 8-zone audio amplifier with
|
|
60
|
+
Dolby support
|
|
36
61
|
|
|
37
62
|
## Requirements
|
|
38
63
|
|
|
39
64
|
- Python 3.11 or later
|
|
40
|
-
- Network access to
|
|
65
|
+
- Network access to one or more of the Pulse-Eight devices above (multicast or broadcast)
|
|
41
66
|
|
|
42
67
|
## Installation
|
|
43
68
|
|
|
@@ -91,11 +116,19 @@ mx = mx_remote.Remote(target_ip="10.8.8.255", port=8811)
|
|
|
91
116
|
|
|
92
117
|
# offline mode for processing capture files
|
|
93
118
|
mx = mx_remote.Remote(open_connection=False)
|
|
119
|
+
|
|
120
|
+
# separate identity, to run concurrent clients from one account
|
|
121
|
+
mx = mx_remote.Remote(uid_path="/var/lib/myapp/.mxr-uid")
|
|
94
122
|
```
|
|
95
123
|
|
|
124
|
+
The uid at `uid_path` (default `~/.mxr-uid`) is this client's identity on the mesh:
|
|
125
|
+
addressing is by uid in the payload, and a frame carrying our own uid is dropped as
|
|
126
|
+
an echo. Two clients sharing a uid are one peer to every device and each silently
|
|
127
|
+
discards everything the other sends, so give concurrent clients separate paths.
|
|
128
|
+
|
|
96
129
|
### Device
|
|
97
130
|
|
|
98
|
-
A `DeviceBase` represents a physical device on the network (matrix, OneIP unit, amplifier). Devices are automatically registered when they respond to discovery requests.
|
|
131
|
+
A `DeviceBase` represents a physical device on the network (a Pulse-Eight neo matrix, OneIP unit, or ProAmp8 amplifier). Devices are automatically registered when they respond to discovery requests.
|
|
99
132
|
|
|
100
133
|
```python
|
|
101
134
|
# look up a device by serial number or unique ID
|
|
@@ -111,17 +144,23 @@ device.version # firmware version
|
|
|
111
144
|
device.online # True if responding
|
|
112
145
|
device.status # DeviceStatus enum (ONLINE, OFFLINE, REBOOTING, BOOTING, INACTIVE)
|
|
113
146
|
device.features # DeviceFeatures bitmask
|
|
147
|
+
device.config_initialised # False if the device broadcasts config blocks built from
|
|
148
|
+
# uninitialised memory; scaling, bay 0 addresses and the
|
|
149
|
+
# rc target padding are unreliable from it
|
|
114
150
|
device.temperatures # dict of temperature sensor readings
|
|
115
151
|
|
|
116
152
|
# device type checks
|
|
117
|
-
device.is_v2ip # OneIP device
|
|
118
|
-
device.is_video_matrix # video matrix
|
|
153
|
+
device.is_v2ip # Pulse-Eight OneIP HDMI-over-IP device
|
|
154
|
+
device.is_video_matrix # neo video matrix
|
|
119
155
|
device.is_audio_matrix # audio-only matrix
|
|
120
|
-
device.is_amp # audio amplifier
|
|
156
|
+
device.is_amp # ProAmp8 audio amplifier
|
|
121
157
|
device.is_oneip_tx # OneIP transmitter
|
|
122
158
|
device.is_oneip_rx # OneIP receiver
|
|
123
159
|
device.is_oneip_tz # OneIP transceiver
|
|
124
160
|
device.is_oneip_multiviewer # OneIP multiviewer
|
|
161
|
+
device.supports_video_wall # sink can crop its source to a video wall window
|
|
162
|
+
# (advertised ~1s after boot by the v2ipwall module,
|
|
163
|
+
# and never withdrawn once advertised)
|
|
125
164
|
|
|
126
165
|
# iterate bays
|
|
127
166
|
for port, bay in device.bays.items():
|
|
@@ -336,24 +375,28 @@ log = await device.get_log()
|
|
|
336
375
|
result = await device.get_api("system/status")
|
|
337
376
|
```
|
|
338
377
|
|
|
339
|
-
## OneIP
|
|
378
|
+
## Pulse-Eight OneIP Devices
|
|
340
379
|
|
|
341
|
-
OneIP devices expose additional streaming properties:
|
|
380
|
+
[Pulse-Eight OneIP](https://www.pulse-eight.com/p/248/oneip-tx) HDMI-over-IP devices expose additional streaming properties:
|
|
342
381
|
|
|
343
382
|
```python
|
|
344
|
-
#
|
|
383
|
+
# stream source addresses
|
|
345
384
|
if device.is_v2ip and device.v2ip_sources:
|
|
346
385
|
for source in device.v2ip_sources:
|
|
347
386
|
print(f"Video: {source.video.ip}:{source.video.port}")
|
|
348
387
|
print(f"Audio: {source.audio.ip}:{source.audio.port}")
|
|
349
388
|
|
|
350
|
-
#
|
|
389
|
+
# stream details (encoder/decoder config)
|
|
351
390
|
if device.v2ip_details:
|
|
352
391
|
details = device.v2ip_details
|
|
353
392
|
print(f"Video: {details.video}")
|
|
354
|
-
print(f"TX rate: {details.tx_rate}")
|
|
393
|
+
print(f"TX rate: {details.tx_rate}") # None when the sender offered no rate
|
|
394
|
+
|
|
395
|
+
# per-stream DSCP marking (None from peers that predate it)
|
|
396
|
+
if details.dscp and details.dscp.complete:
|
|
397
|
+
print(f"DSCP: {details.dscp}") # video / audio / anc, 0..63
|
|
355
398
|
|
|
356
|
-
#
|
|
399
|
+
# streaming statistics
|
|
357
400
|
await device.read_stats(enable=True) # start collecting
|
|
358
401
|
# ... later ...
|
|
359
402
|
stats = device.v2ip_stats
|
|
@@ -368,9 +411,9 @@ if device.v2ip_firmware_versions:
|
|
|
368
411
|
print(f"{fw_type}: {fw.version}")
|
|
369
412
|
```
|
|
370
413
|
|
|
371
|
-
## OneIP Multiviewer
|
|
414
|
+
## Pulse-Eight OneIP Multiviewer
|
|
372
415
|
|
|
373
|
-
Control
|
|
416
|
+
Control [OneIP Multiviewer](https://www.pulse-eight.com/p/248/oneip-tx)-specific settings:
|
|
374
417
|
|
|
375
418
|
```python
|
|
376
419
|
from mx_remote import (
|
|
@@ -1,11 +1,36 @@
|
|
|
1
|
-
# MX Remote
|
|
1
|
+
# MX Remote — Pulse-Eight device interface
|
|
2
2
|
|
|
3
|
-
Python 3 library for
|
|
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
|
+
## What is MX Remote?
|
|
13
|
+
|
|
14
|
+
MX Remote is the network protocol these Pulse-Eight devices use to discover and control one
|
|
15
|
+
another over UDP (multicast or broadcast). All of them run the shared **MatrixOS** firmware,
|
|
16
|
+
which speaks this protocol natively. This library is a client implementation of that
|
|
17
|
+
protocol — its purpose is to expose the devices to third-party software.
|
|
18
|
+
|
|
19
|
+
## Supported devices
|
|
20
|
+
|
|
21
|
+
All devices below run MatrixOS and are controlled through the same protocol:
|
|
22
|
+
|
|
23
|
+
- **[Pulse-Eight neo](https://www.pulse-eight.com/)** — HDBaseT video/audio matrices
|
|
24
|
+
(neo:4, neo:8, neo:X, and splitters)
|
|
25
|
+
- **[Pulse-Eight OneIP](https://www.pulse-eight.com/p/248/oneip-tx)** — HDMI-over-IP
|
|
26
|
+
units: Transmitter (TX), Receiver (RX), Transceiver (TZ), and Multiviewer
|
|
27
|
+
- **[Pulse-Eight ProAmp8](https://www.pulse-eight.com/p/219/proamp-8)** — 8-zone audio amplifier with
|
|
28
|
+
Dolby support
|
|
4
29
|
|
|
5
30
|
## Requirements
|
|
6
31
|
|
|
7
32
|
- Python 3.11 or later
|
|
8
|
-
- Network access to
|
|
33
|
+
- Network access to one or more of the Pulse-Eight devices above (multicast or broadcast)
|
|
9
34
|
|
|
10
35
|
## Installation
|
|
11
36
|
|
|
@@ -59,11 +84,19 @@ mx = mx_remote.Remote(target_ip="10.8.8.255", port=8811)
|
|
|
59
84
|
|
|
60
85
|
# offline mode for processing capture files
|
|
61
86
|
mx = mx_remote.Remote(open_connection=False)
|
|
87
|
+
|
|
88
|
+
# separate identity, to run concurrent clients from one account
|
|
89
|
+
mx = mx_remote.Remote(uid_path="/var/lib/myapp/.mxr-uid")
|
|
62
90
|
```
|
|
63
91
|
|
|
92
|
+
The uid at `uid_path` (default `~/.mxr-uid`) is this client's identity on the mesh:
|
|
93
|
+
addressing is by uid in the payload, and a frame carrying our own uid is dropped as
|
|
94
|
+
an echo. Two clients sharing a uid are one peer to every device and each silently
|
|
95
|
+
discards everything the other sends, so give concurrent clients separate paths.
|
|
96
|
+
|
|
64
97
|
### Device
|
|
65
98
|
|
|
66
|
-
A `DeviceBase` represents a physical device on the network (matrix, OneIP unit, amplifier). Devices are automatically registered when they respond to discovery requests.
|
|
99
|
+
A `DeviceBase` represents a physical device on the network (a Pulse-Eight neo matrix, OneIP unit, or ProAmp8 amplifier). Devices are automatically registered when they respond to discovery requests.
|
|
67
100
|
|
|
68
101
|
```python
|
|
69
102
|
# look up a device by serial number or unique ID
|
|
@@ -79,17 +112,23 @@ device.version # firmware version
|
|
|
79
112
|
device.online # True if responding
|
|
80
113
|
device.status # DeviceStatus enum (ONLINE, OFFLINE, REBOOTING, BOOTING, INACTIVE)
|
|
81
114
|
device.features # DeviceFeatures bitmask
|
|
115
|
+
device.config_initialised # False if the device broadcasts config blocks built from
|
|
116
|
+
# uninitialised memory; scaling, bay 0 addresses and the
|
|
117
|
+
# rc target padding are unreliable from it
|
|
82
118
|
device.temperatures # dict of temperature sensor readings
|
|
83
119
|
|
|
84
120
|
# device type checks
|
|
85
|
-
device.is_v2ip # OneIP device
|
|
86
|
-
device.is_video_matrix # video matrix
|
|
121
|
+
device.is_v2ip # Pulse-Eight OneIP HDMI-over-IP device
|
|
122
|
+
device.is_video_matrix # neo video matrix
|
|
87
123
|
device.is_audio_matrix # audio-only matrix
|
|
88
|
-
device.is_amp # audio amplifier
|
|
124
|
+
device.is_amp # ProAmp8 audio amplifier
|
|
89
125
|
device.is_oneip_tx # OneIP transmitter
|
|
90
126
|
device.is_oneip_rx # OneIP receiver
|
|
91
127
|
device.is_oneip_tz # OneIP transceiver
|
|
92
128
|
device.is_oneip_multiviewer # OneIP multiviewer
|
|
129
|
+
device.supports_video_wall # sink can crop its source to a video wall window
|
|
130
|
+
# (advertised ~1s after boot by the v2ipwall module,
|
|
131
|
+
# and never withdrawn once advertised)
|
|
93
132
|
|
|
94
133
|
# iterate bays
|
|
95
134
|
for port, bay in device.bays.items():
|
|
@@ -304,24 +343,28 @@ log = await device.get_log()
|
|
|
304
343
|
result = await device.get_api("system/status")
|
|
305
344
|
```
|
|
306
345
|
|
|
307
|
-
## OneIP
|
|
346
|
+
## Pulse-Eight OneIP Devices
|
|
308
347
|
|
|
309
|
-
OneIP devices expose additional streaming properties:
|
|
348
|
+
[Pulse-Eight OneIP](https://www.pulse-eight.com/p/248/oneip-tx) HDMI-over-IP devices expose additional streaming properties:
|
|
310
349
|
|
|
311
350
|
```python
|
|
312
|
-
#
|
|
351
|
+
# stream source addresses
|
|
313
352
|
if device.is_v2ip and device.v2ip_sources:
|
|
314
353
|
for source in device.v2ip_sources:
|
|
315
354
|
print(f"Video: {source.video.ip}:{source.video.port}")
|
|
316
355
|
print(f"Audio: {source.audio.ip}:{source.audio.port}")
|
|
317
356
|
|
|
318
|
-
#
|
|
357
|
+
# stream details (encoder/decoder config)
|
|
319
358
|
if device.v2ip_details:
|
|
320
359
|
details = device.v2ip_details
|
|
321
360
|
print(f"Video: {details.video}")
|
|
322
|
-
print(f"TX rate: {details.tx_rate}")
|
|
361
|
+
print(f"TX rate: {details.tx_rate}") # None when the sender offered no rate
|
|
362
|
+
|
|
363
|
+
# per-stream DSCP marking (None from peers that predate it)
|
|
364
|
+
if details.dscp and details.dscp.complete:
|
|
365
|
+
print(f"DSCP: {details.dscp}") # video / audio / anc, 0..63
|
|
323
366
|
|
|
324
|
-
#
|
|
367
|
+
# streaming statistics
|
|
325
368
|
await device.read_stats(enable=True) # start collecting
|
|
326
369
|
# ... later ...
|
|
327
370
|
stats = device.v2ip_stats
|
|
@@ -336,9 +379,9 @@ if device.v2ip_firmware_versions:
|
|
|
336
379
|
print(f"{fw_type}: {fw.version}")
|
|
337
380
|
```
|
|
338
381
|
|
|
339
|
-
## OneIP Multiviewer
|
|
382
|
+
## Pulse-Eight OneIP Multiviewer
|
|
340
383
|
|
|
341
|
-
Control
|
|
384
|
+
Control [OneIP Multiviewer](https://www.pulse-eight.com/p/248/oneip-tx)-specific settings:
|
|
342
385
|
|
|
343
386
|
```python
|
|
344
387
|
from mx_remote import (
|