mx-remote 9.4.0__tar.gz → 11.0.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 (291) hide show
  1. {mx_remote-9.4.0 → mx_remote-11.0.0}/PKG-INFO +1 -1
  2. {mx_remote-9.4.0 → mx_remote-11.0.0}/docs/api.md +33 -2
  3. mx_remote-11.0.0/docs/audio.md +98 -0
  4. {mx_remote-9.4.0 → mx_remote-11.0.0}/docs/callbacks.md +2 -0
  5. {mx_remote-9.4.0 → mx_remote-11.0.0}/docs/configuration.md +40 -10
  6. {mx_remote-9.4.0 → mx_remote-11.0.0}/docs/devices-and-bays.md +33 -1
  7. {mx_remote-9.4.0 → mx_remote-11.0.0}/docs/diagnostics.md +11 -5
  8. {mx_remote-9.4.0 → mx_remote-11.0.0}/docs/oneip.md +147 -4
  9. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/Interface.py +439 -27
  10. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/Interface.pyi +164 -10
  11. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/const.py +1 -1
  12. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/main.py +6 -2
  13. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/Constants.py +78 -19
  14. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/Constants.pyi +21 -7
  15. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameBayConfig.py +2 -0
  16. mx_remote-11.0.0/mx_remote/proto/FrameConnectStatus.py +62 -0
  17. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameConnectStatus.pyi +3 -1
  18. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameFactoryReset.py +7 -0
  19. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameFactoryReset.pyi +3 -0
  20. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameFilterStatus.py +8 -1
  21. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameFilterStatus.pyi +3 -1
  22. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameHello.py +71 -15
  23. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameHello.pyi +13 -0
  24. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameMeshOperation.py +39 -36
  25. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameMeshOperation.pyi +8 -13
  26. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameMirrorStatus.py +8 -1
  27. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameMirrorStatus.pyi +3 -1
  28. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameNetworkStatus.py +12 -0
  29. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameNetworkStatus.pyi +7 -0
  30. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FramePing.py +3 -2
  31. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameRCSettings.py +17 -2
  32. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameRCSettings.pyi +4 -2
  33. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameReboot.py +8 -1
  34. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameReboot.pyi +2 -0
  35. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameSetInstaller.py +7 -0
  36. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameSetInstaller.pyi +3 -0
  37. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameSetupStatus.py +6 -0
  38. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameSetupStatus.pyi +3 -0
  39. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameSignalStatusNew.py +21 -11
  40. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameSignalStatusNew.pyi +2 -0
  41. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameTopology.py +6 -1
  42. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameUpgradeFPGA.py +9 -3
  43. mx_remote-11.0.0/mx_remote/proto/FrameUpgradeFPGA.pyi +6 -0
  44. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPAudio.py +56 -36
  45. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPAudio.pyi +13 -9
  46. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.py +95 -9
  47. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.pyi +12 -2
  48. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPPowerSave.py +22 -2
  49. mx_remote-11.0.0/mx_remote/proto/FrameV2IPPowerSave.pyi +17 -0
  50. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPSourceSwitch.py +10 -3
  51. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPSourceSwitch.pyi +1 -1
  52. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPSources.py +41 -1
  53. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPSources.pyi +5 -1
  54. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPTiling.py +9 -1
  55. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPTiling.pyi +3 -1
  56. mx_remote-11.0.0/mx_remote/remote/AudioRoute.py +87 -0
  57. mx_remote-11.0.0/mx_remote/remote/AudioRoute.pyi +3 -0
  58. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/remote/Bay.py +45 -13
  59. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/remote/Bay.pyi +8 -3
  60. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/remote/ConnectionAsync.py +117 -63
  61. mx_remote-11.0.0/mx_remote/remote/ConnectionAsync.pyi +38 -0
  62. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/remote/Device.py +629 -35
  63. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/remote/Device.pyi +76 -5
  64. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/remote/Remote.py +505 -57
  65. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/remote/Remote.pyi +59 -10
  66. mx_remote-11.0.0/mx_remote/remote/Tree.py +103 -0
  67. mx_remote-11.0.0/mx_remote/remote/Tree.pyi +14 -0
  68. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/audiolock.py +1 -1
  69. mx_remote-11.0.0/tests/audioroute.py +469 -0
  70. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/complete.py +1 -1
  71. mx_remote-11.0.0/tests/connect.py +85 -0
  72. mx_remote-11.0.0/tests/controllerset.py +241 -0
  73. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/deprecated.py +1 -1
  74. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/downmix.py +26 -8
  75. mx_remote-11.0.0/tests/endpoints.py +279 -0
  76. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/entry.py +2 -1
  77. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/hello.py +7 -6
  78. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/lengths.py +1 -1
  79. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/mesh.py +3 -3
  80. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/meshbays.py +1 -1
  81. mx_remote-11.0.0/tests/meshcontrol.py +396 -0
  82. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/offline.py +1 -1
  83. mx_remote-11.0.0/tests/oneiproute.py +179 -0
  84. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/osdmessage.py +1 -1
  85. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/osdsync.py +1 -1
  86. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/ping.py +11 -5
  87. mx_remote-11.0.0/tests/preview.py +91 -0
  88. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/privacy.py +23 -1
  89. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/protogate.py +1 -1
  90. mx_remote-11.0.0/tests/registry.py +528 -0
  91. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/roundtrip.py +1 -1
  92. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/routes.py +1 -1
  93. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/run.py +8 -0
  94. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/smoke.py +1 -1
  95. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/staterequest.py +5 -4
  96. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/thirdparty.py +1 -1
  97. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/txresult.py +13 -1
  98. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/unit.py +1 -1
  99. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/v2ipscaling.py +1 -1
  100. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/v2ipsdp.py +1 -1
  101. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/v2ipsettings.py +2 -2
  102. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/v2ipsubject.py +5 -6
  103. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/v2iptestcard.py +12 -8
  104. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/v2ipvlan.py +1 -1
  105. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/vdetails.py +39 -3
  106. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/wirefix.py +22 -1
  107. mx_remote-9.4.0/docs/audio.md +0 -56
  108. mx_remote-9.4.0/mx_remote/proto/FrameConnectStatus.py +0 -35
  109. mx_remote-9.4.0/mx_remote/proto/FrameUpgradeFPGA.pyi +0 -3
  110. mx_remote-9.4.0/mx_remote/proto/FrameV2IPPowerSave.pyi +0 -12
  111. mx_remote-9.4.0/mx_remote/remote/ConnectionAsync.pyi +0 -27
  112. {mx_remote-9.4.0 → mx_remote-11.0.0}/.gitignore +0 -0
  113. {mx_remote-9.4.0 → mx_remote-11.0.0}/AGENTS.md +0 -0
  114. {mx_remote-9.4.0 → mx_remote-11.0.0}/CLAUDE.md +0 -0
  115. {mx_remote-9.4.0 → mx_remote-11.0.0}/LICENSE +0 -0
  116. {mx_remote-9.4.0 → mx_remote-11.0.0}/README.md +0 -0
  117. {mx_remote-9.4.0 → mx_remote-11.0.0}/docs/README.md +0 -0
  118. {mx_remote-9.4.0 → mx_remote-11.0.0}/docs/_static/brand.css +0 -0
  119. {mx_remote-9.4.0 → mx_remote-11.0.0}/docs/_static/favicon.svg +0 -0
  120. {mx_remote-9.4.0 → mx_remote-11.0.0}/docs/_static/logo-dark.svg +0 -0
  121. {mx_remote-9.4.0 → mx_remote-11.0.0}/docs/_static/logo.svg +0 -0
  122. {mx_remote-9.4.0 → mx_remote-11.0.0}/docs/conf.py +0 -0
  123. {mx_remote-9.4.0 → mx_remote-11.0.0}/docs/index.md +0 -0
  124. {mx_remote-9.4.0 → mx_remote-11.0.0}/docs/multiviewer.md +0 -0
  125. {mx_remote-9.4.0 → mx_remote-11.0.0}/docs/requirements.txt +0 -0
  126. {mx_remote-9.4.0 → mx_remote-11.0.0}/docs/routing.md +0 -0
  127. {mx_remote-9.4.0 → mx_remote-11.0.0}/hatch_build.py +0 -0
  128. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/Uid.py +0 -0
  129. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/Uid.pyi +0 -0
  130. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/__init__.py +0 -0
  131. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/__init__.pyi +0 -0
  132. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/compat.py +0 -0
  133. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/compat.pyi +0 -0
  134. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/const.pyi +0 -0
  135. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/deprecated.py +0 -0
  136. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/deprecated.pyi +0 -0
  137. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/main.pyi +0 -0
  138. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/BayConfig.py +0 -0
  139. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/BayConfig.pyi +0 -0
  140. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/Data.py +0 -0
  141. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/Data.pyi +0 -0
  142. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/Factory.py +0 -0
  143. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/Factory.pyi +0 -0
  144. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameAmpDolbySettings.py +0 -0
  145. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameAmpDolbySettings.pyi +0 -0
  146. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameAmpZoneSettings.py +0 -0
  147. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameAmpZoneSettings.pyi +0 -0
  148. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameAudioClip.py +0 -0
  149. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameAudioClip.pyi +0 -0
  150. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameAudioSetRoute.py +0 -0
  151. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameAudioSetRoute.pyi +0 -0
  152. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameBase.py +0 -0
  153. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameBase.pyi +0 -0
  154. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameBayConfig.pyi +0 -0
  155. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameBayConfigSecondary.py +0 -0
  156. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameBayConfigSecondary.pyi +0 -0
  157. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameBayContent.py +0 -0
  158. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameBayContent.pyi +0 -0
  159. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameBayHide.py +0 -0
  160. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameBayHide.pyi +0 -0
  161. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameBayStatus.py +0 -0
  162. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameBayStatus.pyi +0 -0
  163. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameDebug.py +0 -0
  164. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameDebug.pyi +0 -0
  165. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameDiscover.py +0 -0
  166. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameDiscover.pyi +0 -0
  167. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameEDID.py +0 -0
  168. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameEDID.pyi +0 -0
  169. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameEDIDProfile.py +0 -0
  170. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameEDIDProfile.pyi +0 -0
  171. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameFirmwareVersion.py +0 -0
  172. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameFirmwareVersion.pyi +0 -0
  173. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameHeader.py +0 -0
  174. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameHeader.pyi +0 -0
  175. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameLinks.py +0 -0
  176. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameLinks.pyi +0 -0
  177. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameMonitoringPulse.py +0 -0
  178. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameMonitoringPulse.pyi +0 -0
  179. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FramePing.pyi +0 -0
  180. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FramePowerChange.py +0 -0
  181. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FramePowerChange.pyi +0 -0
  182. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameRCAction.py +0 -0
  183. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameRCAction.pyi +0 -0
  184. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameRCIr.py +0 -0
  185. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameRCIr.pyi +0 -0
  186. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameRCKey.py +0 -0
  187. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameRCKey.pyi +0 -0
  188. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameRoutingChange.py +0 -0
  189. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameRoutingChange.pyi +0 -0
  190. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameSetName.py +0 -0
  191. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameSetName.pyi +0 -0
  192. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameSetRoute.py +0 -0
  193. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameSetRoute.pyi +0 -0
  194. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameSignalStatus.py +0 -0
  195. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameSignalStatus.pyi +0 -0
  196. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameSysTemperature.py +0 -0
  197. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameSysTemperature.pyi +0 -0
  198. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameSystemStatus.py +0 -0
  199. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameSystemStatus.pyi +0 -0
  200. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameTXRCAction.py +0 -0
  201. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameTXRCAction.pyi +0 -0
  202. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameTXRCKey.py +0 -0
  203. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameTXRCKey.pyi +0 -0
  204. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameThirdParty.py +0 -0
  205. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameThirdParty.pyi +0 -0
  206. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameTime.py +0 -0
  207. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameTime.pyi +0 -0
  208. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameTimeZone.py +0 -0
  209. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameTimeZone.pyi +0 -0
  210. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameTopology.pyi +0 -0
  211. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameTxIR.py +0 -0
  212. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameTxIR.pyi +0 -0
  213. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameUnitCommand.py +0 -0
  214. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameUnitCommand.pyi +0 -0
  215. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameUnitStatus.py +0 -0
  216. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameUnitStatus.pyi +0 -0
  217. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPBayMapping.py +0 -0
  218. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPBayMapping.pyi +0 -0
  219. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPDetectBays.py +0 -0
  220. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPDetectBays.pyi +0 -0
  221. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPLink.py +0 -0
  222. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPLink.pyi +0 -0
  223. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.py +0 -0
  224. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.pyi +0 -0
  225. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPMultiviewer.py +0 -0
  226. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPMultiviewer.pyi +0 -0
  227. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPOsdSync.py +0 -0
  228. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPOsdSync.pyi +0 -0
  229. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPPrivacy.py +0 -0
  230. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPPrivacy.pyi +0 -0
  231. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPSetMaster.py +0 -0
  232. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPSetMaster.pyi +0 -0
  233. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPSettingsAll.py +0 -0
  234. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPSettingsAll.pyi +0 -0
  235. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPSinkSdp.py +0 -0
  236. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPSinkSdp.pyi +0 -0
  237. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPStats.py +0 -0
  238. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPStats.pyi +0 -0
  239. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPTestcard.py +0 -0
  240. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPTestcard.pyi +0 -0
  241. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPVideoWall.py +0 -0
  242. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameV2IPVideoWall.pyi +0 -0
  243. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameVolume.py +0 -0
  244. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameVolume.pyi +0 -0
  245. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameVolumeDown.py +0 -0
  246. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameVolumeDown.pyi +0 -0
  247. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameVolumeSet.py +0 -0
  248. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameVolumeSet.pyi +0 -0
  249. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameVolumeUp.py +0 -0
  250. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/FrameVolumeUp.pyi +0 -0
  251. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/LinkConfig.py +0 -0
  252. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/LinkConfig.pyi +0 -0
  253. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/Multiviewer.py +0 -0
  254. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/Multiviewer.pyi +0 -0
  255. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/Svd.py +0 -0
  256. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/Svd.pyi +0 -0
  257. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/V2IPConfig.py +0 -0
  258. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/V2IPConfig.pyi +0 -0
  259. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/V2IPStats.py +0 -0
  260. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/V2IPStats.pyi +0 -0
  261. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/__init__.py +0 -0
  262. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/__init__.pyi +0 -0
  263. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/proto/svd.csv +0 -0
  264. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/py.typed +0 -0
  265. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/remote/Link.py +0 -0
  266. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/remote/Link.pyi +0 -0
  267. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/remote/OsdSync.py +0 -0
  268. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/remote/OsdSync.pyi +0 -0
  269. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/remote/State.py +0 -0
  270. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/remote/State.pyi +0 -0
  271. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/remote/V2IP.py +0 -0
  272. {mx_remote-9.4.0 → mx_remote-11.0.0}/mx_remote/remote/V2IP.pyi +0 -0
  273. {mx_remote-9.4.0 → mx_remote-11.0.0}/pyproject.toml +0 -0
  274. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/audio_ir.py +0 -0
  275. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/baycontent.py +0 -0
  276. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/capture.py +0 -0
  277. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/e2e.py +0 -0
  278. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/handlers.py +0 -0
  279. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/handlers_cover.py +0 -0
  280. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/idempotent.py +0 -0
  281. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/merge.py +0 -0
  282. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/newops.py +0 -0
  283. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/offsets.py +0 -0
  284. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/proto_stamp.py +0 -0
  285. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/rcsettings.py +0 -0
  286. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/sources.py +0 -0
  287. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/stats.py +0 -0
  288. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/structs.py +0 -0
  289. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/uid.py +0 -0
  290. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/unknowns.py +0 -0
  291. {mx_remote-9.4.0 → mx_remote-11.0.0}/tests/v2ipptp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: mx_remote
