mx-remote 5.3.0__tar.gz → 5.4.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 (214) hide show
  1. mx_remote-5.3.0/CLAUDE.md → mx_remote-5.4.0/AGENTS.md +77 -4
  2. mx_remote-5.4.0/CLAUDE.md +4 -0
  3. mx_remote-5.4.0/PKG-INFO +128 -0
  4. mx_remote-5.4.0/README.md +96 -0
  5. mx_remote-5.4.0/docs/README.md +14 -0
  6. mx_remote-5.4.0/docs/audio.md +36 -0
  7. mx_remote-5.4.0/docs/callbacks.md +90 -0
  8. mx_remote-5.4.0/docs/configuration.md +133 -0
  9. mx_remote-5.4.0/docs/devices-and-bays.md +47 -0
  10. mx_remote-5.4.0/docs/diagnostics.md +89 -0
  11. mx_remote-5.4.0/docs/multiviewer.md +52 -0
  12. mx_remote-5.4.0/docs/oneip.md +43 -0
  13. mx_remote-5.4.0/docs/routing.md +24 -0
  14. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/Interface.py +64 -0
  15. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/Interface.pyi +4 -0
  16. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/const.py +1 -1
  17. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/Constants.py +3 -3
  18. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/Data.py +17 -2
  19. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/Data.pyi +2 -0
  20. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/Factory.py +16 -10
  21. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameFirmwareVersion.py +2 -1
  22. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameHeader.py +8 -2
  23. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameMeshOperation.py +43 -2
  24. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameMeshOperation.pyi +2 -0
  25. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameNetworkStatus.py +36 -3
  26. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameNetworkStatus.pyi +8 -0
  27. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameRCAction.py +26 -3
  28. mx_remote-5.4.0/mx_remote/proto/FrameRCIr.py +140 -0
  29. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameRCIr.pyi +4 -0
  30. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameRCKey.py +27 -4
  31. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameSetName.py +10 -1
  32. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameTxIR.py +55 -8
  33. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameTxIR.pyi +4 -0
  34. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPMultiviewer.py +96 -35
  35. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPMultiviewer.pyi +1 -1
  36. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameVolumeSet.py +47 -20
  37. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameVolumeSet.pyi +1 -1
  38. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/Multiviewer.py +39 -0
  39. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/Multiviewer.pyi +9 -0
  40. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/V2IPConfig.py +27 -1
  41. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/V2IPConfig.pyi +5 -1
  42. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/V2IPStats.py +2 -1
  43. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/remote/Bay.py +5 -0
  44. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/remote/Device.py +3 -1
  45. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/remote/Remote.py +39 -2
  46. {mx_remote-5.3.0 → mx_remote-5.4.0}/pyproject.toml +2 -0
  47. {mx_remote-5.3.0 → mx_remote-5.4.0}/tests/hello.py +25 -0
  48. mx_remote-5.4.0/tests/idempotent.py +173 -0
  49. {mx_remote-5.3.0 → mx_remote-5.4.0}/tests/offsets.py +16 -3
  50. {mx_remote-5.3.0 → mx_remote-5.4.0}/tests/proto_stamp.py +7 -12
  51. {mx_remote-5.3.0 → mx_remote-5.4.0}/tests/run.py +5 -1
  52. mx_remote-5.4.0/tests/wirefix.py +588 -0
  53. mx_remote-5.3.0/PKG-INFO +0 -558
  54. mx_remote-5.3.0/README.md +0 -526
  55. mx_remote-5.3.0/mx_remote/proto/FrameRCIr.py +0 -85
  56. {mx_remote-5.3.0 → mx_remote-5.4.0}/.gitignore +0 -0
  57. {mx_remote-5.3.0 → mx_remote-5.4.0}/LICENSE +0 -0
  58. {mx_remote-5.3.0 → mx_remote-5.4.0}/hatch_build.py +0 -0
  59. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/Uid.py +0 -0
  60. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/Uid.pyi +0 -0
  61. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/__init__.py +0 -0
  62. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/__init__.pyi +0 -0
  63. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/const.pyi +0 -0
  64. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/main.py +0 -0
  65. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/main.pyi +0 -0
  66. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/BayConfig.py +0 -0
  67. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/BayConfig.pyi +0 -0
  68. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/Constants.pyi +0 -0
  69. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/Factory.pyi +0 -0
  70. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameAmpDolbySettings.py +0 -0
  71. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameAmpDolbySettings.pyi +0 -0
  72. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameAmpZoneSettings.py +0 -0
  73. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameAmpZoneSettings.pyi +0 -0
  74. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameAudioClip.py +0 -0
  75. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameAudioClip.pyi +0 -0
  76. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameAudioSetRoute.py +0 -0
  77. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameAudioSetRoute.pyi +0 -0
  78. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameBase.py +0 -0
  79. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameBase.pyi +0 -0
  80. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameBayConfig.py +0 -0
  81. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameBayConfig.pyi +0 -0
  82. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameBayConfigSecondary.py +0 -0
  83. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameBayConfigSecondary.pyi +0 -0
  84. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameBayHide.py +0 -0
  85. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameBayHide.pyi +0 -0
  86. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameBayStatus.py +0 -0
  87. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameBayStatus.pyi +0 -0
  88. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameConnectStatus.py +0 -0
  89. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameConnectStatus.pyi +0 -0
  90. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameDebug.py +0 -0
  91. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameDebug.pyi +0 -0
  92. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameDiscover.py +0 -0
  93. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameDiscover.pyi +0 -0
  94. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameEDID.py +0 -0
  95. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameEDID.pyi +0 -0
  96. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameEDIDProfile.py +0 -0
  97. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameEDIDProfile.pyi +0 -0
  98. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameFactoryReset.py +0 -0
  99. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameFactoryReset.pyi +0 -0
  100. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameFilterStatus.py +0 -0
  101. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameFilterStatus.pyi +0 -0
  102. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameFirmwareVersion.pyi +0 -0
  103. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameHeader.pyi +0 -0
  104. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameHello.py +0 -0
  105. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameHello.pyi +0 -0
  106. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameLinks.py +0 -0
  107. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameLinks.pyi +0 -0
  108. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameMirrorStatus.py +0 -0
  109. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameMirrorStatus.pyi +0 -0
  110. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameMonitoringPulse.py +0 -0
  111. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameMonitoringPulse.pyi +0 -0
  112. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FramePowerChange.py +0 -0
  113. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FramePowerChange.pyi +0 -0
  114. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameRCAction.pyi +0 -0
  115. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameRCKey.pyi +0 -0
  116. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameRCSettings.py +0 -0
  117. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameRCSettings.pyi +0 -0
  118. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameReboot.py +0 -0
  119. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameReboot.pyi +0 -0
  120. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameRoutingChange.py +0 -0
  121. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameRoutingChange.pyi +0 -0
  122. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameSetInstaller.py +0 -0
  123. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameSetInstaller.pyi +0 -0
  124. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameSetName.pyi +0 -0
  125. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameSetRoute.py +0 -0
  126. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameSetRoute.pyi +0 -0
  127. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameSetupStatus.py +0 -0
  128. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameSetupStatus.pyi +0 -0
  129. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameSignalStatus.py +0 -0
  130. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameSignalStatus.pyi +0 -0
  131. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameSignalStatusNew.py +0 -0
  132. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameSignalStatusNew.pyi +0 -0
  133. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameSysTemperature.py +0 -0
  134. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameSysTemperature.pyi +0 -0
  135. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameSystemStatus.py +0 -0
  136. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameSystemStatus.pyi +0 -0
  137. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameTXRCAction.py +0 -0
  138. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameTXRCAction.pyi +0 -0
  139. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameTXRCKey.py +0 -0
  140. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameTXRCKey.pyi +0 -0
  141. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameTopology.py +0 -0
  142. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameTopology.pyi +0 -0
  143. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameUpgradeFPGA.py +0 -0
  144. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameUpgradeFPGA.pyi +0 -0
  145. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPAudio.py +0 -0
  146. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPAudio.pyi +0 -0
  147. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPBayMapping.py +0 -0
  148. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPBayMapping.pyi +0 -0
  149. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPDetectBays.py +0 -0
  150. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPDetectBays.pyi +0 -0
  151. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.py +0 -0
  152. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.pyi +0 -0
  153. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPLink.py +0 -0
  154. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPLink.pyi +0 -0
  155. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.py +0 -0
  156. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.pyi +0 -0
  157. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPPowerSave.py +0 -0
  158. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPPowerSave.pyi +0 -0
  159. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPSetMaster.py +0 -0
  160. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPSetMaster.pyi +0 -0
  161. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPSourceSwitch.py +0 -0
  162. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPSourceSwitch.pyi +0 -0
  163. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPSources.py +0 -0
  164. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPSources.pyi +0 -0
  165. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPStats.py +0 -0
  166. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPStats.pyi +0 -0
  167. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPTiling.py +0 -0
  168. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPTiling.pyi +0 -0
  169. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPVideoWall.py +0 -0
  170. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameV2IPVideoWall.pyi +0 -0
  171. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameVolume.py +0 -0
  172. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameVolume.pyi +0 -0
  173. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameVolumeDown.py +0 -0
  174. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameVolumeDown.pyi +0 -0
  175. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameVolumeUp.py +0 -0
  176. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/FrameVolumeUp.pyi +0 -0
  177. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/LinkConfig.py +0 -0
  178. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/LinkConfig.pyi +0 -0
  179. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/Svd.py +0 -0
  180. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/Svd.pyi +0 -0
  181. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/V2IPStats.pyi +0 -0
  182. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/__init__.py +0 -0
  183. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/__init__.pyi +0 -0
  184. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/proto/svd.csv +0 -0
  185. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/py.typed +0 -0
  186. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/remote/Bay.pyi +0 -0
  187. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/remote/ConnectionAsync.py +0 -0
  188. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/remote/ConnectionAsync.pyi +0 -0
  189. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/remote/Device.pyi +0 -0
  190. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/remote/Link.py +0 -0
  191. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/remote/Link.pyi +0 -0
  192. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/remote/Remote.pyi +0 -0
  193. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/remote/State.py +0 -0
  194. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/remote/State.pyi +0 -0
  195. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/remote/V2IP.py +0 -0
  196. {mx_remote-5.3.0 → mx_remote-5.4.0}/mx_remote/remote/V2IP.pyi +0 -0
  197. {mx_remote-5.3.0 → mx_remote-5.4.0}/tests/audio_ir.py +0 -0
  198. {mx_remote-5.3.0 → mx_remote-5.4.0}/tests/capture.py +0 -0
  199. {mx_remote-5.3.0 → mx_remote-5.4.0}/tests/e2e.py +0 -0
  200. {mx_remote-5.3.0 → mx_remote-5.4.0}/tests/entry.py +0 -0
  201. {mx_remote-5.3.0 → mx_remote-5.4.0}/tests/handlers.py +0 -0
  202. {mx_remote-5.3.0 → mx_remote-5.4.0}/tests/handlers_cover.py +0 -0
  203. {mx_remote-5.3.0 → mx_remote-5.4.0}/tests/merge.py +0 -0
  204. {mx_remote-5.3.0 → mx_remote-5.4.0}/tests/newops.py +0 -0
  205. {mx_remote-5.3.0 → mx_remote-5.4.0}/tests/protogate.py +0 -0
  206. {mx_remote-5.3.0 → mx_remote-5.4.0}/tests/rcsettings.py +0 -0
  207. {mx_remote-5.3.0 → mx_remote-5.4.0}/tests/roundtrip.py +0 -0
  208. {mx_remote-5.3.0 → mx_remote-5.4.0}/tests/smoke.py +0 -0
  209. {mx_remote-5.3.0 → mx_remote-5.4.0}/tests/sources.py +0 -0
  210. {mx_remote-5.3.0 → mx_remote-5.4.0}/tests/stats.py +0 -0
  211. {mx_remote-5.3.0 → mx_remote-5.4.0}/tests/structs.py +0 -0
  212. {mx_remote-5.3.0 → mx_remote-5.4.0}/tests/txresult.py +0 -0
  213. {mx_remote-5.3.0 → mx_remote-5.4.0}/tests/unknowns.py +0 -0
  214. {mx_remote-5.3.0 → mx_remote-5.4.0}/tests/vdetails.py +0 -0
