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.
Files changed (208) hide show
  1. {mx_remote-4.7.9 → mx_remote-5.1.0}/CLAUDE.md +60 -4
  2. {mx_remote-4.7.9 → mx_remote-5.1.0}/PKG-INFO +60 -17
  3. {mx_remote-4.7.9 → mx_remote-5.1.0}/README.md +58 -15
  4. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/Interface.py +356 -6
  5. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/Interface.pyi +70 -2
  6. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/const.py +1 -1
  7. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/BayConfig.py +25 -5
  8. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/BayConfig.pyi +3 -1
  9. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/Constants.py +251 -10
  10. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/Constants.pyi +61 -1
  11. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/Factory.py +30 -0
  12. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameAmpZoneSettings.py +53 -5
  13. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameAmpZoneSettings.pyi +2 -0
  14. mx_remote-5.1.0/mx_remote/proto/FrameAudioClip.py +44 -0
  15. mx_remote-4.7.9/mx_remote/proto/FrameRCIr.pyi → mx_remote-5.1.0/mx_remote/proto/FrameAudioClip.pyi +3 -2
  16. mx_remote-5.1.0/mx_remote/proto/FrameAudioSetRoute.py +55 -0
  17. mx_remote-5.1.0/mx_remote/proto/FrameAudioSetRoute.pyi +14 -0
  18. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameBase.py +62 -6
  19. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameBase.pyi +7 -2
  20. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameBayConfig.py +17 -10
  21. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameBayConfigSecondary.py +13 -10
  22. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameBayHide.py +1 -1
  23. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameBayStatus.py +3 -3
  24. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameBayStatus.pyi +1 -1
  25. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameEDIDProfile.py +1 -1
  26. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameFirmwareVersion.py +13 -8
  27. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameFirmwareVersion.pyi +2 -2
  28. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameHeader.py +4 -1
  29. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameLinks.py +17 -13
  30. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameMeshOperation.py +3 -2
  31. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameMeshOperation.pyi +1 -0
  32. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameNetworkStatus.py +5 -4
  33. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameNetworkStatus.pyi +1 -0
  34. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameRCAction.py +3 -6
  35. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameRCAction.pyi +1 -1
  36. mx_remote-5.1.0/mx_remote/proto/FrameRCIr.py +85 -0
  37. mx_remote-5.1.0/mx_remote/proto/FrameRCIr.pyi +23 -0
  38. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameRCKey.py +2 -4
  39. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameRCKey.pyi +1 -1
  40. mx_remote-5.1.0/mx_remote/proto/FrameRCSettings.py +150 -0
  41. mx_remote-5.1.0/mx_remote/proto/FrameRCSettings.pyi +28 -0
  42. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameReboot.py +1 -1
  43. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameRoutingChange.py +5 -5
  44. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSetName.py +1 -1
  45. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSetRoute.py +11 -8
  46. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSetupStatus.py +1 -1
  47. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSignalStatusNew.py +68 -27
  48. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSignalStatusNew.pyi +10 -9
  49. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameTXRCAction.py +29 -11
  50. mx_remote-5.1.0/mx_remote/proto/FrameTXRCKey.py +71 -0
  51. mx_remote-5.1.0/mx_remote/proto/FrameTXRCKey.pyi +18 -0
  52. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameTxIR.py +15 -6
  53. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPAudio.py +24 -7
  54. mx_remote-5.1.0/mx_remote/proto/FrameV2IPDetectBays.py +30 -0
  55. mx_remote-5.1.0/mx_remote/proto/FrameV2IPDetectBays.pyi +7 -0
  56. mx_remote-5.1.0/mx_remote/proto/FrameV2IPDeviceConfiguration.py +168 -0
  57. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.pyi +7 -2
  58. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.py +1 -1
  59. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPMultiviewer.py +61 -30
  60. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPMultiviewer.pyi +6 -0
  61. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPSourceSwitch.py +1 -1
  62. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPSources.py +11 -1
  63. mx_remote-5.1.0/mx_remote/proto/FrameV2IPVideoWall.py +155 -0
  64. mx_remote-5.1.0/mx_remote/proto/FrameV2IPVideoWall.pyi +32 -0
  65. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameVolumeSet.py +1 -1
  66. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/LinkConfig.py +2 -2
  67. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/V2IPConfig.py +3 -1
  68. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/V2IPStats.py +43 -4
  69. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/V2IPStats.pyi +4 -0
  70. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/Bay.py +66 -17
  71. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/Bay.pyi +5 -2
  72. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/ConnectionAsync.py +42 -0
  73. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/Device.py +66 -24
  74. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/Device.pyi +9 -0
  75. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/Remote.py +13 -2
  76. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/Remote.pyi +1 -1
  77. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/V2IP.py +2 -0
  78. {mx_remote-4.7.9 → mx_remote-5.1.0}/pyproject.toml +13 -0
  79. mx_remote-5.1.0/tests/audio_ir.py +41 -0
  80. mx_remote-5.1.0/tests/capture.py +51 -0
  81. mx_remote-5.1.0/tests/e2e.py +108 -0
  82. mx_remote-5.1.0/tests/handlers.py +124 -0
  83. mx_remote-5.1.0/tests/merge.py +112 -0
  84. mx_remote-5.1.0/tests/newops.py +102 -0
  85. mx_remote-5.1.0/tests/offsets.py +59 -0
  86. mx_remote-5.1.0/tests/proto_stamp.py +60 -0
  87. mx_remote-5.1.0/tests/protogate.py +85 -0
  88. mx_remote-5.1.0/tests/rcsettings.py +79 -0
  89. mx_remote-5.1.0/tests/run.py +59 -0
  90. mx_remote-5.1.0/tests/smoke.py +45 -0
  91. mx_remote-5.1.0/tests/sources.py +74 -0
  92. mx_remote-5.1.0/tests/stats.py +73 -0
  93. mx_remote-5.1.0/tests/structs.py +93 -0
  94. mx_remote-5.1.0/tests/txresult.py +198 -0
  95. mx_remote-5.1.0/tests/unknowns.py +58 -0
  96. mx_remote-5.1.0/tests/vdetails.py +86 -0
  97. mx_remote-4.7.9/.claude/settings.local.json +0 -13
  98. mx_remote-4.7.9/mx_remote/proto/FrameRCIr.py +0 -29
  99. mx_remote-4.7.9/mx_remote/proto/FrameRCSettings.py +0 -18
  100. mx_remote-4.7.9/mx_remote/proto/FrameRCSettings.pyi +0 -4
  101. mx_remote-4.7.9/mx_remote/proto/FrameV2IPDeviceConfiguration.py +0 -108
  102. {mx_remote-4.7.9 → mx_remote-5.1.0}/.gitignore +0 -0
  103. {mx_remote-4.7.9 → mx_remote-5.1.0}/LICENSE +0 -0
  104. {mx_remote-4.7.9 → mx_remote-5.1.0}/hatch_build.py +0 -0
  105. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/Uid.py +0 -0
  106. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/Uid.pyi +0 -0
  107. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/__init__.py +0 -0
  108. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/__init__.pyi +0 -0
  109. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/const.pyi +0 -0
  110. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/main.py +0 -0
  111. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/main.pyi +0 -0
  112. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/Data.py +0 -0
  113. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/Data.pyi +0 -0
  114. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/Factory.pyi +0 -0
  115. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameAmpDolbySettings.py +0 -0
  116. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameAmpDolbySettings.pyi +0 -0
  117. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameBayConfig.pyi +0 -0
  118. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameBayConfigSecondary.pyi +0 -0
  119. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameBayHide.pyi +0 -0
  120. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameConnectStatus.py +0 -0
  121. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameConnectStatus.pyi +0 -0
  122. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameDebug.py +0 -0
  123. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameDebug.pyi +0 -0
  124. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameDiscover.py +0 -0
  125. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameDiscover.pyi +0 -0
  126. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameEDID.py +0 -0
  127. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameEDID.pyi +0 -0
  128. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameEDIDProfile.pyi +0 -0
  129. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameFactoryReset.py +0 -0
  130. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameFactoryReset.pyi +0 -0
  131. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameFilterStatus.py +0 -0
  132. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameFilterStatus.pyi +0 -0
  133. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameHeader.pyi +0 -0
  134. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameHello.py +0 -0
  135. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameHello.pyi +0 -0
  136. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameLinks.pyi +0 -0
  137. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameMirrorStatus.py +0 -0
  138. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameMirrorStatus.pyi +0 -0
  139. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameMonitoringPulse.py +0 -0
  140. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameMonitoringPulse.pyi +0 -0
  141. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FramePDUState.py +0 -0
  142. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FramePDUState.pyi +0 -0
  143. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FramePowerChange.py +0 -0
  144. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FramePowerChange.pyi +0 -0
  145. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameReboot.pyi +0 -0
  146. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameRoutingChange.pyi +0 -0
  147. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSetInstaller.py +0 -0
  148. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSetInstaller.pyi +0 -0
  149. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSetName.pyi +0 -0
  150. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSetRoute.pyi +0 -0
  151. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSetupStatus.pyi +0 -0
  152. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSignalStatus.py +0 -0
  153. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSignalStatus.pyi +0 -0
  154. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSysTemperature.py +0 -0
  155. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSysTemperature.pyi +0 -0
  156. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSystemStatus.py +0 -0
  157. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameSystemStatus.pyi +0 -0
  158. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameTXRCAction.pyi +0 -0
  159. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameTopology.py +0 -0
  160. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameTopology.pyi +0 -0
  161. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameTxIR.pyi +0 -0
  162. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameUpgradeFPGA.py +0 -0
  163. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameUpgradeFPGA.pyi +0 -0
  164. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPAudio.pyi +0 -0
  165. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPBayMapping.py +0 -0
  166. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPBayMapping.pyi +0 -0
  167. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPLink.py +0 -0
  168. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPLink.pyi +0 -0
  169. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.pyi +0 -0
  170. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPPowerSave.py +0 -0
  171. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPPowerSave.pyi +0 -0
  172. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPSetMaster.py +0 -0
  173. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPSetMaster.pyi +0 -0
  174. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPSourceSwitch.pyi +0 -0
  175. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPSources.pyi +0 -0
  176. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPStats.py +0 -0
  177. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPStats.pyi +0 -0
  178. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPStreamDetails.py +0 -0
  179. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPStreamDetails.pyi +0 -0
  180. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPTiling.py +0 -0
  181. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameV2IPTiling.pyi +0 -0
  182. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameVolume.py +0 -0
  183. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameVolume.pyi +0 -0
  184. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameVolumeDown.py +0 -0
  185. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameVolumeDown.pyi +0 -0
  186. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameVolumeSet.pyi +0 -0
  187. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameVolumeUp.py +0 -0
  188. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/FrameVolumeUp.pyi +0 -0
  189. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/LinkConfig.pyi +0 -0
  190. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/Multiviewer.py +0 -0
  191. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/Multiviewer.pyi +0 -0
  192. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/PDUState.py +0 -0
  193. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/PDUState.pyi +0 -0
  194. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/Svd.py +0 -0
  195. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/Svd.pyi +0 -0
  196. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/V2IPConfig.pyi +0 -0
  197. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/__init__.py +0 -0
  198. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/__init__.pyi +0 -0
  199. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/proto/svd.csv +0 -0
  200. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/py.typed +0 -0
  201. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/ConnectionAsync.pyi +0 -0
  202. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/Link.py +0 -0
  203. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/Link.pyi +0 -0
  204. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/P8PDU.py +0 -0
  205. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/P8PDU.pyi +0 -0
  206. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/State.py +0 -0
  207. {mx_remote-4.7.9 → mx_remote-5.1.0}/mx_remote/remote/State.pyi +0 -0
  208. {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 **no test suite** and no lint config in this repo. Validate protocol changes by
27
- running `mxr` against live hardware or by replaying a capture file with `mxr -i`.
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
- `[0x50, 0x38, protocol, 0x00]` ("P8" magic + protocol version) + opcode (u16 LE) +
71
- length (u16 LE) + payload. Built by `proto/Factory.py::create_mxr_frame`.
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.4
1
+ Metadata-Version: 2.5
2
2
  Name: mx_remote
3
- Version: 4.7.9
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 Interface
33
+ # MX Remote — Pulse-Eight device interface
34
34
 
35
- Python 3 library for interfacing with [MX Remote](https://github.com/opdenkamp/mx-remote/) compatible devices over a local network. Supports device discovery, video/audio routing, volume control, remote control key passthrough, V2IP (OneIP) streaming, and more.
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 MX Remote compatible devices (multicast or broadcast)
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 (V2IP) Devices
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
- # V2IP stream source addresses
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
- # V2IP device details (encoder/decoder config)
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
- # V2IP statistics
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 multiviewer-specific settings:
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 Interface
1
+ # MX Remote — Pulse-Eight device interface
2
2
 
3
- Python 3 library for interfacing with [MX Remote](https://github.com/opdenkamp/mx-remote/) compatible devices over a local network. Supports device discovery, video/audio routing, volume control, remote control key passthrough, V2IP (OneIP) streaming, and more.
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 MX Remote compatible devices (multicast or broadcast)
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 (V2IP) Devices
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
- # V2IP stream source addresses
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
- # V2IP device details (encoder/decoder config)
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
- # V2IP statistics
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 multiviewer-specific settings:
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 (