3
- Version: 9.4.0
3
+ Version: 11.0.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/
@@ -13,8 +13,10 @@ the directive source as prose and the build still succeeds.
13
13
 
14
14
  ## Client
15
15
 
16
- `Remote` owns the socket, the device registry and the background probe that
17
- keeps both in step with the mesh.
16
+ `Remote` owns the two endpoints, the device registry and the background probe
17
+ that keeps both in step with the mesh. `reboot_all()`, `reboot_mesh()`,
18
+ `factory_reset_all()`, `request_all_oneip_power_save()` and
19
+ `upgrade_all_video_processors()` act on every unit at once.
18
20
 
19
21
  ```{eval-rst}
20
22
  .. automodule:: mx_remote.remote.Remote
@@ -185,6 +187,14 @@ How a device's output mixes 5.1 and 7.1 PCM down to stereo.
185
187
  .. autodata:: mx_remote.proto.Constants.ONEIP_DOWNMIX_LFE_MAX
186
188
  ```
187
189
 
190
+ ## OneIP locale
191
+
192
+ The locale a device runs, by its number.
193
+
194
+ ```{eval-rst}
195
+ .. autodata:: mx_remote.proto.Constants.ONEIP_LOCALE_UNKNOWN
196
+ ```
197
+
188
198
  ## OneIP PTP