@@ -1,6 +1,6 @@
1
- # CLAUDE.md
1
+ # AGENTS.md
2
2
 
3
- This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
3
+ Guidance for coding agents working in this repository.
4
4
 
5
5
  ## What this is
6
6
 
@@ -105,7 +105,7 @@ Three layers, decoupled by abstract base classes:
105
105
  - `Device` (`DeviceBase`), `Bay` (`BayBase`) — live device/port state.
106
106
  - `ConnectionAsync` — `asyncio.DatagramProtocol` UDP transport (multicast/broadcast).
107
107
  - `State` — dispatches events to registered `MxrCallbacks`.
108
- - `V2IP`, `Link`, `P8PDU` — OneIP streaming, virtual bay links, PDU support.
108
+ - `V2IP`, `Link` — OneIP streaming and virtual bay links.
109
109
 
110
110
  ### Receive data flow
111
111
  `ConnectionAsync` datagram → `Remote.on_datagram_received` → `Remote.process_frame` →
@@ -131,5 +131,78 @@ Built by `proto/Factory.py::create_mxr_frame`, decoded by `proto/FrameHeader.py`
131
131
  When adding or editing a `Frame*` class, byte layouts **must match the MatrixOS firmware C
132
132
  structs** — verify against the firmware source rather than inferring from samples. Adding a
