mx-remote 9.4.0__tar.gz → 12.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-12.0.0}/PKG-INFO +1 -1
  2. {mx_remote-9.4.0 → mx_remote-12.0.0}/docs/api.md +54 -2
  3. mx_remote-12.0.0/docs/audio.md +98 -0
  4. {mx_remote-9.4.0 → mx_remote-12.0.0}/docs/callbacks.md +2 -0
  5. {mx_remote-9.4.0 → mx_remote-12.0.0}/docs/configuration.md +40 -10
  6. {mx_remote-9.4.0 → mx_remote-12.0.0}/docs/devices-and-bays.md +33 -1
  7. {mx_remote-9.4.0 → mx_remote-12.0.0}/docs/diagnostics.md +11 -5
  8. {mx_remote-9.4.0 → mx_remote-12.0.0}/docs/oneip.md +176 -4
  9. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/Interface.py +489 -28
  10. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/Interface.pyi +184 -11
  11. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/const.py +1 -1
  12. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/main.py +6 -2
  13. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/Constants.py +98 -20
  14. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/Constants.pyi +27 -7
  15. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameBayConfig.py +2 -0
  16. mx_remote-12.0.0/mx_remote/proto/FrameConnectStatus.py +62 -0
  17. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameConnectStatus.pyi +3 -1
  18. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameFactoryReset.py +7 -0
  19. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameFactoryReset.pyi +3 -0
  20. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameFilterStatus.py +8 -1
  21. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameFilterStatus.pyi +3 -1
  22. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameHello.py +73 -15
  23. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameHello.pyi +13 -0
  24. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameMeshOperation.py +39 -36
  25. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameMeshOperation.pyi +8 -13
  26. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameMirrorStatus.py +8 -1
  27. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameMirrorStatus.pyi +3 -1
  28. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameNetworkStatus.py +12 -0
  29. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameNetworkStatus.pyi +7 -0
  30. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FramePing.py +3 -2
  31. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameRCSettings.py +38 -5
  32. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameRCSettings.pyi +6 -2
  33. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameReboot.py +8 -1
  34. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameReboot.pyi +2 -0
  35. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameSetInstaller.py +7 -0
  36. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameSetInstaller.pyi +3 -0
  37. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameSetupStatus.py +6 -0
  38. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameSetupStatus.pyi +3 -0
  39. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameSignalStatusNew.py +21 -11
  40. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameSignalStatusNew.pyi +2 -0
  41. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameTopology.py +6 -1
  42. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameUpgradeFPGA.py +9 -3
  43. mx_remote-12.0.0/mx_remote/proto/FrameUpgradeFPGA.pyi +6 -0
  44. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPAudio.py +56 -36
  45. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPAudio.pyi +13 -9
  46. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.py +100 -9
  47. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.pyi +12 -2
  48. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPPowerSave.py +22 -2
  49. mx_remote-12.0.0/mx_remote/proto/FrameV2IPPowerSave.pyi +17 -0
  50. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPSourceSwitch.py +10 -3
  51. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPSourceSwitch.pyi +1 -1
  52. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPSources.py +41 -1
  53. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPSources.pyi +5 -1
  54. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPTiling.py +20 -10
  55. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPTiling.pyi +3 -1
  56. mx_remote-12.0.0/mx_remote/remote/AudioRoute.py +87 -0
  57. mx_remote-12.0.0/mx_remote/remote/AudioRoute.pyi +3 -0
  58. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/remote/Bay.py +45 -13
  59. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/remote/Bay.pyi +8 -3
  60. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/remote/ConnectionAsync.py +117 -63
  61. mx_remote-12.0.0/mx_remote/remote/ConnectionAsync.pyi +38 -0
  62. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/remote/Device.py +682 -35
  63. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/remote/Device.pyi +79 -5
  64. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/remote/Remote.py +508 -57
  65. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/remote/Remote.pyi +59 -10
  66. mx_remote-12.0.0/mx_remote/remote/Tree.py +103 -0
  67. mx_remote-12.0.0/mx_remote/remote/Tree.pyi +14 -0
  68. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/audiolock.py +1 -1
  69. mx_remote-12.0.0/tests/audioroute.py +469 -0
  70. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/complete.py +1 -1
  71. mx_remote-12.0.0/tests/connect.py +85 -0
  72. mx_remote-12.0.0/tests/controllerset.py +244 -0
  73. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/deprecated.py +1 -1
  74. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/downmix.py +26 -8
  75. mx_remote-12.0.0/tests/endpoints.py +279 -0
  76. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/entry.py +2 -1
  77. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/hello.py +7 -6
  78. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/lengths.py +1 -1
  79. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/mesh.py +3 -3
  80. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/meshbays.py +1 -1
  81. mx_remote-12.0.0/tests/meshcontrol.py +406 -0
  82. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/offline.py +1 -1
  83. mx_remote-12.0.0/tests/oneiproute.py +179 -0
  84. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/osdmessage.py +1 -1
  85. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/osdsync.py +1 -1
  86. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/ping.py +11 -5
  87. mx_remote-12.0.0/tests/preview.py +93 -0
  88. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/privacy.py +23 -1
  89. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/protogate.py +1 -1
  90. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/rcsettings.py +71 -0
  91. mx_remote-12.0.0/tests/registry.py +528 -0
  92. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/roundtrip.py +1 -1
  93. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/routes.py +1 -1
  94. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/run.py +8 -0
  95. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/smoke.py +1 -1
  96. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/staterequest.py +5 -4
  97. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/thirdparty.py +1 -1
  98. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/txresult.py +13 -1
  99. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/unit.py +8 -1
  100. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/v2ipscaling.py +1 -1
  101. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/v2ipsdp.py +1 -1
  102. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/v2ipsettings.py +2 -2
  103. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/v2ipsubject.py +5 -6
  104. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/v2iptestcard.py +12 -8
  105. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/v2ipvlan.py +1 -1
  106. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/vdetails.py +39 -3
  107. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/wirefix.py +36 -1
  108. mx_remote-9.4.0/docs/audio.md +0 -56
  109. mx_remote-9.4.0/mx_remote/proto/FrameConnectStatus.py +0 -35
  110. mx_remote-9.4.0/mx_remote/proto/FrameUpgradeFPGA.pyi +0 -3
  111. mx_remote-9.4.0/mx_remote/proto/FrameV2IPPowerSave.pyi +0 -12
  112. mx_remote-9.4.0/mx_remote/remote/ConnectionAsync.pyi +0 -27
  113. {mx_remote-9.4.0 → mx_remote-12.0.0}/.gitignore +0 -0
  114. {mx_remote-9.4.0 → mx_remote-12.0.0}/AGENTS.md +0 -0
  115. {mx_remote-9.4.0 → mx_remote-12.0.0}/CLAUDE.md +0 -0
  116. {mx_remote-9.4.0 → mx_remote-12.0.0}/LICENSE +0 -0
  117. {mx_remote-9.4.0 → mx_remote-12.0.0}/README.md +0 -0
  118. {mx_remote-9.4.0 → mx_remote-12.0.0}/docs/README.md +0 -0
  119. {mx_remote-9.4.0 → mx_remote-12.0.0}/docs/_static/brand.css +0 -0
  120. {mx_remote-9.4.0 → mx_remote-12.0.0}/docs/_static/favicon.svg +0 -0
  121. {mx_remote-9.4.0 → mx_remote-12.0.0}/docs/_static/logo-dark.svg +0 -0
  122. {mx_remote-9.4.0 → mx_remote-12.0.0}/docs/_static/logo.svg +0 -0
  123. {mx_remote-9.4.0 → mx_remote-12.0.0}/docs/conf.py +0 -0
  124. {mx_remote-9.4.0 → mx_remote-12.0.0}/docs/index.md +0 -0
  125. {mx_remote-9.4.0 → mx_remote-12.0.0}/docs/multiviewer.md +0 -0
  126. {mx_remote-9.4.0 → mx_remote-12.0.0}/docs/requirements.txt +0 -0
  127. {mx_remote-9.4.0 → mx_remote-12.0.0}/docs/routing.md +0 -0
  128. {mx_remote-9.4.0 → mx_remote-12.0.0}/hatch_build.py +0 -0
  129. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/Uid.py +0 -0
  130. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/Uid.pyi +0 -0
  131. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/__init__.py +0 -0
  132. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/__init__.pyi +0 -0
  133. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/compat.py +0 -0
  134. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/compat.pyi +0 -0
  135. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/const.pyi +0 -0
  136. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/deprecated.py +0 -0
  137. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/deprecated.pyi +0 -0
  138. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/main.pyi +0 -0
  139. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/BayConfig.py +0 -0
  140. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/BayConfig.pyi +0 -0
  141. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/Data.py +0 -0
  142. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/Data.pyi +0 -0
  143. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/Factory.py +0 -0
  144. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/Factory.pyi +0 -0
  145. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameAmpDolbySettings.py +0 -0
  146. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameAmpDolbySettings.pyi +0 -0
  147. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameAmpZoneSettings.py +0 -0
  148. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameAmpZoneSettings.pyi +0 -0
  149. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameAudioClip.py +0 -0
  150. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameAudioClip.pyi +0 -0
  151. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameAudioSetRoute.py +0 -0
  152. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameAudioSetRoute.pyi +0 -0
  153. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameBase.py +0 -0
  154. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameBase.pyi +0 -0
  155. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameBayConfig.pyi +0 -0
  156. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameBayConfigSecondary.py +0 -0
  157. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameBayConfigSecondary.pyi +0 -0
  158. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameBayContent.py +0 -0
  159. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameBayContent.pyi +0 -0
  160. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameBayHide.py +0 -0
  161. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameBayHide.pyi +0 -0
  162. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameBayStatus.py +0 -0
  163. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameBayStatus.pyi +0 -0
  164. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameDebug.py +0 -0
  165. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameDebug.pyi +0 -0
  166. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameDiscover.py +0 -0
  167. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameDiscover.pyi +0 -0
  168. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameEDID.py +0 -0
  169. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameEDID.pyi +0 -0
  170. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameEDIDProfile.py +0 -0
  171. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameEDIDProfile.pyi +0 -0
  172. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameFirmwareVersion.py +0 -0
  173. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameFirmwareVersion.pyi +0 -0
  174. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameHeader.py +0 -0
  175. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameHeader.pyi +0 -0
  176. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameLinks.py +0 -0
  177. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameLinks.pyi +0 -0
  178. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameMonitoringPulse.py +0 -0
  179. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameMonitoringPulse.pyi +0 -0
  180. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FramePing.pyi +0 -0
  181. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FramePowerChange.py +0 -0
  182. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FramePowerChange.pyi +0 -0
  183. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameRCAction.py +0 -0
  184. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameRCAction.pyi +0 -0
  185. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameRCIr.py +0 -0
  186. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameRCIr.pyi +0 -0
  187. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameRCKey.py +0 -0
  188. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameRCKey.pyi +0 -0
  189. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameRoutingChange.py +0 -0
  190. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameRoutingChange.pyi +0 -0
  191. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameSetName.py +0 -0
  192. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameSetName.pyi +0 -0
  193. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameSetRoute.py +0 -0
  194. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameSetRoute.pyi +0 -0
  195. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameSignalStatus.py +0 -0
  196. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameSignalStatus.pyi +0 -0
  197. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameSysTemperature.py +0 -0
  198. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameSysTemperature.pyi +0 -0
  199. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameSystemStatus.py +0 -0
  200. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameSystemStatus.pyi +0 -0
  201. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameTXRCAction.py +0 -0
  202. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameTXRCAction.pyi +0 -0
  203. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameTXRCKey.py +0 -0
  204. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameTXRCKey.pyi +0 -0
  205. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameThirdParty.py +0 -0
  206. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameThirdParty.pyi +0 -0
  207. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameTime.py +0 -0
  208. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameTime.pyi +0 -0
  209. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameTimeZone.py +0 -0
  210. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameTimeZone.pyi +0 -0
  211. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameTopology.pyi +0 -0
  212. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameTxIR.py +0 -0
  213. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameTxIR.pyi +0 -0
  214. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameUnitCommand.py +0 -0
  215. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameUnitCommand.pyi +0 -0
  216. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameUnitStatus.py +0 -0
  217. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameUnitStatus.pyi +0 -0
  218. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPBayMapping.py +0 -0
  219. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPBayMapping.pyi +0 -0
  220. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPDetectBays.py +0 -0
  221. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPDetectBays.pyi +0 -0
  222. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPLink.py +0 -0
  223. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPLink.pyi +0 -0
  224. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.py +0 -0
  225. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.pyi +0 -0
  226. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPMultiviewer.py +0 -0
  227. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPMultiviewer.pyi +0 -0
  228. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPOsdSync.py +0 -0
  229. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPOsdSync.pyi +0 -0
  230. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPPrivacy.py +0 -0
  231. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPPrivacy.pyi +0 -0
  232. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPSetMaster.py +0 -0
  233. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPSetMaster.pyi +0 -0
  234. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPSettingsAll.py +0 -0
  235. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPSettingsAll.pyi +0 -0
  236. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPSinkSdp.py +0 -0
  237. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPSinkSdp.pyi +0 -0
  238. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPStats.py +0 -0
  239. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPStats.pyi +0 -0
  240. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPTestcard.py +0 -0
  241. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPTestcard.pyi +0 -0
  242. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPVideoWall.py +0 -0
  243. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameV2IPVideoWall.pyi +0 -0
  244. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameVolume.py +0 -0
  245. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameVolume.pyi +0 -0
  246. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameVolumeDown.py +0 -0
  247. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameVolumeDown.pyi +0 -0
  248. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameVolumeSet.py +0 -0
  249. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameVolumeSet.pyi +0 -0
  250. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameVolumeUp.py +0 -0
  251. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/FrameVolumeUp.pyi +0 -0
  252. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/LinkConfig.py +0 -0
  253. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/LinkConfig.pyi +0 -0
  254. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/Multiviewer.py +0 -0
  255. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/Multiviewer.pyi +0 -0
  256. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/Svd.py +0 -0
  257. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/Svd.pyi +0 -0
  258. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/V2IPConfig.py +0 -0
  259. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/V2IPConfig.pyi +0 -0
  260. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/V2IPStats.py +0 -0
  261. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/V2IPStats.pyi +0 -0
  262. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/__init__.py +0 -0
  263. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/__init__.pyi +0 -0
  264. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/proto/svd.csv +0 -0
  265. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/py.typed +0 -0
  266. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/remote/Link.py +0 -0
  267. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/remote/Link.pyi +0 -0
  268. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/remote/OsdSync.py +0 -0
  269. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/remote/OsdSync.pyi +0 -0
  270. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/remote/State.py +0 -0
  271. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/remote/State.pyi +0 -0
  272. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/remote/V2IP.py +0 -0
  273. {mx_remote-9.4.0 → mx_remote-12.0.0}/mx_remote/remote/V2IP.pyi +0 -0
  274. {mx_remote-9.4.0 → mx_remote-12.0.0}/pyproject.toml +0 -0
  275. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/audio_ir.py +0 -0
  276. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/baycontent.py +0 -0
  277. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/capture.py +0 -0
  278. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/e2e.py +0 -0
  279. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/handlers.py +0 -0
  280. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/handlers_cover.py +0 -0
  281. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/idempotent.py +0 -0
  282. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/merge.py +0 -0
  283. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/newops.py +0 -0
  284. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/offsets.py +0 -0
  285. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/proto_stamp.py +0 -0
  286. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/sources.py +0 -0
  287. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/stats.py +0 -0
  288. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/structs.py +0 -0
  289. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/uid.py +0 -0
  290. {mx_remote-9.4.0 → mx_remote-12.0.0}/tests/unknowns.py +0 -0
  291. {mx_remote-9.4.0 → mx_remote-12.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: 12.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
@@ -126,6 +128,15 @@ The key and action codes a bay reports, and the target a key was aimed at.
126
128
  :members:
127
129
  :undoc-members:
128
130
 
131
+ .. autoclass:: mx_remote.proto.Constants.RCStatus
132
+ :members:
133
+ :undoc-members:
134
+
135
+ .. autoclass:: mx_remote.RCSettings
136
+ :members:
137
+
138
+ .. autodata:: mx_remote.KEEP_REPORTED
139
+
129
140
  .. autodata:: mx_remote.proto.Constants.MXR_PROTOCOL_VERSION
130
141
  ```
131
142
 
@@ -185,6 +196,26 @@ How a device's output mixes 5.1 and 7.1 PCM down to stereo.
185
196
  .. autodata:: mx_remote.proto.Constants.ONEIP_DOWNMIX_LFE_MAX
186
197
  ```
187
198
 
199
+ ## OneIP locale
200
+
201
+ The locale a device runs, by its name, such as `NL_NL`.
202
+
203
+ ```{eval-rst}
204
+ .. autodata:: mx_remote.proto.Constants.ONEIP_LOCALE_MAX
205
+ ```
206
+
207
+ ## OneIP tiling
208
+
209
+ The window a OneIP output takes for a video wall.
210
+
211
+ ```{eval-rst}
212
+ .. autodata:: mx_remote.proto.Constants.ONEIP_TILING_X_STEP
213
+
214
+ .. autodata:: mx_remote.proto.Constants.ONEIP_TILING_WIDTH_STEP
215
+
216
+ .. autodata:: mx_remote.proto.Constants.ONEIP_TILING_MIN
217
+ ```
218
+
188
219
  ## OneIP PTP
189
220
 
190
221
  A device's PTP settings, which it reports and a controller writes, and the PTP
@@ -357,6 +388,27 @@ The time zone and clock a mesh controller announces.
357
388
  :members:
358
389
  ```
359
390
 
391
+ ## Mesh control
392
+
393
+ The operations a mesh controller sends, and what a controller announces.
394
+
395
+ ```{eval-rst}
396
+ .. autoclass:: mx_remote.MeshOperationMessage
397
+ :members:
398
+
399
+ .. autoclass:: mx_remote.MeshControllerReport
400
+ :members:
401
+
402
+ .. autoclass:: mx_remote.proto.Constants.MeshOperation
403
+ :members:
404
+
405
+ .. autodata:: mx_remote.proto.Constants.ONEIP_SOURCE_FILTER_MAX
406
+
407
+ .. autodata:: mx_remote.proto.Constants.MXR_RC_STATUS_MAX
408
+
409
+ .. autodata:: mx_remote.proto.FrameV2IPSources.ONEIP_SOURCE_LIST_MAX
410
+ ```
411
+
360
412
  ## Names from before OneIP
361
413
 
362
414
  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,42 @@ 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 name, such
381
+ as `NL_NL`: up to `ONEIP_LOCALE_MAX` bytes, NUL padded, at the end of its
382
+ settings block, behind the preview group.
383
+
384
+ ```python
385
+ print(device.oneip_locale) # 'EN_UK', or None: no report yet, or no locales
386
+ await device.set_oneip_locale('NL_NL')
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. The write runs on to 256
392
+ bytes, as a device takes a locale only from a block that holds the whole name.
393
+ Refused before sending: a name that is empty, longer than `ONEIP_LOCALE_MAX`
394
+ bytes or not printable ASCII, and a device whose settings arrived without a
395
+ locale, as its firmware has none. Another controller's write reaches only a
396
+ device that reports a locale.
397
+
398
+ ### Live preview
399
+
400
+ A device whose firmware sends a live preview names the multicast group it sends
401
+ it to at the end of its settings block. The preview is RFC 2435 RTP/JPEG on
402
+ UDP port `ONEIP_PREVIEW_PORT` (50024), after the stream ports.
403
+
404
+ ```python
405
+ from mx_remote.proto.Constants import ONEIP_PREVIEW_PORT
406
+
407
+ print(device.oneip_preview) # '239.7.8.9', or None: no report, or no preview
408
+ ```
409
+
410
+ Only the device's own report names it; another sender's configuration about the
411
+ device leaves it alone. A group of 0, or a block too short to hold one, is
412
+ firmware that sends no preview.
413
+
355
414
  ### VLAN
356
415
 
357
416
  A device that announces `DeviceFeature.VLAN` tags its uplink by a VLAN
@@ -368,12 +427,18 @@ print(vlan.device, vlan.port, vlan.pinned_uplink_port, vlan.active_uplink_port)
368
427
  print(vlan.is_pending, vlan.revert_s)
369
428
 
370
429
  await device.set_oneip_vlan(OneIPVlan(device=10, port=(0, 20, 0), uplink=2))
430
+ if device.oneip_vlan.is_pending:
431
+ await device.confirm_oneip_vlan() # keep it, as the mesh controller does
371
432
  ```
372
433
 
373
434
  Only the device knows what it runs, so `oneip_vlan` is read only from the device
374
435
  describing itself, and a write is not cached. The device applies a change at
375
436
  once and reverts it after `revert_s` seconds unless the mesh controller, hearing
376
- it report the change as pending, confirms it.
437
+ it report the change as pending, confirms it. `confirm_oneip_vlan()` sends that
438
+ confirmation: the pending configuration echoed with its trunk bit and CONFIRM. A
439
+ device repeats its report every 3 s while the change is pending, so a
440
+ confirmation lost or sent twice is answered by the next report. Refused unless
441
+ the device announces the VLAN feature and last reported a pending change.
377
442
 
378
443
  Only the ids, the uplink and the trunk bit are written. Refused before sending:
379
444
  a device without the VLAN feature or that has not reported its configuration, an
@@ -384,7 +449,7 @@ frame it already understood.
384
449
 
385
450
  ### Test pattern, tone and lip-sync
386
451
 
387
- A sink whose video processor reports `OneIPVideoProcessorFeature.SINK_TEST_PATTERN` draws a
452
+ A sink whose video processor reports `OneIPVideoProcessorFeature.RTOS` draws a
388
453
  test card on its output: a pattern, a test tone, and a lip-sync flash that marks
389
454
  a frame and beeps a set number of sample periods after it.
390
455
 
@@ -471,6 +536,11 @@ save then takes this content rather than the controller's. Logo images are
471
536
  served over HTTP from this client, on `logo_port` or a free port. Content a unit
472
537
  would refuse is refused before anything is sent; see `send_osd_sync`.
473
538
 
539
+ A caption setting with `OsdCaptionField.DEFAULT_TEXT` in place of
540
+ `OsdCaptionField.TEXT`, and no text, sends a unit back to its layout's default
541
+ caption, drawn in its own locale. A unit whose firmware predates it keeps the
542
+ text it has.
543
+
474
544
  ### Overlay messages
475
545
 
476
546
  A unit shows a message on its output, over the picture, until it restarts. The
@@ -509,6 +579,11 @@ await mx.request_all_oneip_privacy() # every source unit of the mesh
509
579
  print(source.oneip_privacy) # None until the unit has reported
510
580
  ```
511
581
 
582
+ The video processor picks a new key generator every boot, so a hello showing
583
+ the unit may have restarted - it was offline, flipped its reboot toggle or is
584
+ about to reboot - drops the report, and `oneip_privacy` reads None until the
585
+ unit reports again.
586
+
512
587
  A request to a unit that is not a source, or that is below protocol 0x30, is
513
588
  refused before it is sent.
514
589
 
@@ -557,6 +632,7 @@ from mx_remote import UnitNetConfig
557
632
 
558
633
  await device.clear_unit_status()
559
634
  await device.upgrade_unit_video_processor(force=False)
635
+ await mx.upgrade_all_video_processors() # every unit, flashing only a differing image
560
636
  await device.set_unit_network(UnitNetConfig(
561
637
  ip='10.1.2.3', netmask='255.255.255.0', gateway='10.1.2.254', dns1='10.1.2.254'))
562
638
  await device.confirm_unit_network() # keep it, once the unit reports it
@@ -568,6 +644,9 @@ await device.stop_locating_unit()
568
644
  A unit is located only once its status carries `UnitFlag.LOCATE_SUPPORTED`. Its
569
645
  board-ID LED flashes and an output shows "Locate Device"; `UnitFlag.LOCATE` in
570
646
  the cached status says so from the moment the command is sent.
647
+ `UnitFlag.FIRMWARE_UPGRADE` says the update service offered the unit a firmware
648
+ upgrade, and `UnitFlag.UPGRADE_INSTALLING` that it is writing one to its flash,
649
+ with its picture gone until it restarts.
571
650
 
572
651
  A changed IP configuration applies at once and reverts unless the mesh
573
652
  controller confirms it in time; `status.network_pending` says whether it still
@@ -595,6 +674,99 @@ the source's next configuration report carries the addresses it ends up with, in
595
674
  `oneip_details`. A device that is not a source, or announces less than protocol
596
675
  0x2B, would ignore the operation, so it is refused before anything is sent.
597
676
 
677
+ ### Taking the controller role
678
+
679
+ A client can stand in for a unit as mesh controller. It announces itself as a
680
+ mesh device rather than a management application, so a mesh takes it as a
681
+ member, and under a serial and version of its own so the device list tells it
682
+ apart from the unit it replaces. A unit counts a mesh device without bays as a
683
+ member only under a serial that is `P9SN00000000` or does not start `P9SN0000`:
684
+
685
+ ```python
686
+ from mx_remote import MeshOperationMessage
687
+
688
+ mx = mx_remote.Remote(mesh_device=True, serial='P9SN66660600', version='1.2.3')
689
+ mx.set_mesh_controller(mx.uid) # follow itself as controller; None for none
690
+ mx.set_mesh_bays_mapped(True) # the role is announced only with bays mapped
691
+ await mx.send_mesh_operation(MeshOperationMessage.report_controller(mx.uid, priority=1, installer=0x1234))
692
+ await mx.send_mesh_operation(MeshOperationMessage.register(unit.remote_id, mx.uid))
693
+ await mx.send_oneip_source_list(sources) # the mesh's whole source list
694
+ await mx.set_mesh_setup_completed(True)
695
+ await mx.set_mesh_installer(0x1234)
696
+
697
+ await output.set_oneip_mirroring(other_output) # None to stop
698
+ await output.set_oneip_source_filter([source.remote_id])
699
+ await output.set_oneip_tiling(pos_x=0, pos_y=0, width=1920, height=1080)
700
+ await unit.set_rc_settings(rc_target=RCType.CEC, ip=None, cec_enabled=True, cec_auto_on=True,
701
+ forward_rc=True, forward_ir=False)
702
+ await unit.set_rc_settings(forward_ir=True) # the rest as the unit reported
703
+ ```
704
+
705
+ The tiling window is in pixels of the decoded source picture: the output crops
706
+ it out and scales it up. A width or height of 0 shows the whole picture. An
707
+ output ignores, without a word, a window whose `pos_x` is not a multiple of
708
+ `ONEIP_TILING_X_STEP` (64), whose width is not a multiple of
709
+ `ONEIP_TILING_WIDTH_STEP` (4), or whose sides are under `ONEIP_TILING_MIN` (64),
710
+ so the library refuses those before sending. The output keeps a window in
711
+ memory only and does not report it: a reboot, a stream restart or a change of
712
+ source format drops it.
713
+
714
+ Every unit reports its remote control settings itself, periodically and right
715
+ after a change, and `unit.rc_settings` holds the last report (an `RCSettings`).
716
+ A report is what runs, not what is stored: `rc_type` is the driver in use, and
717
+ the CEC switches read False while the unit's CEC module is not loaded. A
718
+ setting `set_rc_settings()` is not given, or `ip` left at `KEEP_REPORTED`, is
719
+ sent as last reported, so it can store what runs; it is refused until the unit
720
+ has reported.
721
+
722
+ `set_mesh_controller()` changes only the announcement, at once: membership
723
+ while there is a controller, and the controller role while it is this client
724
+ and `set_mesh_bays_mapped()` says it has bays, as a unit announces it.
725
+ `mx.features` is what the hello announces as of now. `send_oneip_detect_bays()`
726
+ asks every unit to detect its bays, which current firmware ignores.
727
+ Membership reports and the units that follow this client are the caller's to
728
+ keep in step. A mesh device's writes reach a unit only while that unit follows
729
+ it as its controller.
730
+
731
+ A unit named in a source list, hearing it from the controller it follows,
732
+ registers every unit listed and unregisters every source unit that is not, so
733
+ the list must always be complete. A list longer than 36 entries goes in
734
+ numbered pages.
735
+
736
+ What is set on an output is held once the frame is away: `mirroring` and
737
+ `filtered` on its output bay read it until the output reports again. Each write
738
+ is refused before anything is sent where a unit would drop it: a device that is
739
+ not a OneIP output (or unit, for remote control settings), an output whose bay
740
+ has not been reported, an output mirroring itself, a filter longer than
741
+ `ONEIP_SOURCE_FILTER_MAX` (89), or a field that does not fit its width.
742
+
743
+ A controller routes an output unit by the streams a source unit advertises for
744
+ its own input, so the output plays them whether or not it lists the source:
745
+
746
+ ```python
747
+ await output.route_oneip_unit(source) # audio follows the video
748
+ await output.route_oneip_unit(source, audio=other) # audio from another unit's input
749
+ await output.route_oneip_unit(source, power_on=False) # leave the display as it is
750
+ await output.route_oneip_unit_with_arc(source, tv_unit) # audio from tv_unit's audio return
751
+ await output.unroute_oneip_unit() # names no streams
752
+ ```
753
+
754
+ Each powers the output's display on with the switch unless `power_on=False`. An
755
+ output below protocol 0x27 is told the source's own audio rather than none, as
756
+ `select_video_source()` does. `route_oneip_unit_with_arc()` names the audio
757
+ return group the other unit last reported in `oneip_details.arc`. The output
758
+ resolves that group to the other unit's audio return, but audio return is
759
+ compiled out of OneIP firmware, so such a route plays no audio. It is what a
760
+ controller sends all the same. A route is refused
761
+ before anything is sent where the device is not a OneIP output, a source has no
762
+ input of its own or has not reported its stream addresses, or the other unit has
763
+ not reported a usable audio return group.
764
+
765
+ A unit's controller report is kept as `device.mesh_controller_report`, and its
766
+ membership report as `mesh_master`. Every other mesh operation a device sends
767
+ arrives at `MxrCallbacks.on_mesh_operation()`, one this library has no name for
768
+ with its raw value.
769
+
598
770
  ### Time zone and time
599
771
 
600
772
  The mesh controller announces its time zone and clock with every periodic