189
199
 
190
200
  A device's PTP settings, which it reports and a controller writes, and the PTP
@@ -357,6 +367,27 @@ The time zone and clock a mesh controller announces.
357
367
  :members:
358
368
  ```
359
369
 
370
+ ## Mesh control
371
+
372
+ The operations a mesh controller sends, and what a controller announces.
373
+
374
+ ```{eval-rst}
375
+ .. autoclass:: mx_remote.MeshOperationMessage
376
+ :members:
377
+
378
+ .. autoclass:: mx_remote.MeshControllerReport
379
+ :members:
380
+
381
+ .. autoclass:: mx_remote.proto.Constants.MeshOperation
382
+ :members:
383
+
384
+ .. autodata:: mx_remote.proto.Constants.ONEIP_SOURCE_FILTER_MAX
385
+
386
+ .. autodata:: mx_remote.proto.Constants.MXR_RC_STATUS_MAX
387
+
388
+ .. autodata:: mx_remote.proto.FrameV2IPSources.ONEIP_SOURCE_LIST_MAX
389
+ ```
390
+
360
391
  ## Names from before OneIP
361
392
 
362
393
  Releases up to 5.11 used the firmware's internal names in the public API: V2IP
@@ -0,0 +1,98 @@
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
+ ## Audio endpoints
16
+
17
+ A OneIP device reports its audio endpoints, each with its features and a status
18
+ word on the same bits: `FEATURE_MUTE` while it is muted, `FEATURE_TRIGGER`
19
+ while its trigger is active, and `FEATURE_AUDIO_LOCK` while its audio source is
20
+ locked.
21
+
22
+ ```python
23
+ ep = device.audio_endpoint_by_id(1)
24
+ print(ep.features, ep.status, ep.audio_locked)
25
+
26
+ # keep the endpoint's audio source when the video route changes
27
+ await device.set_audio_endpoint_locked(1, True)
28
+ ```
29
+
30
+ Only an endpoint whose features include `FEATURE_AUDIO_LOCK` is written, since
31
+ the device ignores the rest. Once the frame is away `audio_locked` reads the new
32
+ lock, until the device reports its endpoints again. A report whose only change
33
+ is a status, the routing or the device's own address fires the device's
34
+ callbacks.
35
+
36
+ `device.audio_endpoints.address` is the device's own address and HTTP port, as
37
+ its report gives them. `device.audio_reported` is when its last report was
38
+ taken, as `time.time()` read it.
39
+
40
+ ### What a command holds
41
+
42
+ For 500 ms after an audio command to a device, its features and links reports
43
+ are ignored, so one it sent before it took the command cannot undo what the
44
+ command set. The report after that replaces it.
45
+
46
+ ## Audio routes
47
+
48
+ A route can run from an endpoint on one unit to an endpoint on another, such as
49
+ an encoder's HDMI input to a decoder's HDMI output. Every unit owning an
50
+ endpoint on the way switches its own hops, and each finds the path itself from
51
+ the two units' endpoint tables. `audio_route()` finds the same path and returns
52
+ it hop by hop from the source, or `None` when there is none:
53
+
54
+ ```python
55
+ hops = mx.audio_route(decoder.remote_id, 3, encoder.remote_id, 2)
56
+ for hop in hops: # AudioHop(sink_device, sink, source_device, source)
57
+ print(hop)
58
+
59
+ await mx.select_audio_endpoint_input(decoder.remote_id, 3, encoder.remote_id, 2)
60
+ ```
61
+
62
+ `select_audio_endpoint_input()` sends the request to every unit on the path,
63
+ from the sink's end, and each unit's own hops read routed once its frame is
64
+ away. It is refused, sending nothing, when no reported path leads from the
65
+ source to the sink, and when the path reaches a unit on released firmware up to
66
+ 4.4.2 from another unit, since such a unit routes only between its own
67
+ endpoints. An output bay's `select_audio_source(source, endpoint=...)` goes the
68
+ same way.
69
+
70
+ The path is searched back from the sink, within the sink's unit, depth first.
71
+ An endpoint takes the source directly when it is routed from the source's
72
+ endpoint id, or when it receives a OneIP stream and the source sends one.
73
+ Otherwise the endpoints it is routed from are tried in ascending id, a later
74
+ one kept only when its path is strictly shorter. Failing that, the source
75
+ unit's first sending endpoint feeds the sink unit's first receiving one.
76
+
77
+ ## Remote Control
78
+
79
+ Send remote control key presses and actions:
80
+
81
+ ```python
82
+ from mx_remote import RCKey, RCAction
83
+
84
+ # send a key press
85
+ await bay.send_key(RCKey.KEY_SELECT)
86
+ await bay.send_key(RCKey.KEY_UP)
87
+
88
+ # send a remote control action
89
+ await bay.tx_action(RCAction.ACTION_POWER_ON)
90
+ await bay.tx_action(RCAction.ACTION_POWER_OFF)
91
+ await bay.tx_action(RCAction.ACTION_POWER_TOGGLE)
92
+ await bay.tx_action(RCAction.ACTION_VOLUME_UP)
93
+ await bay.tx_action(RCAction.ACTION_VOLUME_DOWN)
94
+ ```
95
+
96
+ ---
97
+
98
+ [Documentation index](README.md) | [Project README](https://github.com/opdenkamp/mx-remote#readme)
@@ -41,6 +41,7 @@ Available callback methods:
41
41
  | `on_device_config_changed` | device configuration updated |
42
42
  | `on_device_config_complete` | all device configuration received |
43
43
  | `on_device_online_status_changed` | device went online/offline |
44
+ | `on_device_unregistered` | device forgotten after three offline windows of silence |
44
45
  | `on_device_temperature_changed` | temperature readings changed |
45
46
  | `on_bay_registered` | new bay discovered |
46
47
  | `on_video_source_changed` | video routing changed |
@@ -68,6 +69,7 @@ Available callback methods:
68
69
  | `on_amp_zone_settings_changed` | amplifier zone settings changed |
69
70
  | `on_amp_dolby_settings_changed` | amplifier Dolby settings changed |
70
71
  | `on_third_party_message` | another application sent a message under its vendor id |
72
+ | `on_mesh_operation` | a device sent a mesh operation other than a membership or controller report |
71
73
 
72
74
  You can also register per-device and per-bay callbacks:
73
75
 
@@ -6,28 +6,46 @@ Opening a connection and keeping it in step with the mesh.
6
6
 
7
7
  ### Remote
8
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.
9
+ `Remote` is the main entry point. It manages the UDP connection, handles device discovery, and maintains a registry of all discovered devices.
10
10
 
11
11
  ```python