133
133
  new opcode requires both the `Frame*` class and a dispatch entry in
134
- `Factory.py::_mxr_frame_factory`. Note the proto/ and remote/ modules use tab indentation
134
+ `Factory.py::_mxr_frame_factory`.
135
+
136
+ Get sizes and offsets from a compiler, not by adding up field widths. Two things
137
+ defeat reading:
138
+
139
+ - `mxr_uid` is `uint32_t[4]`, so it aligns to 4 and shifts every field behind it
140
+ in a struct that is not `PACKED`.
141
+ - an `ALIGN(8)` struct is as wide as its alignment rounds it to, and its tail
142
+ padding is part of the payload. Receivers test `len < sizeof(...)` before
143
+ reading a field, so a frame short by its padding is dropped without a reply —
144
+ which looks exactly like one that was accepted and ignored.
145
+
146
+ Paste the struct into a throwaway `.c` file with `__builtin_offsetof` and run it.
147
+ Note that `uint_fast16_t` is 4 bytes on the target, so model it as `uint32_t`
148
+ rather than letting the host pick.
149
+
150
+ Compile the struct's *own* component types, at the version you are decoding.
151
+ Substituting today's equivalents makes the result a reconstruction rather than a
152
+ measurement, and a reconstruction that happens to be right is indistinguishable
153
+ from one that is wrong until something else disagrees with it. Types get renamed
154
+ and moved between headers without their layout changing, so a name that no
155
+ longer exists is not evidence that the layout did change.
156
+
157
+ `PACKED` on a struct sets where its members are *placed*, not how wide they are.
158
+ A nested aggregate keeps its own internal padding, so a packed struct holding an
159
+ array of unpacked 12-byte records still spends 48 bytes on four of them — it
160
+ just starts them at an unaligned offset. Assuming the nesting flattens gives a
161
+ smaller size that looks plausible and decodes.
162
+
163
+ A protocol version outlives layout revisions. The struct at the commit that
164
+ introduced a version is not necessarily the struct any device sent under it, and
165
+ two revisions can share one version — so date a layout from the releases that
166
+ carried it, not from the commit that named it.
167
+
168
+ That needs both halves of the release history, which only the firmware side has.
169
+ The tag says what bytes a version sends; the distribution list says whether
170
+ anyone runs it. Tags alone invent field versions nobody has, and this protocol
171
+ has several — versions tagged and never uploaded are why two apparent
172
+ compatibility problems here turned out not to exist.
173
+
174
+ The protocol floor in `MXR_OPCODE_VERSIONS` is a delivery gate, not a layout
175
+ selector. A payload can widen without its floor moving, and then the stamp says
176
+ nothing about which layout arrived — dispatch on payload length instead.
177
+ `tests/wirefix.py` covers the cases where the two come apart.
178
+
179
+ ## Writing down a deliberate divergence
180
+
181
+ A note recording *what the firmware does* reads as *what this library should
182
+ do*, and the gap between them is where every deliberate difference lives. So
183
+ write the divergence beside the fact, not somewhere else: "firmware ignores this
184
+ below 0x22" invites the next reader to delete the path that decodes it, where
185
+ "firmware ignores it, we decode it anyway, because those units still transmit
186
+ and nothing else answers them" does not.
187
+
188
+ This library is older than the firmware currently shipping and serves versions
189
+ that will never be upgraded, so it decodes more than any device does. That is
190
+ the intended relationship, not drift to be tidied up.
191
+
192
+ ## Fixtures that cannot fail
193
+
194
+ A fixture can be too simple to reach the code under test. It can also be too
195
+ *helpful*: it reaches the code with the interesting input already removed, or it
196
+ supplies for free the precondition the code is supposed to establish. The second
197
+ kind is worse, because it occupies the slot where the real test would go and
198
+ reports green forever.
199
+
200
+ Ask what the setup does for the code, and whether a caller gets that for free.
201
+ A frame stamped at a version no device sends, a payload length chosen so that
202
+ one wrong branch still parses, a send exercised only against a device that had
203
+ already been introduced to us — each passes, and none of them can fail.
204
+
205
+ The check is mechanical: break the thing the test is for, one part at a time,
206
+ and watch that test go red for the right reason. An assertion that fires on a
207
+ sanity check placed above the subject has tested nothing. Note the proto/ and remote/ modules use tab indentation
135
208
  while `Interface.py` and `const.py` use spaces — match the file you are editing.
