mx-remote 9.2.0__tar.gz → 9.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mx_remote-9.2.0 → mx_remote-9.3.0}/PKG-INFO +1 -1
- {mx_remote-9.2.0 → mx_remote-9.3.0}/docs/api.md +17 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/docs/callbacks.md +25 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/Interface.py +69 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/Interface.pyi +18 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/const.py +1 -1
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/Constants.py +18 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/Constants.pyi +5 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/Factory.py +3 -0
- mx_remote-9.3.0/mx_remote/proto/FrameThirdParty.py +50 -0
- mx_remote-9.3.0/mx_remote/proto/FrameThirdParty.pyi +15 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/remote/Remote.py +20 -2
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/remote/Remote.pyi +4 -2
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/handlers.py +1 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/run.py +1 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/smoke.py +1 -0
- mx_remote-9.3.0/tests/thirdparty.py +146 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/.gitignore +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/AGENTS.md +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/CLAUDE.md +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/LICENSE +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/README.md +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/docs/README.md +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/docs/_static/brand.css +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/docs/_static/favicon.svg +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/docs/_static/logo-dark.svg +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/docs/_static/logo.svg +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/docs/audio.md +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/docs/conf.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/docs/configuration.md +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/docs/devices-and-bays.md +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/docs/diagnostics.md +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/docs/index.md +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/docs/multiviewer.md +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/docs/oneip.md +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/docs/requirements.txt +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/docs/routing.md +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/hatch_build.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/Uid.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/Uid.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/__init__.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/__init__.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/compat.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/compat.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/const.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/deprecated.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/deprecated.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/main.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/main.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/BayConfig.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/BayConfig.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/Data.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/Data.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/Factory.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameAmpDolbySettings.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameAmpDolbySettings.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameAmpZoneSettings.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameAmpZoneSettings.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameAudioClip.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameAudioClip.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameAudioSetRoute.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameAudioSetRoute.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameBase.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameBase.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameBayConfig.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameBayConfig.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameBayConfigSecondary.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameBayConfigSecondary.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameBayContent.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameBayContent.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameBayHide.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameBayHide.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameBayStatus.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameBayStatus.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameConnectStatus.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameConnectStatus.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameDebug.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameDebug.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameDiscover.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameDiscover.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameEDID.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameEDID.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameEDIDProfile.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameEDIDProfile.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameFactoryReset.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameFactoryReset.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameFilterStatus.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameFilterStatus.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameFirmwareVersion.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameFirmwareVersion.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameHeader.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameHeader.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameHello.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameHello.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameLinks.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameLinks.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameMeshOperation.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameMeshOperation.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameMirrorStatus.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameMirrorStatus.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameMonitoringPulse.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameMonitoringPulse.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameNetworkStatus.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameNetworkStatus.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FramePing.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FramePing.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FramePowerChange.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FramePowerChange.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameRCAction.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameRCAction.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameRCIr.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameRCIr.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameRCKey.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameRCKey.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameRCSettings.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameRCSettings.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameReboot.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameReboot.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameRoutingChange.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameRoutingChange.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameSetInstaller.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameSetInstaller.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameSetName.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameSetName.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameSetRoute.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameSetRoute.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameSetupStatus.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameSetupStatus.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameSignalStatus.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameSignalStatus.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameSignalStatusNew.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameSignalStatusNew.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameSysTemperature.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameSysTemperature.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameSystemStatus.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameSystemStatus.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameTXRCAction.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameTXRCAction.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameTXRCKey.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameTXRCKey.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameTime.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameTime.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameTimeZone.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameTimeZone.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameTopology.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameTopology.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameTxIR.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameTxIR.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameUnitCommand.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameUnitCommand.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameUnitStatus.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameUnitStatus.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameUpgradeFPGA.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameUpgradeFPGA.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPAudio.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPAudio.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPBayMapping.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPBayMapping.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPDetectBays.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPDetectBays.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPLink.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPLink.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPMultiviewer.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPMultiviewer.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPOsdSync.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPOsdSync.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPPowerSave.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPPowerSave.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPPrivacy.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPPrivacy.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPSetMaster.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPSetMaster.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPSettingsAll.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPSettingsAll.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPSinkSdp.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPSinkSdp.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPSourceSwitch.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPSourceSwitch.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPSources.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPSources.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPStats.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPStats.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPTestcard.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPTestcard.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPTiling.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPTiling.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPVideoWall.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameV2IPVideoWall.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameVolume.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameVolume.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameVolumeDown.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameVolumeDown.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameVolumeSet.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameVolumeSet.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameVolumeUp.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/FrameVolumeUp.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/LinkConfig.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/LinkConfig.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/Multiviewer.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/Multiviewer.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/Svd.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/Svd.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/V2IPConfig.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/V2IPConfig.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/V2IPStats.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/V2IPStats.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/__init__.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/__init__.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/proto/svd.csv +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/py.typed +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/remote/Bay.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/remote/Bay.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/remote/ConnectionAsync.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/remote/ConnectionAsync.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/remote/Device.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/remote/Device.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/remote/Link.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/remote/Link.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/remote/OsdSync.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/remote/OsdSync.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/remote/State.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/remote/State.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/remote/V2IP.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/mx_remote/remote/V2IP.pyi +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/pyproject.toml +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/audio_ir.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/audiolock.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/baycontent.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/capture.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/complete.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/deprecated.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/downmix.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/e2e.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/entry.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/handlers_cover.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/hello.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/idempotent.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/lengths.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/merge.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/mesh.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/meshbays.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/newops.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/offline.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/offsets.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/osdmessage.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/osdsync.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/ping.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/privacy.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/proto_stamp.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/protogate.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/rcsettings.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/roundtrip.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/routes.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/sources.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/staterequest.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/stats.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/structs.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/txresult.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/uid.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/unit.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/unknowns.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/v2ipptp.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/v2ipscaling.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/v2ipsdp.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/v2ipsettings.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/v2ipsubject.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/v2iptestcard.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/v2ipvlan.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/vdetails.py +0 -0
- {mx_remote-9.2.0 → mx_remote-9.3.0}/tests/wirefix.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: mx_remote
|
|
3
|
-
Version: 9.
|
|
3
|
+
Version: 9.3.0
|
|
4
4
|
Summary: Python 3 library for interfacing with MX Remote compatible devices
|
|
5
5
|
Project-URL: Homepage, https://github.com/opdenkamp/mx-remote/
|
|
6
6
|
Project-URL: Documentation, https://opdenkamp.github.io/mx-remote/
|
|
@@ -288,6 +288,23 @@ How a source unit encrypts its streams, as it reports it.
|
|
|
288
288
|
:members:
|
|
289
289
|
```
|
|
290
290
|
|
|
291
|
+
## Application messages
|
|
292
|
+
|
|
293
|
+
Messages applications exchange over the mesh under their own vendor id.
|
|
294
|
+
|
|
295
|
+
```{eval-rst}
|
|
296
|
+
.. autoclass:: mx_remote.ThirdPartyMessage
|
|
297
|
+
:members:
|
|
298
|
+
|
|
299
|
+
.. autoclass:: mx_remote.ThirdPartyVendor
|
|
300
|
+
:members:
|
|
301
|
+
|
|
302
|
+
.. autoclass:: mx_remote.proto.Constants.ThirdPartyVendorFlag
|
|
303
|
+
:members:
|
|
304
|
+
|
|
305
|
+
.. autodata:: mx_remote.proto.Constants.THIRD_PARTY_PAYLOAD_MAX
|
|
306
|
+
```
|
|
307
|
+
|
|
291
308
|
## OneIP test card
|
|
292
309
|
|
|
293
310
|
A sink's test pattern, tone and lip-sync flash, and the ranges it accepts.
|
|
@@ -67,6 +67,7 @@ Available callback methods:
|
|
|
67
67
|
| `on_rc_type_changed` | remote control type changed |
|
|
68
68
|
| `on_amp_zone_settings_changed` | amplifier zone settings changed |
|
|
69
69
|
| `on_amp_dolby_settings_changed` | amplifier Dolby settings changed |
|
|
70
|
+
| `on_third_party_message` | another application sent a message under its vendor id |
|
|
70
71
|
|
|
71
72
|
You can also register per-device and per-bay callbacks:
|
|
72
73
|
|
|
@@ -85,6 +86,30 @@ device.unregister_callback(on_device_changed)
|
|
|
85
86
|
bay.unregister_callback(on_bay_changed)
|
|
86
87
|
```
|
|
87
88
|
|
|
89
|
+
## Application messages
|
|
90
|
+
|
|
91
|
+
Applications built on this library can message each other over the mesh. A
|
|
92
|
+
message carries a vendor id, then up to `THIRD_PARTY_PAYLOAD_MAX` (1444) bytes
|
|
93
|
+
that only the application reads. The vendor id is the vendor's IEEE OUI, or an
|
|
94
|
+
identifier it chose, such as three letters:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from mx_remote import ThirdPartyVendor
|
|
98
|
+
|
|
99
|
+
ODK = ThirdPartyVendor.private(b'ODK')
|
|
100
|
+
|
|
101
|
+
await mx.send_third_party(ODK, b'hello') # False when over 1444 bytes
|
|
102
|
+
|
|
103
|
+
class MyCallbacks(mx_remote.MxrCallbacks):
|
|
104
|
+
def on_third_party_message(self, dev, message):
|
|
105
|
+
if message.vendor.same_vendor(ODK):
|
|
106
|
+
print(f"{dev} says {message.payload!r}")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Every other client that has heard this one announce itself receives the
|
|
110
|
+
message; this client does not. No unit reads it, and nothing acknowledges it,
|
|
111
|
+
so an application that needs to know a message arrived has its peers answer.
|
|
112
|
+
|
|
88
113
|
---
|
|
89
114
|
|
|
90
115
|
[Documentation index](README.md) | [Project README](https://github.com/opdenkamp/mx-remote#readme)
|
|
@@ -2601,6 +2601,68 @@ class OneIPPrivacy:
|
|
|
2601
2601
|
def __str__(self) -> str:
|
|
2602
2602
|
return f"video {self.video}, audio {self.audio}, anc {self.anc}"
|
|
2603
2603
|
|
|
2604
|
+
@dataclass(frozen=True)
|
|
2605
|
+
class ThirdPartyVendor:
|
|
2606
|
+
"""
|
|
2607
|
+
Whose message a third-party message is: a flag byte, then three bytes
|
|
2608
|
+
naming the vendor.
|
|
2609
|
+
|
|
2610
|
+
The three bytes are the vendor's IEEE OUI, in the order it is written, or
|
|
2611
|
+
with ThirdPartyVendorFlag.NOT_OUI set an identifier of the vendor's own
|
|
2612
|
+
choosing, such as three ASCII letters. The flag keeps the two spaces apart,
|
|
2613
|
+
so a chosen identifier never collides with a registered OUI.
|
|
2614
|
+
"""
|
|
2615
|
+
flags: ThirdPartyVendorFlag
|
|
2616
|
+
"""The flag byte, at offset 0."""
|
|
2617
|
+
id: bytes
|
|
2618
|
+
"""The vendor's three identifying bytes, at offsets 1 to 3."""
|
|
2619
|
+
|
|
2620
|
+
def __post_init__(self) -> None:
|
|
2621
|
+
if (len(self.id) != 3):
|
|
2622
|
+
raise ValueError(f"a vendor id is 3 bytes, not {len(self.id)}")
|
|
2623
|
+
|
|
2624
|
+
@classmethod
|
|
2625
|
+
def oui(cls, oui:bytes) -> 'ThirdPartyVendor':
|
|
2626
|
+
"""A vendor named by its IEEE OUI."""
|
|
2627
|
+
return cls(flags=ThirdPartyVendorFlag(0), id=bytes(oui))
|
|
2628
|
+
|
|
2629
|
+
@classmethod
|
|
2630
|
+
def private(cls, id:bytes) -> 'ThirdPartyVendor':
|
|
2631
|
+
"""A vendor named by an identifier it chose, rather than an OUI."""
|
|
2632
|
+
return cls(flags=ThirdPartyVendorFlag.NOT_OUI, id=bytes(id))
|
|
2633
|
+
|
|
2634
|
+
def same_vendor(self, other:'ThirdPartyVendor') -> bool:
|
|
2635
|
+
"""Whether other names the same vendor.
|
|
2636
|
+
|
|
2637
|
+
Only ThirdPartyVendorFlag.NOT_OUI and the three identifying bytes say
|
|
2638
|
+
who the vendor is, so the other flag bits are left out."""
|
|
2639
|
+
return ((ThirdPartyVendorFlag.NOT_OUI in self.flags) == (ThirdPartyVendorFlag.NOT_OUI in other.flags)) \
|
|
2640
|
+
and (self.id == other.id)
|
|
2641
|
+
|
|
2642
|
+
def __str__(self) -> str:
|
|
2643
|
+
if (ThirdPartyVendorFlag.NOT_OUI in self.flags):
|
|
2644
|
+
return f"vendor {self.id!r}"
|
|
2645
|
+
return f"vendor OUI {self.id.hex(':')}"
|
|
2646
|
+
|
|
2647
|
+
@dataclass(frozen=True)
|
|
2648
|
+
class ThirdPartyMessage:
|
|
2649
|
+
"""
|
|
2650
|
+
A message an application sent to every device, under its own vendor id.
|
|
2651
|
+
|
|
2652
|
+
The library reads only the vendor id. What follows is the application's,
|
|
2653
|
+
and arrives exactly as it was sent.
|
|
2654
|
+
"""
|
|
2655
|
+
vendor: ThirdPartyVendor
|
|
2656
|
+
"""Which application's message this is. The library checks none: each
|
|
2657
|
+
application ignores every message whose vendor is not its own."""
|
|
2658
|
+
payload: bytes
|
|
2659
|
+
"""The application's bytes, at most THIRD_PARTY_PAYLOAD_MAX."""
|
|
2660
|
+
address: str|None
|
|
2661
|
+
"""Where the datagram came from."""
|
|
2662
|
+
|
|
2663
|
+
def __str__(self) -> str:
|
|
2664
|
+
return f"{self.vendor}: {len(self.payload)} bytes"
|
|
2665
|
+
|
|
2604
2666
|
@dataclass(frozen=True)
|
|
2605
2667
|
class OneIPTestTone:
|
|
2606
2668
|
"""A OneIP sink's test tone."""
|
|
@@ -4370,6 +4432,13 @@ class MxrCallbacks:
|
|
|
4370
4432
|
_LOGGER.debug(f"{device} dolby settings changed")
|
|
4371
4433
|
self.on_device_update(device)
|
|
4372
4434
|
|
|
4435
|
+
def on_third_party_message(self, dev:DeviceBase, message:ThirdPartyMessage) -> None:
|
|
4436
|
+
''' called when the application on 'dev' sent a message under its own
|
|
4437
|
+
vendor id. A message from a sender this client has not yet heard
|
|
4438
|
+
announce itself is dropped, as every frame is, and this client never
|
|
4439
|
+
receives its own. '''
|
|
4440
|
+
pass
|
|
4441
|
+
|
|
4373
4442
|
# The V2IP spelling of a name here, from before the rename to OneIP.
|
|
4374
4443
|
from .deprecated import alias_members as _alias_members, module_getattr as _module_getattr
|
|
4375
4444
|
# Installed through globals() so the generated stubs carry no __getattr__, which
|
|
@@ -930,6 +930,23 @@ class OneIPPrivacy:
|
|
|
930
930
|
audio: OneIPPrivacyStream
|
|
931
931
|
anc: OneIPPrivacyStream
|
|
932
932
|
|
|
933
|
+
@dataclass(frozen=True)
|
|
934
|
+
class ThirdPartyVendor:
|
|
935
|
+
flags: ThirdPartyVendorFlag
|
|
936
|
+
id: bytes
|
|
937
|
+
def __post_init__(self) -> None: ...
|
|
938
|
+
@classmethod
|
|
939
|
+
def oui(cls, oui: bytes) -> ThirdPartyVendor: ...
|
|
940
|
+
@classmethod
|
|
941
|
+
def private(cls, id: bytes) -> ThirdPartyVendor: ...
|
|
942
|
+
def same_vendor(self, other: ThirdPartyVendor) -> bool: ...
|
|
943
|
+
|
|
944
|
+
@dataclass(frozen=True)
|
|
945
|
+
class ThirdPartyMessage:
|
|
946
|
+
vendor: ThirdPartyVendor
|
|
947
|
+
payload: bytes
|
|
948
|
+
address: str | None
|
|
949
|
+
|
|
933
950
|
@dataclass(frozen=True)
|
|
934
951
|
class OneIPTestTone:
|
|
935
952
|
mode: OneIPToneMode | int = ...
|
|
@@ -1620,3 +1637,4 @@ class MxrCallbacks:
|
|
|
1620
1637
|
def on_rc_type_changed(self, bay: BayBase, rc_type: RCType | None) -> None: ...
|
|
1621
1638
|
def on_amp_zone_settings_changed(self, bay: BayBase, settings: AmpZoneSettings) -> None: ...
|
|
1622
1639
|
def on_amp_dolby_settings_changed(self, device: DeviceBase, settings: AmpDolbySettings) -> None: ...
|
|
1640
|
+
def on_third_party_message(self, dev: DeviceBase, message: ThirdPartyMessage) -> None: ...
|
|
@@ -123,6 +123,9 @@ MXR_OPCODE_VERSIONS: dict[int, int] = {
|
|
|
123
123
|
0x52: 0x2E, # V2IP_SINK_SDP
|
|
124
124
|
0x53: 0x2F, # V2IP_OSD_SYNC
|
|
125
125
|
0x54: 0x30, # V2IP_PRIVACY
|
|
126
|
+
# The lowest stamp, so that every receiver's ceiling passes it. No device
|
|
127
|
+
# decodes the payload, so there is no layout for a stamp to announce.
|
|
128
|
+
0x55: 0x01, # THIRD_PARTY
|
|
126
129
|
}
|
|
127
130
|
"""Per-opcode minimum compatible protocol version, mirroring the firmware's
|
|
128
131
|
own opcode table. Transmitters stamp the
|
|
@@ -822,6 +825,21 @@ ONEIP_VLAN_PORT_SFP = 0
|
|
|
822
825
|
ONEIP_VLAN_ID_MAX = 4094
|
|
823
826
|
"""The highest VLAN id: 0 means untagged, and 4095 is reserved by IEEE 802.1Q."""
|
|
824
827
|
|
|
828
|
+
class ThirdPartyVendorFlag(IntFlag):
|
|
829
|
+
'''The first byte of a third-party vendor id, ahead of its three
|
|
830
|
+
identifying bytes. Bits without a name are kept as they arrived.'''
|
|
831
|
+
NOT_OUI = (1 << 0)
|
|
832
|
+
'''The three bytes are an identifier the vendor chose, rather than its
|
|
833
|
+
IEEE OUI.'''
|
|
834
|
+
|
|
835
|
+
THIRD_PARTY_PAYLOAD_MAX = 1444
|
|
836
|
+
"""The largest payload a third-party message carries.
|
|
837
|
+
|
|
838
|
+
A frame this size fills one 1500-byte Ethernet frame exactly: 20 bytes of IPv4
|
|
839
|
+
header, 8 of UDP, the 24-byte frame header and the 4-byte vendor id leave 1444.
|
|
840
|
+
A larger send is refused rather than fragmented, because a lost fragment loses
|
|
841
|
+
the whole datagram and nothing on these paths retries."""
|
|
842
|
+
|
|
825
843
|
BAY_FEATURE_DOLBY_IN_POS = 24
|
|
826
844
|
|
|
827
845
|
class BayFeaturesMask(IntFlag):
|
|
@@ -340,6 +340,11 @@ class OneIPVlanFlag(IntFlag):
|
|
|
340
340
|
ONEIP_VLAN_PORTS: int
|
|
341
341
|
ONEIP_VLAN_PORT_SFP: int
|
|
342
342
|
ONEIP_VLAN_ID_MAX: int
|
|
343
|
+
|
|
344
|
+
class ThirdPartyVendorFlag(IntFlag):
|
|
345
|
+
NOT_OUI: Incomplete
|
|
346
|
+
|
|
347
|
+
THIRD_PARTY_PAYLOAD_MAX: int
|
|
343
348
|
BAY_FEATURE_DOLBY_IN_POS: int
|
|
344
349
|
|
|
345
350
|
class BayFeaturesMask(IntFlag):
|
|
@@ -249,6 +249,9 @@ def _mxr_frame_factory(hdr:FrameHeader, timestamp:float) -> FrameBase|None:
|
|
|
249
249
|
if hdr.opcode == 0x54:
|
|
250
250
|
from .FrameV2IPPrivacy import FrameV2IPPrivacy
|
|
251
251
|
return FrameV2IPPrivacy(header=hdr, timestamp=timestamp)
|
|
252
|
+
if hdr.opcode == 0x55:
|
|
253
|
+
from .FrameThirdParty import FrameThirdParty
|
|
254
|
+
return FrameThirdParty(header=hdr, timestamp=timestamp)
|
|
252
255
|
if hdr.opcode == 0x50:
|
|
253
256
|
from .FrameUnitCommand import FrameUnitCommand
|
|
254
257
|
return FrameUnitCommand(header=hdr, timestamp=timestamp)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
######################################################
|
|
2
|
+
## MX Remote Python Interface ##
|
|
3
|
+
## ##
|
|
4
|
+
## author: Lars Op den Kamp (lars@opdenkamp-it.nl) ##
|
|
5
|
+
## copyright (c) 2021-2026 Op den Kamp IT Solutions ##
|
|
6
|
+
######################################################
|
|
7
|
+
'''Protocol frame for THIRD_PARTY (opcode 0x55): messages applications
|
|
8
|
+
exchange over the mesh.'''
|
|
9
|
+
|
|
10
|
+
from functools import cached_property
|
|
11
|
+
from ..compat import override
|
|
12
|
+
from .Constants import ThirdPartyVendorFlag
|
|
13
|
+
from .FrameBase import FrameBase
|
|
14
|
+
from ..Interface import DeviceRegistry, ThirdPartyMessage, ThirdPartyVendor
|
|
15
|
+
|
|
16
|
+
# The flag byte at 0 and the three identifying bytes at 1, then the
|
|
17
|
+
# application's bytes to the end of the frame.
|
|
18
|
+
VENDOR_SIZE = 4
|
|
19
|
+
|
|
20
|
+
class FrameThirdParty(FrameBase):
|
|
21
|
+
'''An application's own message, under its vendor id.'''
|
|
22
|
+
@staticmethod
|
|
23
|
+
def construct(mxr:DeviceRegistry, vendor:ThirdPartyVendor, payload:bytes) -> FrameBase|None:
|
|
24
|
+
'''A message to every device: the vendor's flag byte and three
|
|
25
|
+
identifying bytes, then payload as it was given.'''
|
|
26
|
+
data = bytes([int(vendor.flags)]) + vendor.id + bytes(payload)
|
|
27
|
+
return FrameBase.construct_base(mxr=mxr, opcode=0x55, payload=data)
|
|
28
|
+
|
|
29
|
+
@cached_property
|
|
30
|
+
def message(self) -> ThirdPartyMessage|None:
|
|
31
|
+
'''The message, whatever its vendor: telling them apart is the
|
|
32
|
+
application's. None for a payload too short for the vendor id.
|
|
33
|
+
Everything after the id is the application's, so the length is a
|
|
34
|
+
minimum and nothing past it is read.'''
|
|
35
|
+
p = self.payload or b''
|
|
36
|
+
if (len(p) < VENDOR_SIZE):
|
|
37
|
+
return None
|
|
38
|
+
vendor = ThirdPartyVendor(flags=ThirdPartyVendorFlag(p[0]), id=bytes(p[1:VENDOR_SIZE]))
|
|
39
|
+
return ThirdPartyMessage(vendor=vendor, payload=bytes(p[VENDOR_SIZE:]), address=self.address)
|
|
40
|
+
|
|
41
|
+
@override
|
|
42
|
+
def process(self) -> None:
|
|
43
|
+
'''Hand the message to the callbacks, as sent by the device it came from.'''
|
|
44
|
+
if ((message := self.message) is not None) and ((dev := self.remote_device) is not None):
|
|
45
|
+
dev.callbacks.on_third_party_message(dev, message)
|
|
46
|
+
|
|
47
|
+
def __str__(self) -> str:
|
|
48
|
+
if (self.message is None):
|
|
49
|
+
return "third-party message <dropped>"
|
|
50
|
+
return f"third-party message from {self.message}"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from ..Interface import DeviceRegistry as DeviceRegistry, ThirdPartyMessage as ThirdPartyMessage, ThirdPartyVendor as ThirdPartyVendor
|
|
2
|
+
from ..compat import override as override
|
|
3
|
+
from .Constants import ThirdPartyVendorFlag as ThirdPartyVendorFlag
|
|
4
|
+
from .FrameBase import FrameBase as FrameBase
|
|
5
|
+
from functools import cached_property
|
|
6
|
+
|
|
7
|
+
VENDOR_SIZE: int
|
|
8
|
+
|
|
9
|
+
class FrameThirdParty(FrameBase):
|
|
10
|
+
@staticmethod
|
|
11
|
+
def construct(mxr: DeviceRegistry, vendor: ThirdPartyVendor, payload: bytes) -> FrameBase | None: ...
|
|
12
|
+
@cached_property
|
|
13
|
+
def message(self) -> ThirdPartyMessage | None: ...
|
|
14
|
+
@override
|
|
15
|
+
def process(self) -> None: ...
|
|
@@ -22,12 +22,13 @@ from datetime import datetime, timezone
|
|
|
22
22
|
from .ConnectionAsync import ConnectionAsync
|
|
23
23
|
from ..const import MXR_HELLO_INTERVAL_MIN, MXR_HELLO_INTERVAL_RAND, __version__
|
|
24
24
|
from .Device import Device
|
|
25
|
-
from ..Interface import ConnectionCallbacks, DeviceRegistry, MxrDeviceUid, BayLinks, BayBase, DeviceBase, MxrCallbacks, AudioEndpoint, mxr_broadcast_address, OneIPDeviceSettings, OneIPPowerSaveSchedule, OneIPPtpMessages, OsdSyncContent, OsdSyncRequest
|
|
26
|
-
from ..proto.Constants import MXR_PROTOCOL_VERSION, DeviceFeature, OsdSyncWhat
|
|
25
|
+
from ..Interface import ConnectionCallbacks, DeviceRegistry, MxrDeviceUid, BayLinks, BayBase, DeviceBase, MxrCallbacks, AudioEndpoint, mxr_broadcast_address, OneIPDeviceSettings, OneIPPowerSaveSchedule, OneIPPtpMessages, OsdSyncContent, OsdSyncRequest, ThirdPartyVendor
|
|
26
|
+
from ..proto.Constants import MXR_PROTOCOL_VERSION, THIRD_PARTY_PAYLOAD_MAX, DeviceFeature, OsdSyncWhat
|
|
27
27
|
from ..proto.FrameDiscover import FrameDiscover, constructFrameDiscover
|
|
28
28
|
from ..proto.Factory import process_mxr_frame
|
|
29
29
|
from ..proto.FrameHello import FrameHello, constructFrameHello
|
|
30
30
|
from ..proto.FramePing import FramePing
|
|
31
|
+
from ..proto.FrameThirdParty import FrameThirdParty
|
|
31
32
|
from ..proto.FrameBase import FrameBase
|
|
32
33
|
from ..proto.FrameTime import FrameTime
|
|
33
34
|
from ..proto.FrameTimeZone import FrameTimeZone
|
|
@@ -573,6 +574,23 @@ class Remote(DeviceRegistry, ConnectionCallbacks):
|
|
|
573
574
|
protocol 0x30 ignores it.'''
|
|
574
575
|
return self._send_to_all(FrameV2IPPrivacy.construct_request(mxr=self, target=None, target_uid=None))
|
|
575
576
|
|
|
577
|
+
async def send_third_party(self, vendor:ThirdPartyVendor, payload:bytes) -> bool:
|
|
578
|
+
'''Send an application's own message to every device, under vendor.
|
|
579
|
+
True when all of it was written.
|
|
580
|
+
|
|
581
|
+
Every other client of this library that has heard this one announce
|
|
582
|
+
itself receives it at MxrCallbacks.on_third_party_message(); this client
|
|
583
|
+
does not, and neither does one sharing its uid. No unit is meant to read
|
|
584
|
+
it. Nothing acknowledges it, so an application that needs to know it
|
|
585
|
+
arrived has to have its peers answer.
|
|
586
|
+
|
|
587
|
+
A payload over THIRD_PARTY_PAYLOAD_MAX bytes is refused.'''
|
|
588
|
+
if (len(payload) > THIRD_PARTY_PAYLOAD_MAX):
|
|
589
|
+
_LOGGER.warning(f"not sending a third-party message: {len(payload)} bytes, "
|
|
590
|
+
f"the most is {THIRD_PARTY_PAYLOAD_MAX}")
|
|
591
|
+
return False
|
|
592
|
+
return self._send_to_all(FrameThirdParty.construct(mxr=self, vendor=vendor, payload=payload))
|
|
593
|
+
|
|
576
594
|
async def set_mesh_time_zone(self, zone:str, rule:str) -> bool:
|
|
577
595
|
'''Set the time zone of every device that hears it.
|
|
578
596
|
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import aiohttp
|
|
2
|
-
from ..Interface import AudioEndpoint as AudioEndpoint, BayBase as BayBase, BayLinks as BayLinks, ConnectionCallbacks as ConnectionCallbacks, DeviceBase as DeviceBase, DeviceRegistry as DeviceRegistry, MxrCallbacks as MxrCallbacks, OneIPDeviceSettings as OneIPDeviceSettings, OneIPPowerSaveSchedule as OneIPPowerSaveSchedule, OneIPPtpMessages as OneIPPtpMessages, OsdSyncContent as OsdSyncContent, OsdSyncRequest as OsdSyncRequest, mxr_broadcast_address as mxr_broadcast_address
|
|
2
|
+
from ..Interface import AudioEndpoint as AudioEndpoint, BayBase as BayBase, BayLinks as BayLinks, ConnectionCallbacks as ConnectionCallbacks, DeviceBase as DeviceBase, DeviceRegistry as DeviceRegistry, MxrCallbacks as MxrCallbacks, OneIPDeviceSettings as OneIPDeviceSettings, OneIPPowerSaveSchedule as OneIPPowerSaveSchedule, OneIPPtpMessages as OneIPPtpMessages, OsdSyncContent as OsdSyncContent, OsdSyncRequest as OsdSyncRequest, ThirdPartyVendor as ThirdPartyVendor, mxr_broadcast_address as mxr_broadcast_address
|
|
3
3
|
from ..Uid import MxrDeviceUid as MxrDeviceUid
|
|
4
4
|
from ..compat import override as override
|
|
5
5
|
from ..const import MXR_HELLO_INTERVAL_MIN as MXR_HELLO_INTERVAL_MIN, MXR_HELLO_INTERVAL_RAND as MXR_HELLO_INTERVAL_RAND, MX_BCAST_UDP_PORT as MX_BCAST_UDP_PORT, MX_MCAST_UDP_IP as MX_MCAST_UDP_IP, MX_MCAST_UDP_PORT as MX_MCAST_UDP_PORT, __version__ as __version__
|
|
6
|
-
from ..proto.Constants import DeviceFeature as DeviceFeature, MXR_PROTOCOL_VERSION as MXR_PROTOCOL_VERSION, ONEIP_DEVICE_SETTINGS_REPORTED_ONLY as ONEIP_DEVICE_SETTINGS_REPORTED_ONLY, ONEIP_DSCP_MAX as ONEIP_DSCP_MAX, ONEIP_IR_PROFILE_MAX as ONEIP_IR_PROFILE_MAX, ONEIP_IR_PROFILE_NOT_SET as ONEIP_IR_PROFILE_NOT_SET, ONEIP_PTP_DOMAIN_MAX as ONEIP_PTP_DOMAIN_MAX, ONEIP_SINK_ROTATION_MAX as ONEIP_SINK_ROTATION_MAX, OneIPDeviceSetting as OneIPDeviceSetting, OneIPPowerSaveLevel as OneIPPowerSaveLevel, OneIPPtpMode as OneIPPtpMode, OsdSyncWhat as OsdSyncWhat
|
|
6
|
+
from ..proto.Constants import DeviceFeature as DeviceFeature, MXR_PROTOCOL_VERSION as MXR_PROTOCOL_VERSION, ONEIP_DEVICE_SETTINGS_REPORTED_ONLY as ONEIP_DEVICE_SETTINGS_REPORTED_ONLY, ONEIP_DSCP_MAX as ONEIP_DSCP_MAX, ONEIP_IR_PROFILE_MAX as ONEIP_IR_PROFILE_MAX, ONEIP_IR_PROFILE_NOT_SET as ONEIP_IR_PROFILE_NOT_SET, ONEIP_PTP_DOMAIN_MAX as ONEIP_PTP_DOMAIN_MAX, ONEIP_SINK_ROTATION_MAX as ONEIP_SINK_ROTATION_MAX, OneIPDeviceSetting as OneIPDeviceSetting, OneIPPowerSaveLevel as OneIPPowerSaveLevel, OneIPPtpMode as OneIPPtpMode, OsdSyncWhat as OsdSyncWhat, THIRD_PARTY_PAYLOAD_MAX as THIRD_PARTY_PAYLOAD_MAX
|
|
7
7
|
from ..proto.Factory import process_mxr_frame as process_mxr_frame
|
|
8
8
|
from ..proto.FrameBase import FrameBase as FrameBase
|
|
9
9
|
from ..proto.FrameDiscover import FrameDiscover as FrameDiscover, constructFrameDiscover as constructFrameDiscover
|
|
10
10
|
from ..proto.FrameHello import FrameHello as FrameHello, constructFrameHello as constructFrameHello
|
|
11
11
|
from ..proto.FramePing import FramePing as FramePing
|
|
12
|
+
from ..proto.FrameThirdParty import FrameThirdParty as FrameThirdParty
|
|
12
13
|
from ..proto.FrameTime import FrameTime as FrameTime
|
|
13
14
|
from ..proto.FrameTimeZone import FrameTimeZone as FrameTimeZone
|
|
14
15
|
from ..proto.FrameV2IPOsdSync import FrameV2IPOsdSync as FrameV2IPOsdSync
|
|
@@ -75,6 +76,7 @@ class Remote(DeviceRegistry, ConnectionCallbacks):
|
|
|
75
76
|
async def set_all_oneip_device_settings(self, settings: OneIPDeviceSettings) -> bool: ...
|
|
76
77
|
async def set_all_oneip_power_save(self, enabled: bool) -> bool: ...
|
|
77
78
|
async def request_all_oneip_privacy(self) -> bool: ...
|
|
79
|
+
async def send_third_party(self, vendor: ThirdPartyVendor, payload: bytes) -> bool: ...
|
|
78
80
|
async def set_mesh_time_zone(self, zone: str, rule: str) -> bool: ...
|
|
79
81
|
async def clear_mesh_time_zone(self) -> bool: ...
|
|
80
82
|
async def set_mesh_time(self, when: datetime | None = None) -> bool: ...
|
|
@@ -59,6 +59,7 @@ SUITES = [
|
|
|
59
59
|
'meshbays', # the bays a unit lists for other units, from its mappings
|
|
60
60
|
'v2ipsdp', # what a OneIP output plays from a session description
|
|
61
61
|
'privacy', # how a OneIP source unit encrypts its streams
|
|
62
|
+
'thirdparty', # applications' own messages, sent and received
|
|
62
63
|
'baycontent', # what the source on an input is showing
|
|
63
64
|
'osdsync', # overlay sync, watched and sent
|
|
64
65
|
'osdmessage', # the message on a unit's output, read and written
|
|
@@ -12,6 +12,7 @@ print('version', mx_remote.const.VERSION, 'proto', hex(MXR_PROTOCOL_VERSION))
|
|
|
12
12
|
assert MXR_PROTOCOL_VERSION == max(MXR_OPCODE_VERSIONS.values()), hex(MXR_PROTOCOL_VERSION)
|
|
13
13
|
assert MXR_OPCODE_VERSIONS[0x52] == 0x2E and MXR_OPCODE_VERSIONS[0x53] == 0x2F # V2IP_SINK_SDP, V2IP_OSD_SYNC
|
|
14
14
|
assert MXR_OPCODE_VERSIONS[0x54] == 0x30 # V2IP_PRIVACY
|
|
15
|
+
assert MXR_OPCODE_VERSIONS[0x55] == 0x01 # THIRD_PARTY
|
|
15
16
|
assert MXR_OPCODE_VERSIONS[0x49] == 0x28 and MXR_OPCODE_VERSIONS[0x3C] == 0x11
|
|
16
17
|
assert MXR_OPCODE_VERSIONS[0x4F] == 0x2C and MXR_OPCODE_VERSIONS[0x50] == 0x2C # SYS_UNIT_STATUS, SYS_UNIT_COMMAND
|
|
17
18
|
assert DeviceFeature.VIDEO_WALL == (1 << 24)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
######################################################
|
|
2
|
+
## MX Remote Python Interface ##
|
|
3
|
+
## ##
|
|
4
|
+
## author: Lars Op den Kamp (lars@opdenkamp-it.nl) ##
|
|
5
|
+
## copyright (c) 2021-2026 Op den Kamp IT Solutions ##
|
|
6
|
+
######################################################
|
|
7
|
+
'''Applications' own messages (0x55), sent and received.
|
|
8
|
+
|
|
9
|
+
The fixtures are composed from the layout this library defines for the opcode,
|
|
10
|
+
so they rule out a slip rather than a shared misreading. Payload bytes are all
|
|
11
|
+
distinct from their neighbours, so a payload read from the wrong offset shows.
|
|
12
|
+
'''
|
|
13
|
+
|
|
14
|
+
import asyncio, os, struct, sys, logging
|
|
15
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
16
|
+
logging.disable(logging.CRITICAL)
|
|
17
|
+
import mx_remote
|
|
18
|
+
from mx_remote import DeviceFeature, MxrDeviceUid, ThirdPartyVendor
|
|
19
|
+
from mx_remote.proto.Constants import THIRD_PARTY_PAYLOAD_MAX, ThirdPartyVendorFlag
|
|
20
|
+
from mx_remote.proto.Factory import create_mxr_frame
|
|
21
|
+
|
|
22
|
+
ADDR = ('192.0.2.9', 8812)
|
|
23
|
+
OURS = bytes(range(0x20, 0x30))
|
|
24
|
+
|
|
25
|
+
class Listening(mx_remote.MxrCallbacks):
|
|
26
|
+
def __init__(self):
|
|
27
|
+
self.messages = []
|
|
28
|
+
def on_third_party_message(self, dev, message):
|
|
29
|
+
self.messages.append((dev, message))
|
|
30
|
+
|
|
31
|
+
seen = Listening()
|
|
32
|
+
mx = mx_remote.Remote(open_connection=False, callbacks=seen)
|
|
33
|
+
mx._uid = OURS
|
|
34
|
+
sent = []
|
|
35
|
+
mx.transmit = lambda data: (sent.append(bytes(data)), len(data))[1]
|
|
36
|
+
|
|
37
|
+
def uid(n):
|
|
38
|
+
return bytes([n]) * 16
|
|
39
|
+
|
|
40
|
+
def nm(s, sz=16):
|
|
41
|
+
b = s.encode('ascii')[:sz]
|
|
42
|
+
return b + bytes(sz - len(b))
|
|
43
|
+
|
|
44
|
+
def body(n):
|
|
45
|
+
return bytes((0x80 + i * 7) & 0xFF for i in range(n))
|
|
46
|
+
|
|
47
|
+
def rx(sender, payload, protocol=0x01):
|
|
48
|
+
'''A message stamped as a client stamps it, at the opcode's row.'''
|
|
49
|
+
frame = bytearray(create_mxr_frame(sender, 0x55, payload))
|
|
50
|
+
frame[2] = protocol
|
|
51
|
+
mx.process_frame(0.0, bytes(frame), ADDR)
|
|
52
|
+
|
|
53
|
+
def known(n):
|
|
54
|
+
'''Another client, announced.'''
|
|
55
|
+
frame = bytearray(create_mxr_frame(uid(n), 0x00, struct.pack('<H', 0x30) + nm('audio-cast') + nm('P9SN00000000')
|
|
56
|
+
+ nm('1.0.0') + struct.pack('<I', int(DeviceFeature.MANAGER))))
|
|
57
|
+
mx.process_frame(0.0, bytes(frame), ADDR)
|
|
58
|
+
seen.messages.clear()
|
|
59
|
+
return mx.get_by_uid(MxrDeviceUid(uid(n)))
|
|
60
|
+
|
|
61
|
+
def call(coro):
|
|
62
|
+
return asyncio.run(coro)
|
|
63
|
+
|
|
64
|
+
# ------------------------------------------------------------------- sending
|
|
65
|
+
# Every device gets it at the lowest stamp, with the flag byte, the three
|
|
66
|
+
# identifying bytes and the payload as given. A flag bit without a name goes
|
|
67
|
+
# out as it was set.
|
|
68
|
+
odk = ThirdPartyVendor(flags=ThirdPartyVendorFlag.NOT_OUI | ThirdPartyVendorFlag(0x40), id=b'ODK')
|
|
69
|
+
sent.clear()
|
|
70
|
+
assert call(mx.send_third_party(odk, body(41))) is True
|
|
71
|
+
frame = sent[-1]
|
|
72
|
+
assert frame[20:22] == bytes([0x55, 0x00]), frame[:24].hex()
|
|
73
|
+
assert frame[2] == 0x01, f'stamped {frame[2]:#04x}'
|
|
74
|
+
assert frame[24:28] == bytes([0x41]) + b'ODK', frame[24:28].hex()
|
|
75
|
+
assert frame[28:] == body(41)
|
|
76
|
+
print('send : stamped 0x01, the vendor id then the payload as given')
|
|
77
|
+
|
|
78
|
+
sent.clear()
|
|
79
|
+
assert call(mx.send_third_party(ThirdPartyVendor.oui(bytes([0, 0x1A, 0x2B])), b'')) is True
|
|
80
|
+
assert sent[-1][24:] == bytes([0, 0, 0x1A, 0x2B]), sent[-1][24:].hex()
|
|
81
|
+
|
|
82
|
+
sent.clear()
|
|
83
|
+
assert call(mx.send_third_party(odk, bytes(THIRD_PARTY_PAYLOAD_MAX))) is True, 'the largest payload was refused'
|
|
84
|
+
assert len(sent[-1]) + 28 == 1500, 'the largest frame does not fill the MTU'
|
|
85
|
+
|
|
86
|
+
sent.clear()
|
|
87
|
+
assert call(mx.send_third_party(odk, bytes(THIRD_PARTY_PAYLOAD_MAX + 1))) is False
|
|
88
|
+
assert not sent, 'an oversized message was sent'
|
|
89
|
+
print('size : an empty payload goes, and up to', THIRD_PARTY_PAYLOAD_MAX, 'bytes, which fill the MTU')
|
|
90
|
+
|
|
91
|
+
# ------------------------------------------------------------------- receiving
|
|
92
|
+
# The flag byte comes first and the three identifying bytes follow, and the
|
|
93
|
+
# payload after them arrives byte for byte.
|
|
94
|
+
dev = known(0x31)
|
|
95
|
+
rx(uid(0x31), bytes([0x81]) + b'ODK' + body(37))
|
|
96
|
+
assert len(seen.messages) == 1, seen.messages
|
|
97
|
+
by, message = seen.messages[0]
|
|
98
|
+
assert by is dev, by
|
|
99
|
+
assert message.vendor.flags == ThirdPartyVendorFlag(0x81), message.vendor.flags
|
|
100
|
+
assert message.vendor.id == b'ODK'
|
|
101
|
+
assert message.payload == body(37)
|
|
102
|
+
assert message.address == ADDR[0]
|
|
103
|
+
assert message.vendor.same_vendor(ThirdPartyVendor.private(b'ODK'))
|
|
104
|
+
print('receive :', message)
|
|
105
|
+
|
|
106
|
+
# A vendor id alone is a message with nothing after it, and anything shorter
|
|
107
|
+
# is not a message.
|
|
108
|
+
known(0x32)
|
|
109
|
+
rx(uid(0x32), bytes([0x00, 0x00, 0x1A, 0x2B]))
|
|
110
|
+
assert len(seen.messages) == 1, seen.messages
|
|
111
|
+
assert seen.messages[0][1].vendor == ThirdPartyVendor.oui(bytes([0x00, 0x1A, 0x2B]))
|
|
112
|
+
assert seen.messages[0][1].payload == b''
|
|
113
|
+
seen.messages.clear()
|
|
114
|
+
for n in range(4):
|
|
115
|
+
rx(uid(0x32), body(n))
|
|
116
|
+
assert not seen.messages, seen.messages
|
|
117
|
+
print('minimum : the vendor id is the whole minimum')
|
|
118
|
+
|
|
119
|
+
# A sender that has not announced itself is not heard.
|
|
120
|
+
rx(uid(0x34), bytes([1]) + b'ODK' + bytes([7]))
|
|
121
|
+
assert not seen.messages, seen.messages
|
|
122
|
+
|
|
123
|
+
# This client never hears itself. Its own uid is never a known device either,
|
|
124
|
+
# so the stranger rule drops the frame too, and only the two together being
|
|
125
|
+
# lost turns this red.
|
|
126
|
+
rx(OURS, bytes([1]) + b'ODK' + bytes([7]))
|
|
127
|
+
assert not seen.messages, seen.messages
|
|
128
|
+
print('senders : a stranger and this client are not heard')
|
|
129
|
+
|
|
130
|
+
# ------------------------------------------------------------------- vendors
|
|
131
|
+
# Whether two ids name the same vendor rests on the not-an-OUI flag and the
|
|
132
|
+
# three bytes, not on the other flag bits.
|
|
133
|
+
plain = ThirdPartyVendor.private(b'ODK')
|
|
134
|
+
assert plain.same_vendor(ThirdPartyVendor(flags=ThirdPartyVendorFlag(0xF1), id=b'ODK'))
|
|
135
|
+
assert not plain.same_vendor(ThirdPartyVendor.oui(b'ODK'))
|
|
136
|
+
for other in (b'ODL', b'PDK', b'OEK'):
|
|
137
|
+
assert not plain.same_vendor(ThirdPartyVendor.private(other)), other
|
|
138
|
+
for wrong in (b'', b'OD', b'ODKX'):
|
|
139
|
+
try:
|
|
140
|
+
ThirdPartyVendor.private(wrong)
|
|
141
|
+
except ValueError:
|
|
142
|
+
continue
|
|
143
|
+
raise AssertionError(f'a {len(wrong)}-byte vendor id was taken')
|
|
144
|
+
print('vendors : the flag and the three bytes name a vendor')
|
|
145
|
+
|
|
146
|
+
print('ALL OK')
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|