12
- # default: multicast on 224.8.8.8:8812
12
+ # both endpoints a unit has: the group 224.8.8.8:8812 and the subnet broadcast on 8811
13
13
  mx = mx_remote.Remote()
14
14
 
15
- # use broadcast instead
16
- mx = mx_remote.Remote(broadcast=True)
17
-
18
15
  # bind to a specific network interface
19
16
  mx = mx_remote.Remote(local_ip="192.168.1.100")
20
17
 
21
- # custom target address and port
22
- mx = mx_remote.Remote(target_ip="10.8.8.255", port=8811)
18
+ # tests and development only: one endpoint on a scratch address in place of both
19
+ mx = mx_remote.Remote(target_ip="239.255.77.90", port=18890)
23
20
 
24
21
  # offline mode for processing capture files
25
22
  mx = mx_remote.Remote(open_connection=False)
26
23
 
27
24
  # separate identity, to run concurrent clients from one account
28
25
  mx = mx_remote.Remote(uid_path="/var/lib/myapp/.mxr-uid")
26
+
27
+ # a mesh device that can take the controller role, under its own serial and version
28
+ mx = mx_remote.Remote(mesh_device=True, serial="P9SN66660600", version="1.2.3")
29
29
  ```
30
30
 
31
+ Each frame leaves by the endpoints a unit picks for it:
32
+
33
+ - a frame for every unit at once goes to both: a discover, reboot or factory
34
+ reset of every unit, the monitoring pulse, bay detection and the video
35
+ processor upgrade;
36
+ - the hello goes to both when it is the first since the sockets opened, or the
37
+ last one there went 30 s ago or more;
38
+ - a frame meant for one device alone, such as a ping or a remote-control
39
+ action, goes by the endpoint that device is heard by;
40
+ - every other frame goes to the group, and to the broadcast address as well
41
+ while some peer has not been heard by multicast for two minutes or reports
42
+ that its multicast fails.
43
+
44
+ A peer heard only by broadcast in that time has this client announce
45
+ `DeviceFeature.STATUS_MCAST_FAULT` itself, at once. A datagram arriving by both
46
+ endpoints within 100 ms is acted on once. `broadcast=` is deprecated and
47
+ ignored.
48
+
31
49
  The uid at `uid_path` (default `~/.mxr-uid`) is this client's identity on the mesh:
32
50
  addressing is by uid in the payload, and a frame carrying our own uid is dropped as
33
51
  an echo. Two clients sharing a uid are one peer to every device and each silently
@@ -37,6 +55,13 @@ discards everything the other sends, so give concurrent clients separate paths.
37
55
 
38
56
  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
57
 
58
+ The registry is kept as a unit keeps its own. Every frame from a live device
59
+ keeps it alive, and only a hello brings back one gone silent. A device silent
60
+ for three times the window that takes it offline is forgotten:
61
+ `on_device_unregistered` fires, and a hello from it later registers it as a new
62
+ device. `mx.device_tree` lists every device and this client as a unit lists
63
+ them: depth first, each under the device whose topology report names it.
64
+
40
65
  ```python