@@ -0,0 +1,4 @@
1
+ # CLAUDE.md
2
+
3
+ The instructions for this repository live in [AGENTS.md](AGENTS.md), in the
4
+ format every agent reads. This file only points at them.
@@ -0,0 +1,128 @@
1
+ Metadata-Version: 2.5
2
+ Name: mx_remote
3
+ Version: 5.4.0
4
+ Summary: Python 3 library for interfacing with MX Remote compatible devices
5
+ Project-URL: Homepage, https://github.com/opdenkamp/mx-remote/
6
+ Project-URL: Documentation, https://github.com/opdenkamp/mx-remote/
7
+ Project-URL: Repository, https://github.com/opdenkamp/mx-remote.git
8
+ Project-URL: Bug Tracker, https://github.com/opdenkamp/mx-remote/issues
9
+ Author-email: Lars Op den Kamp <lars@opdenkamp-it.nl>
10
+ Maintainer-email: Lars Op den Kamp <lars@opdenkamp-it.nl>
11
+ License: Copyright 2021-2026 Lars Op den Kamp <lars@opdenkamp-it.nl>
12
+
13
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
14
+
15
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
16
+
17
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
18
+
19
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
20
+
21
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22
+ License-File: LICENSE
23
+ Keywords: ampos,matrixos,oneip,pulse-eight
24
+ Classifier: Development Status :: 3 - Alpha
25
+ Classifier: Programming Language :: Python
26
+ Classifier: Topic :: System :: Networking :: Monitoring
27
+ Requires-Python: >=3.11
28
+ Requires-Dist: aiofiles>=24.1.0
29
+ Requires-Dist: aiohttp>=3.10.5
30
+ Requires-Dist: netifaces>=0.11.0
31
+ Description-Content-Type: text/markdown
32
+
33
+ # MX Remote - Python Client for Pulse-Eight MatrixOS devices
34
+
35
+ Python 3 library for discovering and controlling [Pulse-Eight](https://www.pulse-eight.com/)
36
+ AV distribution hardware over a local network: video/audio matrices, HDMI-over-IP
37
+ encoders/decoders, multiviewers, and audio amplifiers. Supports device discovery,
38
+ video/audio routing, volume control, remote-control key passthrough, HDMI-over-IP
39
+ streaming, multiviewer control, and more.
40
+
41
+ If you are looking to integrate Pulse-Eight **neo**, **OneIP**, or **ProAmp8** devices into
42
+ your own software or home-automation system, this is the library for it.
43
+
44
+ ## 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
61
+
62
+ ## Requirements
63
+
64
+ - Python 3.11 or later
65
+ - Network access to one or more of the Pulse-Eight devices above (multicast or broadcast)
66
+
67
+ ## Installation
68
+
69
+ ```bash
70
+ pip install .
71
+ ```
72
+
73
+ ## Quick Start
74
+
75
+ The minimum code to discover devices on the network:
76
+
77
+ ```python
78
+ import asyncio
79
+ import mx_remote
80
+
81
+ async def main():
82
+ mx = mx_remote.Remote()
83
+ await mx.start_async()
84
+
85
+ # wait for devices to be discovered
86
+ await asyncio.sleep(5)
87
+
88
+ for uid, device in mx.remotes.items():
89
+ print(f"{device.serial} ({device.name}) - {device.model_name} - {device.status}")
90
+ for port, bay in device.bays.items():
91
+ print(f" {bay.bay_label} [{bay.mode}] signal={bay.signal_detected}")
92
+
93
+ await mx.close()
94
+
95
+ asyncio.run(main())
96
+ ```
97
+
98
+ ## Documentation
99
+
100
+ The full API reference lives in [docs/](docs/README.md).
101
+
102
+ | Page | Covers |
103
+ | --- | --- |
104
+ | [Configuration](configuration.md) | Opening a connection and keeping it in step with the mesh. |
105
+ | [Devices and bays](devices-and-bays.md) | Naming, hiding, EDID and the device registry. |
106
+ | [Routing](routing.md) | Selecting video and audio sources for an output. |
107
+ | [Callbacks](callbacks.md) | Reacting to state changes rather than polling for them. |
108
+ | [Audio](audio.md) | Volume, mute and remote-control passthrough. |
109
+ | [OneIP and V2IP](oneip.md) | Streaming endpoints, stream sources and statistics. |
110
+ | [Multiviewer](multiviewer.md) | Layout, sources and output configuration. |
111
+ | [Diagnostics](diagnostics.md) | Network status, the mxr console app and capture replay. |
112
+
113
+ ## Other languages
114
+
115
+ The same protocol, implemented independently:
116
+
117
+ - **Go** — https://github.com/opdenkamp/mx-remote-golang
118
+ - **Rust** — https://github.com/opdenkamp/mx-remote-rust (also ships a C ABI, for C and C++)
119
+
120
+ Python is the oldest of the three and the one that still decodes what older
121
+ firmware sent: `0x06`, `0x36` and `0x47` are superseded opcodes kept because a
122
+ unit on older firmware still emits them. So it decodes a slightly wider set than
123
+ the others, and the difference is deliberate rather than drift.
124
+
125
+ ## License
126
+
127
+ BSD 3-Clause License. See [LICENSE](LICENSE) for details.
128
+
@@ -0,0 +1,96 @@
1
+ # MX Remote - Python Client for Pulse-Eight MatrixOS devices
2
+
3
+ Python 3 library for discovering and controlling [Pulse-Eight](https://www.pulse-eight.com/)
4
+ AV distribution hardware over a local network: video/audio matrices, HDMI-over-IP
5
+ encoders/decoders, multiviewers, and audio amplifiers. Supports device discovery,
6
+ video/audio routing, volume control, remote-control key passthrough, HDMI-over-IP
7
+ streaming, multiviewer control, and more.
8
+
9
+ If you are looking to integrate Pulse-Eight **neo**, **OneIP**, or **ProAmp8** devices into
10
+ your own software or home-automation system, this is the library for it.
11
+
12
+ ## 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
29
+
30
+ ## Requirements
31
+
32
+ - Python 3.11 or later
33
+ - Network access to one or more of the Pulse-Eight devices above (multicast or broadcast)
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ pip install .
39
+ ```
40
+
41
+ ## Quick Start
42
+
43
+ The minimum code to discover devices on the network:
44
+
45
+ ```python
46
+ import asyncio
47
+ import mx_remote
48
+
49
+ async def main():
50
+ mx = mx_remote.Remote()
51
+ await mx.start_async()
52
+
53
+ # wait for devices to be discovered
54
+ await asyncio.sleep(5)
55
+
56
+ for uid, device in mx.remotes.items():
57
+ print(f"{device.serial} ({device.name}) - {device.model_name} - {device.status}")
58
+ for port, bay in device.bays.items():
59
+ print(f" {bay.bay_label} [{bay.mode}] signal={bay.signal_detected}")
60
+
61
+ await mx.close()
62
+
63
+ asyncio.run(main())
64
+ ```
65
+
66
+ ## Documentation
67
+
68
+ The full API reference lives in [docs/](docs/README.md).
69
+
70
+ | Page | Covers |
71
+ | --- | --- |
72
+ | [Configuration](configuration.md) | Opening a connection and keeping it in step with the mesh. |
73
+ | [Devices and bays](devices-and-bays.md) | Naming, hiding, EDID and the device registry. |
74
+ | [Routing](routing.md) | Selecting video and audio sources for an output. |
75
+ | [Callbacks](callbacks.md) | Reacting to state changes rather than polling for them. |
76
+ | [Audio](audio.md) | Volume, mute and remote-control passthrough. |
77
+ | [OneIP and V2IP](oneip.md) | Streaming endpoints, stream sources and statistics. |
78
+ | [Multiviewer](multiviewer.md) | Layout, sources and output configuration. |
79
+ | [Diagnostics](diagnostics.md) | Network status, the mxr console app and capture replay. |
80
+
81
+ ## Other languages
82
+
83
+ The same protocol, implemented independently:
84
+
85
+ - **Go** — https://github.com/opdenkamp/mx-remote-golang
86
+ - **Rust** — https://github.com/opdenkamp/mx-remote-rust (also ships a C ABI, for C and C++)
87
+
88
+ Python is the oldest of the three and the one that still decodes what older
89
+ firmware sent: `0x06`, `0x36` and `0x47` are superseded opcodes kept because a
90
+ unit on older firmware still emits them. So it decodes a slightly wider set than
91
+ the others, and the difference is deliberate rather than drift.
92
+
93
+ ## License
94
+
95
+ BSD 3-Clause License. See [LICENSE](LICENSE) for details.
96
+
@@ -0,0 +1,14 @@
1
+ # Documentation
2
+
3
+ The API reference for `mx_remote`. Start at the [project README](../README.md) for installation and a quick start.
4
+
5
+ | Page | Covers |
6
+ | --- | --- |
7
+ | [Configuration](configuration.md) | Opening a connection and keeping it in step with the mesh. |
8
+ | [Devices and bays](devices-and-bays.md) | Naming, hiding, EDID and the device registry. |
9
+ | [Routing](routing.md) | Selecting video and audio sources for an output. |
10
+ | [Callbacks](callbacks.md) | Reacting to state changes rather than polling for them. |
11
+ | [Audio](audio.md) | Volume, mute and remote-control passthrough. |
12
+ | [OneIP and V2IP](oneip.md) | Streaming endpoints, stream sources and statistics. |
13
+ | [Multiviewer](multiviewer.md) | Layout, sources and output configuration. |
14
+ | [Diagnostics](diagnostics.md) | Network status, the mxr console app and capture replay. |
@@ -0,0 +1,36 @@
1
+ # Audio
2
+
3
+ Volume, mute and remote-control passthrough.
4
+
5
+ ## Volume Control
6
+
7
+ ```python
8
+ bay.volume_up()
9
+ bay.volume_down()
10
+ bay.volume_set(volume=50) # set to 50%
11
+ bay.volume_set(volume=50, muted=False)
12
+ bay.mute_set(mute=True)
13
+ ```
14
+
15
+ ## Remote Control
16
+
17
+ Send remote control key presses and actions:
18
+
19
+ ```python
20
+ from mx_remote import RCKey, RCAction
21
+
22
+ # send a key press
23
+ await bay.send_key(RCKey.KEY_SELECT)
24
+ await bay.send_key(RCKey.KEY_UP)
25
+
26
+ # send a remote control action
27
+ await bay.tx_action(RCAction.ACTION_POWER_ON)
28
+ await bay.tx_action(RCAction.ACTION_POWER_OFF)
29
+ await bay.tx_action(RCAction.ACTION_POWER_TOGGLE)
30
+ await bay.tx_action(RCAction.ACTION_VOLUME_UP)
31
+ await bay.tx_action(RCAction.ACTION_VOLUME_DOWN)
32
+ ```
33
+
34
+ ---
35
+
36
+ [Documentation index](README.md) | [Project README](../README.md)
@@ -0,0 +1,90 @@
1
+ # Callbacks
2
+
3
+ Reacting to state changes rather than polling for them.
4
+
5
+ ## Callbacks
6
+
7
+ Subclass `MxrCallbacks` to receive notifications when device or bay state changes:
8
+
9
+ ```python
10
+ class MyCallbacks(mx_remote.MxrCallbacks):
11
+ def on_device_config_complete(self, dev):
12
+ print(f"Device ready: {dev.serial} ({dev.name})")
13
+
14
+ def on_bay_registered(self, bay):
15
+ print(f"Bay found: {bay.bay_label}")
16
+
17
+ def on_video_source_changed(self, bay, video_source):
18
+ print(f"{bay.user_name} video source -> {video_source.user_name}")
19
+
20
+ def on_audio_source_changed(self, bay, audio_source):
21
+ print(f"{bay.user_name} audio source -> {audio_source.user_name}")
22
+
23
+ def on_volume_changed(self, bay, volume):
24
+ print(f"{bay.user_name} volume: {volume}")
25
+
26
+ def on_power_changed(self, bay, power):
27
+ print(f"{bay.user_name} power: {power}")
28
+
29
+ def on_device_online_status_changed(self, dev, online):
30
+ print(f"{dev.serial} {'online' if online else 'offline'}")
31
+
32
+ mx = mx_remote.Remote(callbacks=MyCallbacks())
33
+ ```
34
+
35
+ Available callback methods:
36
+
37
+ | Method | Trigger |
38
+ |---|---|
39
+ | `on_device_update` | any device property changed |
40
+ | `on_bay_update` | any bay property changed |
41
+ | `on_device_config_changed` | device configuration updated |
42
+ | `on_device_config_complete` | all device configuration received |
43
+ | `on_device_online_status_changed` | device went online/offline |
44
+ | `on_device_temperature_changed` | temperature readings changed |
45
+ | `on_bay_registered` | new bay discovered |
46
+ | `on_video_source_changed` | video routing changed |
47
+ | `on_audio_source_changed` | audio routing changed |
48
+ | `on_volume_changed` | volume or mute status changed |
49
+ | `on_power_changed` | CEC power status changed |
50
+ | `on_name_changed` | user-assigned bay name changed |
51
+ | `on_status_signal_detected_changed` | signal detect status changed |
52
+ | `on_status_faulty_changed` | fault status changed |
53
+ | `on_status_hidden_changed` | hidden status changed |
54
+ | `on_status_poe_powered_changed` | PoE power status changed |
55
+ | `on_status_hdbt_connected_changed` | HDBaseT link status changed |
56
+ | `on_status_signal_type_changed` | signal type changed |
57
+ | `on_status_hpd_detected_changed` | hotplug detect changed |
58
+ | `on_status_cec_detected_changed` | CEC device detected/lost |
59
+ | `on_status_arc_changed` | audio return channel status changed |
60
+ | `on_key_pressed` | remote control key press received |
61
+ | `on_action_received` | remote control action received |
62
+ | `on_bay_linked` | virtual link created |
63
+ | `on_bay_unlinked` | virtual link removed |
64
+ | `on_mirror_status_changed` | bay mirroring changed |
65
+ | `on_filter_status_changed` | bay filtering changed |
66
+ | `on_edid_profile_changed` | EDID profile changed |
67
+ | `on_rc_type_changed` | remote control type changed |
68
+ | `on_amp_zone_settings_changed` | amplifier zone settings changed |
69
+ | `on_amp_dolby_settings_changed` | amplifier Dolby settings changed |
70
+
71
+ You can also register per-device and per-bay callbacks:
72
+
73
+ ```python
74
+ def on_device_changed(device):
75
+ print(f"{device.serial} updated")
76
+
77
+ def on_bay_changed(bay):
78
+ print(f"{bay.bay_label} updated")
79
+
80
+ device.register_callback(on_device_changed)
81
+ bay.register_callback(on_bay_changed)
82
+
83
+ # to unregister:
84
+ device.unregister_callback(on_device_changed)
85
+ bay.unregister_callback(on_bay_changed)
86
+ ```
87
+
88
+ ---
89
+
90
+ [Documentation index](README.md) | [Project README](../README.md)
@@ -0,0 +1,133 @@
1
+ # Configuration
2
+
3
+ Opening a connection and keeping it in step with the mesh.
4
+
5
+ ## Core Concepts
6
+
7
+ ### Remote
8
+
9
+ `Remote` is the main entry point. It manages the UDP connection (multicast or broadcast), handles device discovery, and maintains a registry of all discovered devices.
10
+
11
+ ```python
12
+ # default: multicast on 224.8.8.8:8812
13
+ mx = mx_remote.Remote()
14
+
15
+ # use broadcast instead
16
+ mx = mx_remote.Remote(broadcast=True)
17
+
18
+ # bind to a specific network interface
19
+ mx = mx_remote.Remote(local_ip="192.168.1.100")
20
+
21
+ # custom target address and port
22
+ mx = mx_remote.Remote(target_ip="10.8.8.255", port=8811)
23
+
24
+ # offline mode for processing capture files
25
+ mx = mx_remote.Remote(open_connection=False)
26
+
27
+ # separate identity, to run concurrent clients from one account
28
+ mx = mx_remote.Remote(uid_path="/var/lib/myapp/.mxr-uid")
29
+ ```
30
+
31
+ The uid at `uid_path` (default `~/.mxr-uid`) is this client's identity on the mesh:
32
+ addressing is by uid in the payload, and a frame carrying our own uid is dropped as
33
+ an echo. Two clients sharing a uid are one peer to every device and each silently
34
+ discards everything the other sends, so give concurrent clients separate paths.
35
+
36
+ ### Device
37
+
38
+ 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.
39
+
40
+ ```python
41
+ # look up a device by serial number or unique ID
42
+ device = mx.get_by_serial("AB1234")
43
+ device = mx.get_by_uid(uid)
44
+
45
+ # device properties
46
+ device.serial # serial number
47
+ device.name # device name
48
+ device.model_name # model (e.g. "neo:8", "OneIP Transmitter")
49
+ device.address # IP address
50
+ device.version # firmware version
51
+ device.online # True if responding
52
+ device.status # DeviceStatus enum (ONLINE, OFFLINE, REBOOTING, BOOTING, INACTIVE)
53
+ device.features # DeviceFeatures bitmask
54
+ device.config_initialised # False if the device broadcasts config blocks built from
55
+ # uninitialised memory; scaling, bay 0 addresses and the
56
+ # rc target padding are unreliable from it
57
+ device.temperatures # dict of temperature sensor readings
58
+
59
+ # device type checks
60
+ device.is_v2ip # Pulse-Eight OneIP HDMI-over-IP device
61
+ device.is_video_matrix # neo video matrix
62
+ device.is_audio_matrix # audio-only matrix
63
+ device.is_amp # ProAmp8 audio amplifier
64
+ device.is_oneip_tx # OneIP transmitter
65
+ device.is_oneip_rx # OneIP receiver
66
+ device.is_oneip_tz # OneIP transceiver
67
+ device.is_oneip_multiviewer # OneIP multiviewer
68
+ device.supports_video_wall # sink can crop its source to a video wall window
69
+ # (advertised ~1s after boot by the v2ipwall module,
70
+ # and never withdrawn once advertised)
71
+
72
+ # iterate bays
73
+ for port, bay in device.bays.items():
74
+ print(bay)
75
+ for name, bay in device.inputs.items():
76
+ print(f"Input: {name}")
77
+ for name, bay in device.outputs.items():
78
+ print(f"Output: {name}")
79
+ ```
80
+
81
+ ### Bay
82
+
83
+ A `BayBase` represents a single input or output on a device (e.g. "Input 1", "Output 3").
84
+
85
+ ```python
86
+ bay.bay_name # port name (e.g. "Input 1")
87
+ bay.user_name # user-assigned name
88
+ bay.is_input # True if source/input
89
+ bay.is_output # True if sink/output
90
+ bay.is_hdmi # True if HDMI
91
+ bay.is_audio # True if audio-only
92
+ bay.is_hdbaset # True if HDBaseT
93
+ bay.signal_detected # video/audio signal present
94
+ bay.power_status # PowerStatus enum (ON, OFF, UNKNOWN)
95
+ bay.faulty # fault detected
96
+ bay.hidden # hidden from UI
97
+ bay.online # device is online
98
+ bay.features # BayFeaturesMask
99
+ bay.status # DeviceStatus enum
100
+
101
+ # video/audio routing (output bays)
102
+ bay.video_source # currently selected video source bay
103
+ bay.audio_source # currently selected audio source bay
104
+ bay.available_video_sources # list of selectable video sources
105
+ bay.available_audio_sources # list of selectable audio sources
106
+
107
+ # volume (bays with volume control)
108
+ bay.volume # current volume percentage (or None)
109
+ bay.muted # True if muted (or None)
110
+
111
+ # EDID and remote control (input bays)
112
+ bay.edid_profile # EdidProfile enum
113
+ bay.rc_type # RCType enum (IR, CEC, Sky, TiVo, etc.)
114
+ ```
115
+
116
+ ## Configuration Updates
117
+
118
+ Update connection settings at runtime:
119
+
120
+ ```python
121
+ await mx.update_config(
122
+ target_ip="10.8.8.255",
123
+ port=8811,
124
+ local_ip="192.168.1.100",
125
+ broadcast=True,
126
+ callbacks=MyCallbacks(),
127
+ name="My Application",
128
+ )
129
+ ```
130
+
131
+ ---
132
+
133
+ [Documentation index](README.md) | [Project README](../README.md)
@@ -0,0 +1,47 @@
1
+ # Devices and bays
2
+
3
+ Naming, hiding, EDID and the device registry.
4
+
5
+ ## Device Management
6
+
7
+ ```python
8
+ # reboot a device
9
+ await device.reboot()
10
+
11
+ # read the device log
12
+ log = await device.get_log()
13
+
14
+ # call an HTTP API endpoint on the device
15
+ result = await device.get_api("system/status")
16
+ ```
17
+
18
+ ## Bay Naming
19
+
20
+ ```python
21
+ await bay.set_name("Living Room TV")
22
+ ```
23
+
24
+ ## Bay Visibility
25
+
26
+ Hide or show bays:
27
+
28
+ ```python
29
+ await bay.set_hidden(True) # hide
30
+ await bay.set_hidden(False) # show
31
+ ```
32
+
33
+ ## EDID Profiles
34
+
35
+ Change the EDID profile on input bays:
36
+
37
+ ```python
38
+ from mx_remote import EdidProfile
39
+
40
+ await bay.select_edid_profile(EdidProfile.TEMPLATE_1080P_STEREO)
41
+ await bay.select_edid_profile(EdidProfile.TEMPLATE_4K_HDR_7_1)
42
+ await bay.select_edid_profile(EdidProfile.LOWEST_COMMON_DENOMINATOR)
43
+ ```
44
+
45
+ ---
46
+
47
+ [Documentation index](README.md) | [Project README](../README.md)