41
66
  # look up a device by serial number or unique ID
42
67
  device = mx.get_by_serial("AB1234")
@@ -49,7 +74,14 @@ device.model_name # model (e.g. "neo:8", "OneIP Transmitter")
49
74
  device.address # IP address
50
75
  device.version # firmware version
51
76
  device.online # True if responding
77
+ device.bays_online # online, and its bays have arrived since it was heard,
78
+ # rebooted or went offline, as a unit judges it
79
+ device.multicast # heard by multicast in the last two minutes; None on one endpoint
52
80
  device.status # DeviceStatus enum (ONLINE, OFFLINE, REBOOTING, BOOTING, INACTIVE)
81
+ device.raw_name # name, version and serial as the bytes announced,
82
+ device.raw_version # never substituted or decoded
83
+ device.raw_serial
84
+ device.igmp_querier # the IGMP querier its last uplink report names, or None
53
85
  device.features # DeviceFeatures bitmask
54
86
  device.config_initialised # False if the device broadcasts config blocks built from
55
87
  # uninitialised memory; scaling, bay 0 addresses and the
@@ -97,6 +129,7 @@ bay.hidden # hidden from UI
97
129
  bay.online # device is online
98
130
  bay.features # BayFeaturesMask
99
131
  bay.status # DeviceStatus enum
132
+ bay.status_word # the whole BayStatusMask as last sent, or None
100
133
 
101
134
  # video/audio routing (output bays)
102
135
  bay.video_source # currently selected video source bay
@@ -119,10 +152,7 @@ Update connection settings at runtime:
119
152
 
120
153
  ```python
121
154
  await mx.update_config(
122
- target_ip="10.8.8.255",
123
- port=8811,
124
155
  local_ip="192.168.1.100",
125
- broadcast=True,
126
156
  callbacks=MyCallbacks(),
127
157
  name="My Application",
128
158
  )
@@ -20,14 +20,20 @@ result = await device.get_api("system/status")
20
20
 
21
21
  A device on protocol 0x2A pings a peer it suspects is gone and takes it
22
22
  offline when nothing answers within about 1.5s. The client answers a ping
23
- addressed to it with a hello at once, from any sender.
23
+ addressed to it with a hello at once, from a device it has heard, as a unit
24
+ does.
24
25
 
25
26
  ## Bay Naming
26
27
 
27
28
  ```python
28
29
  await bay.set_name("Living Room TV")
30
+ await bay.set_name("") # back to the default name the unit picks
29
31
  ```
30
32
 
33
+ An empty name asks the unit to reset the bay to its default. The unit picks
34
+ that name itself, so the bay keeps its current name until the unit reports the
35
+ new one in its next bay list.
36
+
31
37
  ## Bay Visibility
32
38
 
33
39
  Hide or show bays:
@@ -65,6 +71,32 @@ cleans it, so it reads as the unit's own API shows it. The content outlives the
65
71
  signal, so check `signal_detected` before showing it. `on_bay_content_changed`
66
72
  reports a change, and an empty `content` is a source showing nothing.
67
73
 
74
+ ## Signal and Connected Device
75
+
76
+ `signal_type` is a description written for display. `video_details` holds the
77
+ parts it is written from, so a caller can name the signal its own way:
78
+
79
+ ```python
80
+ d = bay.video_details
81
+ if d is not None and d.from_report:
82
+ print(d.width, d.height, d.colour, d.bpp, d.interlaced, d.hdr)
83
+ ```
84
+
85
+ A signal status report carries all of them for a valid stream, with `svd` 0 and
86
+ the measured size for a stream whose mode the CTA table lacks. The bay config
87
+ snapshot, which `video_details` falls back to, carries no size or flags, and
88
+ leaves them None.
89
+
90
+ A unit reports the Pulse-Eight device connected to a bay, such as an amplifier
91
+ on a matrix output, in the long form of its connect frame:
92
+
93
+ ```python
94
+ if (p8 := bay.p8_device) is not None:
95
+ print(p8.model, p8.serial, p8.version, p8.ip)
96
+ ```
97
+
98
+ It is None after a short or a disconnecting connect frame.
99
+
68
100
  ## Mesh Bays
69
101
 
70
102
  A OneIP unit lists the inputs and outputs of the other units in its mesh as
@@ -14,14 +14,21 @@ for port_id, port_status in device.network_status.items():
14
14
  print(f" IP: {port_status.ip}")
15
15
  if port_status.mac_address:
16
16
  print(f" MAC: {port_status.mac_address}")
17
+ if port_status.uplink:
18
+ print(" the device's uplink")
19
+ if port_status.video_link:
20
+ print(" carries video")
17
21
  ```
18
22
 
23
+ `uplink` and `video_link` are read from protocol 0x22; an older report marks
24
+ neither.
25
+
19
26
  ## CLI Application
20
27
 
21
28
  The `mxr` console application is installed with the package. It discovers devices and logs all received frames in human-readable form.
22
29
 
23
30
  ```
24
- usage: mxr [-h] [-i INPUT] [-f FILTER] [-o OUTPUT] [-l LOCAL_IP] [-b]
31
+ usage: mxr [-h] [-i INPUT] [-f FILTER] [-o OUTPUT] [-l LOCAL_IP]
25
32
 
26
33
  MX Remote Manager / Debugger
27
34
 
@@ -31,9 +38,11 @@ options:
31
38
  -f FILTER only log frames from this ip address
32
39
  -o OUTPUT write output to a file
33
40
  -l LOCAL_IP local ip address of the network interface to use
34
- -b use broadcast mode instead of multicast
35
41
  ```
36
42
 
43
+ `mxr` always runs both endpoints a unit has, the multicast group and the subnet
44
+ broadcast. `-b` is still accepted, and ignored.
45
+
37
46
  ### Examples
38
47
 
39
48
  ```bash
@@ -43,9 +52,6 @@ mxr
43
52
  # bind to a specific network interface
44
53
  mxr -l 192.168.1.100
45
54
 
46
- # use broadcast mode
47
- mxr -b
48
-
49
55
  # log output to a file
50
56
  mxr -o /path/to/output.txt
51
57
 
@@ -32,6 +32,12 @@ if device.oneip_details:
32
32
  if details.dscp and details.dscp.complete:
33
33
  print(f"DSCP: {details.dscp}") # video / audio / anc, 0..63
34
34
 
35
+ # mark a source's three streams; a unit takes a marking only for all three at
36
+ # once, so a partial one or one above ONEIP_DSCP_MAX is refused before sending,
37
+ # as is a device that announces no source. Read back from oneip_details.dscp
38
+ # until the device reports its own.
39
+ await device.set_oneip_dscp(mx_remote.OneIPDscpConfig(video=46, audio=34, anc=26))
40
+
35
41
  # streaming statistics
36
42
  # The subscription lapses 60s after the request and there is no free-running
37
43
  # mode, so call this again inside the minute to keep the 1Hz reports coming.
@@ -125,6 +131,11 @@ await device.set_oneip_output_mode(mode)
125
131
  await device.set_oneip_auto_scaling(True) # back on, if that is what you want
126
132
 
127
133
  await device.clear_oneip_output_mode() # the only way to say "no mode"
134
+
135
+ # skip the check that the display lists 4:2:0; one validity bit covers this and
136
+ # match-source, so the write restates match-source as the sink last reported it
137
+ print(scaling.skip_420, scaling.match_source)
138
+ await device.set_oneip_skip_420(True)
128
139
  ```
129
140
 
130
141
  **Set the mode with automatic scaling off.** A sink scaling automatically
@@ -240,8 +251,16 @@ print(device.unit_status.power_save_level(OneIPPowerSaveLevel.LOW)) # e.g. SUPP
240
251
 
241
252
  await device.set_oneip_power_save(True)
242
253
  await mx.set_all_oneip_power_save(False) # every OneIP device of the mesh
254
+
255
+ # ask for the state now, as a mesh controller does, rather than the setting
256
+ await device.request_oneip_power_save(True) # 0x41 naming the unit
257
+ await mx.request_all_oneip_power_save(False) # 0x41 with the flag alone
243
258
  ```
244
259
 
260
+ A power save request reaches a unit only from its mesh controller or a
261
+ management application, and one that cannot power down ignores it. A unit tells
262
+ the two forms apart by their exact length.
263
+
245
264
  Switched off, `OneIPDeviceSetting.POWER_SAVE_DISABLED` is on: the device does
246
265
  not power down by itself, and a power save request still powers it down.
247
266
  Switching on is written as a mesh controller writes it, with that switch clear
@@ -335,7 +354,10 @@ the LFE channel is mixed in at, which a controller writes, and the state and
335
354
  channel count, which only the device reports. A device that reports none has
336
355
  no downmix to set, so a write to one is refused, as is a mode, level or LFE
337
356
  percent out of range. The write sends the mode, level and LFE together; the
338
- state and channels are never sent.
357
+ status, whether the stream is mixed down now, is the device's own and never
358
+ sent. After this client's write it is None until the device reports how it
359
+ mixes the new settings; another controller's write keeps the status the device
360
+ reported.
339
361
 
340
362
  ```python
341
363
  from mx_remote import OneIPDownmix
@@ -344,7 +366,8 @@ from mx_remote.proto.Constants import OneIPDownmixMode, OneIPDownmixLevel
344
366
  downmix = device.oneip_downmix # None: no report yet, or no downmix
345
367
  if downmix is not None:
346
368
  print(downmix.mode, downmix.level, downmix.lfe) # LFE up to ONEIP_DOWNMIX_LFE_MAX
347
- print(downmix.state, downmix.channels) # the device's own
369
+ if downmix.status is not None: # the device's own
370
+ print(downmix.status.state, downmix.status.channels)
348
371
  await device.set_oneip_downmix(OneIPDownmix(mode=OneIPDownmixMode.AUTO,
349
372
  level=OneIPDownmixLevel.NORMALISED, lfe=50))
350
373
  ```
@@ -352,6 +375,40 @@ if downmix is not None:
352
375
  The downmix ends the settings block, behind a second valid word, so the write
353
376
  runs on to 248 bytes and carries no other setting.
354
377
 
378
+ ### Locale
379
+
380
+ A device whose firmware has locales reports the one it runs by its number, its
381
+ position in the list the device's `/system/locale` names, behind the downmix.
382
+ `ONEIP_LOCALE_UNKNOWN` is a stored locale the firmware does not know.
383
+
384
+ ```python
385
+ print(device.oneip_locale) # None: no report yet, or no locales
386
+ await device.set_oneip_locale(1)
387
+ ```
388
+
389
+ A device stores a locale it knows and ignores one it does not, so the write goes
390
+ also to a device that has not reported its settings yet, and the locale is
391
+ cached once sent until the device's next report. Refused before sending:
392
+ `ONEIP_LOCALE_UNKNOWN`, a number that is not a byte, and a device whose settings
393
+ arrived without a locale, as its firmware has none. Another controller's write
394
+ reaches only a device that reports a locale.
395
+
396
+ ### Live preview
397
+
398
+ A device whose firmware sends a live preview names the multicast group it sends
399
+ it to at the end of its settings block. The preview is RFC 2435 RTP/JPEG on
400
+ UDP port `ONEIP_PREVIEW_PORT` (5880).
401
+
402
+ ```python
403
+ from mx_remote.proto.Constants import ONEIP_PREVIEW_PORT
404
+
405
+ print(device.oneip_preview) # '239.7.8.9', or None: no report, or no preview
406
+ ```
407
+
408
+ Only the device's own report names it; another sender's configuration about the
409
+ device leaves it alone. A group of 0, or a block too short to hold one, is
410
+ firmware that sends no preview.
411
+
355
412
  ### VLAN
356
413
 
357
414
  A device that announces `DeviceFeature.VLAN` tags its uplink by a VLAN
@@ -368,12 +425,18 @@ print(vlan.device, vlan.port, vlan.pinned_uplink_port, vlan.active_uplink_port)
368
425
  print(vlan.is_pending, vlan.revert_s)
369
426
 
370
427
  await device.set_oneip_vlan(OneIPVlan(device=10, port=(0, 20, 0), uplink=2))
428
+ if device.oneip_vlan.is_pending:
429
+ await device.confirm_oneip_vlan() # keep it, as the mesh controller does
371
430
  ```
372
431
 
373
432
  Only the device knows what it runs, so `oneip_vlan` is read only from the device
374
433
  describing itself, and a write is not cached. The device applies a change at
375
434
  once and reverts it after `revert_s` seconds unless the mesh controller, hearing
376
- it report the change as pending, confirms it.
435
+ it report the change as pending, confirms it. `confirm_oneip_vlan()` sends that
436
+ confirmation: the pending configuration echoed with its trunk bit and CONFIRM. A
437
+ device repeats its report every 3 s while the change is pending, so a
438
+ confirmation lost or sent twice is answered by the next report. Refused unless
439
+ the device announces the VLAN feature and last reported a pending change.
377
440
 
378
441
  Only the ids, the uplink and the trunk bit are written. Refused before sending:
379
442
  a device without the VLAN feature or that has not reported its configuration, an
@@ -384,7 +447,7 @@ frame it already understood.
384
447
 
385
448
  ### Test pattern, tone and lip-sync
386
449
 
387
- A sink whose video processor reports `OneIPVideoProcessorFeature.SINK_TEST_PATTERN` draws a
450
+ A sink whose video processor reports `OneIPVideoProcessorFeature.RTOS` draws a
388
451
  test card on its output: a pattern, a test tone, and a lip-sync flash that marks
389
452
  a frame and beeps a set number of sample periods after it.
390
453
 
@@ -509,6 +572,11 @@ await mx.request_all_oneip_privacy() # every source unit of the mesh
509
572
  print(source.oneip_privacy) # None until the unit has reported
510
573
  ```
511
574
 
575
+ The video processor picks a new key generator every boot, so a hello showing
576
+ the unit may have restarted - it was offline, flipped its reboot toggle or is
577
+ about to reboot - drops the report, and `oneip_privacy` reads None until the
578
+ unit reports again.
579
+
512
580
  A request to a unit that is not a source, or that is below protocol 0x30, is
513
581
  refused before it is sent.
514
582
 
@@ -557,6 +625,7 @@ from mx_remote import UnitNetConfig
557
625
 
558
626
  await device.clear_unit_status()
559
627
  await device.upgrade_unit_video_processor(force=False)
628
+ await mx.upgrade_all_video_processors() # every unit, flashing only a differing image
560
629
  await device.set_unit_network(UnitNetConfig(
561
630
  ip='10.1.2.3', netmask='255.255.255.0', gateway='10.1.2.254', dns1='10.1.2.254'))
562
631
  await device.confirm_unit_network() # keep it, once the unit reports it
@@ -595,6 +664,80 @@ the source's next configuration report carries the addresses it ends up with, in
595
664
  `oneip_details`. A device that is not a source, or announces less than protocol
596
665
  0x2B, would ignore the operation, so it is refused before anything is sent.
597
666
 
667
+ ### Taking the controller role
668
+
669
+ A client can stand in for a unit as mesh controller. It announces itself as a
670
+ mesh device rather than a management application, so a mesh takes it as a
671
+ member, and under a serial and version of its own so the device list tells it
672
+ apart from the unit it replaces:
673
+
674
+ ```python
675
+ from mx_remote import MeshOperationMessage
676
+
677
+ mx = mx_remote.Remote(mesh_device=True, serial='P9SN66660600', version='1.2.3')
678
+ mx.set_mesh_controller(mx.uid) # follow itself as controller; None for none
679
+ mx.set_mesh_bays_mapped(True) # the role is announced only with bays mapped
680
+ await mx.send_mesh_operation(MeshOperationMessage.report_controller(mx.uid, priority=1, installer=0x1234))
681
+ await mx.send_mesh_operation(MeshOperationMessage.register(unit.remote_id, mx.uid))
682
+ await mx.send_oneip_source_list(sources) # the mesh's whole source list
683
+ await mx.set_mesh_setup_completed(True)
684
+ await mx.set_mesh_installer(0x1234)
685
+
686
+ await output.set_oneip_mirroring(other_output) # None to stop
687
+ await output.set_oneip_source_filter([source.remote_id])
688
+ await output.set_oneip_tiling(pos_x=0, pos_y=0, width=1920, height=1080)
689
+ await unit.set_rc_settings(rc_target=RCType.CEC, ip=None, cec_enabled=True, cec_auto_on=True,
690
+ forward_rc=True, forward_ir=False)
691
+ ```
692
+
693
+ `set_mesh_controller()` changes only the announcement, at once: membership
694
+ while there is a controller, and the controller role while it is this client
695
+ and `set_mesh_bays_mapped()` says it has bays, as a unit announces it.
696
+ `mx.features` is what the hello announces as of now. `send_oneip_detect_bays()`
697
+ asks every unit to detect its bays, which current firmware ignores.
698
+ Membership reports and the units that follow this client are the caller's to
699
+ keep in step. A mesh device's writes reach a unit only while that unit follows
700
+ it as its controller.
701
+
702
+ A unit named in a source list, hearing it from the controller it follows,
703
+ registers every unit listed and unregisters every source unit that is not, so
704
+ the list must always be complete. A list longer than 36 entries goes in
705
+ numbered pages.
706
+
707
+ What is set on an output is held once the frame is away: `mirroring` and
708
+ `filtered` on its output bay read it until the output reports again. Each write
709
+ is refused before anything is sent where a unit would drop it: a device that is
710
+ not a OneIP output (or unit, for remote control settings), an output whose bay
711
+ has not been reported, an output mirroring itself, a filter longer than
712
+ `ONEIP_SOURCE_FILTER_MAX` (89), or a field that does not fit its width.
713
+
714
+ A controller routes an output unit by the streams a source unit advertises for
715
+ its own input, so the output plays them whether or not it lists the source:
716
+
717
+ ```python
718
+ await output.route_oneip_unit(source) # audio follows the video
719
+ await output.route_oneip_unit(source, audio=other) # audio from another unit's input
720
+ await output.route_oneip_unit(source, power_on=False) # leave the display as it is
721
+ await output.route_oneip_unit_with_arc(source, tv_unit) # audio from tv_unit's audio return
722
+ await output.unroute_oneip_unit() # names no streams
723
+ ```
724
+
725
+ Each powers the output's display on with the switch unless `power_on=False`. An
726
+ output below protocol 0x27 is told the source's own audio rather than none, as
727
+ `select_video_source()` does. `route_oneip_unit_with_arc()` names the audio
728
+ return group the other unit last reported in `oneip_details.arc`; no unit maps
729
+ that group to a bay, so on current firmware the output plays it on the default
730
+ audio port, 50022, while the audio return is sent on 50023, and such a route
731
+ plays no audio. It is what a controller sends all the same. A route is refused
732
+ before anything is sent where the device is not a OneIP output, a source has no
733
+ input of its own or has not reported its stream addresses, or the other unit has
734
+ not reported a usable audio return group.
735
+
736
+ A unit's controller report is kept as `device.mesh_controller_report`, and its
737
+ membership report as `mesh_master`. Every other mesh operation a device sends
738
+ arrives at `MxrCallbacks.on_mesh_operation()`, one this library has no name for
739
+ with its raw value.
740
+
598
741
  ### Time zone and time
599
742
 
600
743
  The mesh controller announces its time zone and clock with every periodic