mx-remote 12.0.0__tar.gz → 13.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 (299) hide show
  1. {mx_remote-12.0.0 → mx_remote-13.0.0}/PKG-INFO +1 -1
  2. {mx_remote-12.0.0 → mx_remote-13.0.0}/docs/api.md +62 -0
  3. {mx_remote-12.0.0 → mx_remote-13.0.0}/docs/configuration.md +23 -0
  4. {mx_remote-12.0.0 → mx_remote-13.0.0}/docs/devices-and-bays.md +5 -0
  5. {mx_remote-12.0.0 → mx_remote-13.0.0}/docs/oneip.md +193 -3
  6. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/Interface.py +259 -0
  7. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/Interface.pyi +94 -0
  8. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/compat.py +9 -1
  9. mx_remote-13.0.0/mx_remote/compat.pyi +3 -0
  10. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/const.py +1 -1
  11. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/Constants.py +66 -2
  12. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/Constants.pyi +22 -0
  13. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/Factory.py +9 -0
  14. mx_remote-13.0.0/mx_remote/proto/FrameControllerState.py +151 -0
  15. mx_remote-13.0.0/mx_remote/proto/FrameControllerState.pyi +43 -0
  16. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameHello.py +11 -5
  17. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameHello.pyi +1 -1
  18. mx_remote-13.0.0/mx_remote/proto/FrameMeshConfig.py +239 -0
  19. mx_remote-13.0.0/mx_remote/proto/FrameMeshConfig.pyi +59 -0
  20. mx_remote-13.0.0/mx_remote/proto/FrameMeshReplace.py +178 -0
  21. mx_remote-13.0.0/mx_remote/proto/FrameMeshReplace.pyi +44 -0
  22. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameUnitStatus.py +25 -2
  23. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameUnitStatus.pyi +5 -1
  24. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPAudio.py +1 -1
  25. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPOsdSync.py +33 -4
  26. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPOsdSync.pyi +4 -0
  27. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/remote/Bay.py +8 -2
  28. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/remote/ConnectionAsync.py +142 -2
  29. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/remote/ConnectionAsync.pyi +14 -0
  30. mx_remote-13.0.0/mx_remote/remote/Controller.py +150 -0
  31. mx_remote-13.0.0/mx_remote/remote/Controller.pyi +37 -0
  32. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/remote/Device.py +39 -1
  33. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/remote/Device.pyi +11 -1
  34. mx_remote-13.0.0/mx_remote/remote/Mesh.py +156 -0
  35. mx_remote-13.0.0/mx_remote/remote/Mesh.pyi +26 -0
  36. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/remote/OsdSync.py +26 -5
  37. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/remote/OsdSync.pyi +4 -1
  38. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/remote/Remote.py +684 -22
  39. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/remote/Remote.pyi +49 -5
  40. mx_remote-13.0.0/tests/controllerstate.py +560 -0
  41. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/endpoints.py +213 -2
  42. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/handlers.py +3 -0
  43. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/meshcontrol.py +37 -1
  44. mx_remote-13.0.0/tests/meshmodule.py +515 -0
  45. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/osdsync.py +181 -3
  46. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/proto_stamp.py +15 -2
  47. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/registry.py +38 -7
  48. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/run.py +2 -0
  49. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/unit.py +44 -0
  50. mx_remote-12.0.0/mx_remote/compat.pyi +0 -0
  51. {mx_remote-12.0.0 → mx_remote-13.0.0}/.gitignore +0 -0
  52. {mx_remote-12.0.0 → mx_remote-13.0.0}/AGENTS.md +0 -0
  53. {mx_remote-12.0.0 → mx_remote-13.0.0}/CLAUDE.md +0 -0
  54. {mx_remote-12.0.0 → mx_remote-13.0.0}/LICENSE +0 -0
  55. {mx_remote-12.0.0 → mx_remote-13.0.0}/README.md +0 -0
  56. {mx_remote-12.0.0 → mx_remote-13.0.0}/docs/README.md +0 -0
  57. {mx_remote-12.0.0 → mx_remote-13.0.0}/docs/_static/brand.css +0 -0
  58. {mx_remote-12.0.0 → mx_remote-13.0.0}/docs/_static/favicon.svg +0 -0
  59. {mx_remote-12.0.0 → mx_remote-13.0.0}/docs/_static/logo-dark.svg +0 -0
  60. {mx_remote-12.0.0 → mx_remote-13.0.0}/docs/_static/logo.svg +0 -0
  61. {mx_remote-12.0.0 → mx_remote-13.0.0}/docs/audio.md +0 -0
  62. {mx_remote-12.0.0 → mx_remote-13.0.0}/docs/callbacks.md +0 -0
  63. {mx_remote-12.0.0 → mx_remote-13.0.0}/docs/conf.py +0 -0
  64. {mx_remote-12.0.0 → mx_remote-13.0.0}/docs/diagnostics.md +0 -0
  65. {mx_remote-12.0.0 → mx_remote-13.0.0}/docs/index.md +0 -0
  66. {mx_remote-12.0.0 → mx_remote-13.0.0}/docs/multiviewer.md +0 -0
  67. {mx_remote-12.0.0 → mx_remote-13.0.0}/docs/requirements.txt +0 -0
  68. {mx_remote-12.0.0 → mx_remote-13.0.0}/docs/routing.md +0 -0
  69. {mx_remote-12.0.0 → mx_remote-13.0.0}/hatch_build.py +0 -0
  70. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/Uid.py +0 -0
  71. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/Uid.pyi +0 -0
  72. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/__init__.py +0 -0
  73. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/__init__.pyi +0 -0
  74. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/const.pyi +0 -0
  75. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/deprecated.py +0 -0
  76. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/deprecated.pyi +0 -0
  77. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/main.py +0 -0
  78. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/main.pyi +0 -0
  79. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/BayConfig.py +0 -0
  80. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/BayConfig.pyi +0 -0
  81. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/Data.py +0 -0
  82. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/Data.pyi +0 -0
  83. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/Factory.pyi +0 -0
  84. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameAmpDolbySettings.py +0 -0
  85. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameAmpDolbySettings.pyi +0 -0
  86. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameAmpZoneSettings.py +0 -0
  87. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameAmpZoneSettings.pyi +0 -0
  88. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameAudioClip.py +0 -0
  89. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameAudioClip.pyi +0 -0
  90. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameAudioSetRoute.py +0 -0
  91. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameAudioSetRoute.pyi +0 -0
  92. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameBase.py +0 -0
  93. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameBase.pyi +0 -0
  94. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameBayConfig.py +0 -0
  95. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameBayConfig.pyi +0 -0
  96. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameBayConfigSecondary.py +0 -0
  97. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameBayConfigSecondary.pyi +0 -0
  98. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameBayContent.py +0 -0
  99. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameBayContent.pyi +0 -0
  100. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameBayHide.py +0 -0
  101. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameBayHide.pyi +0 -0
  102. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameBayStatus.py +0 -0
  103. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameBayStatus.pyi +0 -0
  104. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameConnectStatus.py +0 -0
  105. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameConnectStatus.pyi +0 -0
  106. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameDebug.py +0 -0
  107. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameDebug.pyi +0 -0
  108. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameDiscover.py +0 -0
  109. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameDiscover.pyi +0 -0
  110. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameEDID.py +0 -0
  111. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameEDID.pyi +0 -0
  112. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameEDIDProfile.py +0 -0
  113. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameEDIDProfile.pyi +0 -0
  114. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameFactoryReset.py +0 -0
  115. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameFactoryReset.pyi +0 -0
  116. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameFilterStatus.py +0 -0
  117. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameFilterStatus.pyi +0 -0
  118. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameFirmwareVersion.py +0 -0
  119. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameFirmwareVersion.pyi +0 -0
  120. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameHeader.py +0 -0
  121. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameHeader.pyi +0 -0
  122. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameLinks.py +0 -0
  123. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameLinks.pyi +0 -0
  124. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameMeshOperation.py +0 -0
  125. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameMeshOperation.pyi +0 -0
  126. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameMirrorStatus.py +0 -0
  127. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameMirrorStatus.pyi +0 -0
  128. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameMonitoringPulse.py +0 -0
  129. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameMonitoringPulse.pyi +0 -0
  130. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameNetworkStatus.py +0 -0
  131. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameNetworkStatus.pyi +0 -0
  132. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FramePing.py +0 -0
  133. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FramePing.pyi +0 -0
  134. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FramePowerChange.py +0 -0
  135. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FramePowerChange.pyi +0 -0
  136. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameRCAction.py +0 -0
  137. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameRCAction.pyi +0 -0
  138. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameRCIr.py +0 -0
  139. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameRCIr.pyi +0 -0
  140. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameRCKey.py +0 -0
  141. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameRCKey.pyi +0 -0
  142. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameRCSettings.py +0 -0
  143. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameRCSettings.pyi +0 -0
  144. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameReboot.py +0 -0
  145. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameReboot.pyi +0 -0
  146. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameRoutingChange.py +0 -0
  147. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameRoutingChange.pyi +0 -0
  148. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameSetInstaller.py +0 -0
  149. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameSetInstaller.pyi +0 -0
  150. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameSetName.py +0 -0
  151. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameSetName.pyi +0 -0
  152. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameSetRoute.py +0 -0
  153. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameSetRoute.pyi +0 -0
  154. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameSetupStatus.py +0 -0
  155. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameSetupStatus.pyi +0 -0
  156. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameSignalStatus.py +0 -0
  157. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameSignalStatus.pyi +0 -0
  158. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameSignalStatusNew.py +0 -0
  159. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameSignalStatusNew.pyi +0 -0
  160. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameSysTemperature.py +0 -0
  161. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameSysTemperature.pyi +0 -0
  162. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameSystemStatus.py +0 -0
  163. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameSystemStatus.pyi +0 -0
  164. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameTXRCAction.py +0 -0
  165. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameTXRCAction.pyi +0 -0
  166. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameTXRCKey.py +0 -0
  167. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameTXRCKey.pyi +0 -0
  168. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameThirdParty.py +0 -0
  169. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameThirdParty.pyi +0 -0
  170. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameTime.py +0 -0
  171. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameTime.pyi +0 -0
  172. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameTimeZone.py +0 -0
  173. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameTimeZone.pyi +0 -0
  174. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameTopology.py +0 -0
  175. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameTopology.pyi +0 -0
  176. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameTxIR.py +0 -0
  177. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameTxIR.pyi +0 -0
  178. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameUnitCommand.py +0 -0
  179. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameUnitCommand.pyi +0 -0
  180. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameUpgradeFPGA.py +0 -0
  181. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameUpgradeFPGA.pyi +0 -0
  182. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPAudio.pyi +0 -0
  183. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPBayMapping.py +0 -0
  184. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPBayMapping.pyi +0 -0
  185. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPDetectBays.py +0 -0
  186. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPDetectBays.pyi +0 -0
  187. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.py +0 -0
  188. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.pyi +0 -0
  189. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPLink.py +0 -0
  190. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPLink.pyi +0 -0
  191. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.py +0 -0
  192. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.pyi +0 -0
  193. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPMultiviewer.py +0 -0
  194. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPMultiviewer.pyi +0 -0
  195. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPPowerSave.py +0 -0
  196. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPPowerSave.pyi +0 -0
  197. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPPrivacy.py +0 -0
  198. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPPrivacy.pyi +0 -0
  199. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPSetMaster.py +0 -0
  200. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPSetMaster.pyi +0 -0
  201. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPSettingsAll.py +0 -0
  202. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPSettingsAll.pyi +0 -0
  203. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPSinkSdp.py +0 -0
  204. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPSinkSdp.pyi +0 -0
  205. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPSourceSwitch.py +0 -0
  206. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPSourceSwitch.pyi +0 -0
  207. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPSources.py +0 -0
  208. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPSources.pyi +0 -0
  209. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPStats.py +0 -0
  210. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPStats.pyi +0 -0
  211. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPTestcard.py +0 -0
  212. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPTestcard.pyi +0 -0
  213. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPTiling.py +0 -0
  214. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPTiling.pyi +0 -0
  215. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPVideoWall.py +0 -0
  216. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameV2IPVideoWall.pyi +0 -0
  217. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameVolume.py +0 -0
  218. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameVolume.pyi +0 -0
  219. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameVolumeDown.py +0 -0
  220. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameVolumeDown.pyi +0 -0
  221. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameVolumeSet.py +0 -0
  222. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameVolumeSet.pyi +0 -0
  223. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameVolumeUp.py +0 -0
  224. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/FrameVolumeUp.pyi +0 -0
  225. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/LinkConfig.py +0 -0
  226. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/LinkConfig.pyi +0 -0
  227. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/Multiviewer.py +0 -0
  228. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/Multiviewer.pyi +0 -0
  229. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/Svd.py +0 -0
  230. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/Svd.pyi +0 -0
  231. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/V2IPConfig.py +0 -0
  232. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/V2IPConfig.pyi +0 -0
  233. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/V2IPStats.py +0 -0
  234. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/V2IPStats.pyi +0 -0
  235. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/__init__.py +0 -0
  236. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/__init__.pyi +0 -0
  237. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/proto/svd.csv +0 -0
  238. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/py.typed +0 -0
  239. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/remote/AudioRoute.py +0 -0
  240. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/remote/AudioRoute.pyi +0 -0
  241. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/remote/Bay.pyi +0 -0
  242. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/remote/Link.py +0 -0
  243. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/remote/Link.pyi +0 -0
  244. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/remote/State.py +0 -0
  245. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/remote/State.pyi +0 -0
  246. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/remote/Tree.py +0 -0
  247. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/remote/Tree.pyi +0 -0
  248. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/remote/V2IP.py +0 -0
  249. {mx_remote-12.0.0 → mx_remote-13.0.0}/mx_remote/remote/V2IP.pyi +0 -0
  250. {mx_remote-12.0.0 → mx_remote-13.0.0}/pyproject.toml +0 -0
  251. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/audio_ir.py +0 -0
  252. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/audiolock.py +0 -0
  253. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/audioroute.py +0 -0
  254. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/baycontent.py +0 -0
  255. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/capture.py +0 -0
  256. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/complete.py +0 -0
  257. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/connect.py +0 -0
  258. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/controllerset.py +0 -0
  259. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/deprecated.py +0 -0
  260. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/downmix.py +0 -0
  261. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/e2e.py +0 -0
  262. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/entry.py +0 -0
  263. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/handlers_cover.py +0 -0
  264. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/hello.py +0 -0
  265. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/idempotent.py +0 -0
  266. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/lengths.py +0 -0
  267. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/merge.py +0 -0
  268. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/mesh.py +0 -0
  269. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/meshbays.py +0 -0
  270. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/newops.py +0 -0
  271. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/offline.py +0 -0
  272. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/offsets.py +0 -0
  273. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/oneiproute.py +0 -0
  274. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/osdmessage.py +0 -0
  275. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/ping.py +0 -0
  276. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/preview.py +0 -0
  277. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/privacy.py +0 -0
  278. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/protogate.py +0 -0
  279. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/rcsettings.py +0 -0
  280. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/roundtrip.py +0 -0
  281. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/routes.py +0 -0
  282. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/smoke.py +0 -0
  283. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/sources.py +0 -0
  284. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/staterequest.py +0 -0
  285. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/stats.py +0 -0
  286. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/structs.py +0 -0
  287. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/thirdparty.py +0 -0
  288. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/txresult.py +0 -0
  289. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/uid.py +0 -0
  290. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/unknowns.py +0 -0
  291. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/v2ipptp.py +0 -0
  292. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/v2ipscaling.py +0 -0
  293. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/v2ipsdp.py +0 -0
  294. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/v2ipsettings.py +0 -0
  295. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/v2ipsubject.py +0 -0
  296. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/v2iptestcard.py +0 -0
  297. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/v2ipvlan.py +0 -0
  298. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/vdetails.py +0 -0
  299. {mx_remote-12.0.0 → mx_remote-13.0.0}/tests/wirefix.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: mx_remote
3
- Version: 12.0.0
3
+ Version: 13.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/
@@ -74,6 +74,8 @@ place, backgrounds and logo images.
74
74
 
75
75
  .. autodata:: mx_remote.proto.Constants.OSD_SYNC_RECORDS_MAX
76
76
 
77
+ .. autodata:: mx_remote.proto.Constants.OSD_SETTINGS_ASKED
78
+
77
79
  .. autodata:: mx_remote.proto.Constants.OSD_SYNC_FONT_MAX
78
80
 
79
81
  .. autodata:: mx_remote.proto.Constants.OSD_SYNC_TEXT_MAX
@@ -409,6 +411,66 @@ The operations a mesh controller sends, and what a controller announces.
409
411
  .. autodata:: mx_remote.proto.FrameV2IPSources.ONEIP_SOURCE_LIST_MAX
410
412
  ```
411
413
 
414
+ ## Mesh replacement and kept settings
415
+
416
+ Replacing an offline unit of a mesh, the settings each unit keeps for its mesh,
417
+ and the numbering of a controller's bays.
418
+
419
+ ```{eval-rst}
420
+ .. autoclass:: mx_remote.MeshReplaceUnit
421
+ :members:
422
+
423
+ .. autoclass:: mx_remote.MeshReplaceUnits
424
+ :members:
425
+
426
+ .. autoclass:: mx_remote.MeshReplaceable
427
+ :members:
428
+
429
+ .. autoclass:: mx_remote.proto.Constants.MeshReplaceResult
430
+ :members:
431
+
432
+ .. autoclass:: mx_remote.MeshSetting
433
+ :members:
434
+
435
+ .. autoclass:: mx_remote.MeshConfigDigest
436
+ :members:
437
+
438
+ .. autoclass:: mx_remote.MeshConfig
439
+ :members:
440
+
441
+ .. autodata:: mx_remote.proto.Constants.MESH_REPLACE_UNITS_MAX
442
+
443
+ .. autodata:: mx_remote.proto.Constants.MESH_REPLACE_NAME_MAX
444
+
445
+ .. autodata:: mx_remote.proto.Constants.MESH_CONFIG_MAX
446
+
447
+ .. autoclass:: mx_remote.MeshBay
448
+ :members:
449
+
450
+ .. autoclass:: mx_remote.MeshBays
451
+ :members:
452
+
453
+ .. autodata:: mx_remote.proto.Constants.MESH_BAYS_MAX
454
+ ```
455
+
456
+ ## Dedicated controllers
457
+
458
+ What a dedicated mesh controller, such as a router, announces of itself and its
459
+ state.
460
+
461
+ ```{eval-rst}
462
+ .. autoclass:: mx_remote.ControllerHello
463
+ :members:
464
+
465
+ .. autoclass:: mx_remote.ControllerPart
466
+ :members:
467
+
468
+ .. autoclass:: mx_remote.proto.Constants.ControllerRole
469
+ :members:
470
+
471
+ .. autodata:: mx_remote.proto.Constants.CONTROLLER_PARTS_MAX
472
+ ```
473
+
412
474
  ## Names from before OneIP
413
475
 
414
476
  Releases up to 5.11 used the firmware's internal names in the public API: V2IP
@@ -28,6 +28,29 @@ mx = mx_remote.Remote(uid_path="/var/lib/myapp/.mxr-uid")
28
28
  mx = mx_remote.Remote(mesh_device=True, serial="P9SN66660600", version="1.2.3")
29
29
  ```
30
30
 
31
+ A client confined to one interface is invisible on every other, so a dedicated
32
+ controller speaking the protocol on its mesh link alone is not listed by the
33
+ units on its uplink LAN. `announce_interfaces` names interfaces that get each
34
+ hello, and a dedicated controller's `CONTROLLER_STATE` hello, from a send-only
35
+ socket bound to the interface's current address:
36
+
37
+ ```python
38
+ mx = mx_remote.Remote(local_ip="10.8.80.30", dedicated_controller=True, announce_interfaces=["eth1"])
39
+ mx.set_announce_interfaces(["eth1", "wlan0"]) # while running, announced at once
40
+ ```
41
+
42
+ The socket is rebound when that address changes and skipped while there is
43
+ none, as on a DHCP uplink; nothing arriving there is taken. The hello there
44
+ leaves out `MANAGER` (a unit takes a promotion to controller from a manager),
45
+ `MESH_MASTER` (a unit joining a mesh joins it) and `OSD_SYNC` (a source syncs
46
+ every device with it, in any mesh). `MESH_MEMBER` stays, so no unit takes the
47
+ client for one free to join.
48
+
49
+ On Linux, the interface `local_ip` selects is the only one datagrams are taken
50
+ from, so a device reachable only by another interface stays unknown, and
51
+ starting fails when no interface holds the address. Elsewhere, broadcasts on
52
+ every interface still arrive.
53
+
31
54
  Each frame leaves by the endpoints a unit picks for it:
32
55
 
33
56
  - a frame for every unit at once goes to both: a discover, reboot or factory
@@ -43,6 +43,11 @@ await bay.set_hidden(True) # hide
43
43
  await bay.set_hidden(False) # show
44
44
  ```
45
45
 
46
+ A device that lost its settings reports its bays' default names and hidden
47
+ flags, with `BayStatusMask.SETTINGS_LOST` set. A bay keeps the name and hidden
48
+ flag it had until the bit clears; a bay first heard while it is set takes what
49
+ it is sent.
50
+
46
51
  ## EDID Profiles
47
52
 
48
53
  Change the EDID profile on input bays:
@@ -533,9 +533,49 @@ Each unit asks for the settings it lacks and takes only those, so this client
533
533
  holds the content and answers until it is replaced or cleared, announcing
534
534
  itself meanwhile as the mesh's overlay sync manager: a unit waking from power
535
535
  save then takes this content rather than the controller's. Logo images are
536
- served over HTTP from this client, on `logo_port` or a free port. Content a unit
536
+ served over HTTP from this client, on `logo_port` or a free port, at `local_ip`
537
+ when one is given and on every address otherwise; a changed `local_ip` moves
538
+ the server there on the same port. Content a unit
537
539
  would refuse is refused before anything is sent; see `send_osd_sync`.
538
540
 
541
+ A mesh controller holding the sync its units already took from another source
542
+ keeps it without sending it again, and sends it to a unit that joins:
543
+
544
+ ```python
545
+ await mx.keep_osd_sync(content) # held and served, sent to no unit
546
+ await mx.resend_osd_sync(joined)
547
+ ```
548
+
549
+ A client made with `Remote(osd_sync=True)` takes a sync the way a unit does,
550
+ which a dedicated controller needs to hold the mesh's overlays. Its hello
551
+ announces `DeviceFeature.OSD_SYNC`, so a source sends it a sync of its own:
552
+
553
+ ```python
554
+ await mx.request_osd_sync(OsdSyncWhat.CAPTIONS | OsdSyncWhat.LOGO_SLOTS)
555
+ # MxrCallbacks.on_osd_sync(dev, sync): the sync naming this client
556
+ await mx.request_osd_settings(dev, sync.seq, sync.what, missing=list(range(sync.records)))
557
+ # MxrCallbacks.on_osd_sync_setting(dev, setting), once per setting
558
+ url = sync.logo_url(sender_address, 1) # where slot 1's PNG is fetched
559
+ ```
560
+
561
+ A sync naming this client is read from any device for 10 s after it asked for
562
+ one, and a setting naming it from the source of one of the last
563
+ `OSD_SETTINGS_ASKED` (8) syncs it asked the settings of. A source sends a few
564
+ settings at a time; a unit asks again for those still missing a second after
565
+ the last one came. `setting.entry` converts a setting into the `OsdSyncEntry`
566
+ that `send_osd_sync()` and `keep_osd_sync()` take, so the client can serve the
567
+ sync it took. Fetching the logos is the caller's.
568
+
569
+ Like a unit, it reports to the source at each stage, so the source lists it
570
+ with the units that took the sync:
571
+
572
+ ```python
573
+ await mx.send_osd_report(dev, OsdSyncReport(seq=sync.seq, state=OsdSyncState.DONE, written=3, refused=0,
574
+ reason=''))
575
+ ```
576
+
577
+ The reason is cut to 63 bytes at a character boundary.
578
+
539
579
  A caption setting with `OsdCaptionField.DEFAULT_TEXT` in place of
540
580
  `OsdCaptionField.TEXT`, and no text, sends a unit back to its layout's default
541
581
  caption, drawn in its own locale. A unit whose firmware predates it keeps the
@@ -654,6 +694,20 @@ waits. Each command returns False without sending for a device that is not a
654
694
  OneIP unit, one below 0x2C, or an address and netmask a unit cannot hold.
655
695
  Nothing acknowledges a command: `unit_status` reads what the unit reports next.
656
696
 
697
+ This client reports its own status the way a unit does, to every device:
698
+
699
+ ```python
700
+ from mx_remote import UnitStatus
701
+
702
+ await mx.send_unit_status(UnitStatus(flags=UnitFlag.FIRMWARE_UPGRADE, uptime=3600))
703
+ ```
704
+
705
+ A mesh controller that runs no update checks of its own passes its members'
706
+ upgrade flags on this way: a member that sees `UnitFlag.FIRMWARE_UPGRADE` or
707
+ `UnitFlag.VIDEO_PROCESSOR_UPGRADE` appear in its controller's report checks
708
+ for the upgrade itself. More than six temperatures, or a status message over
709
+ 48 bytes or holding a NUL, is refused before anything is sent.
710
+
657
711
  ## Mesh and firmware
658
712
 
659
713
  ```python
@@ -691,7 +745,9 @@ mx.set_mesh_bays_mapped(True) # the role is announced only with bays mappe
691
745
  await mx.send_mesh_operation(MeshOperationMessage.report_controller(mx.uid, priority=1, installer=0x1234))
692
746
  await mx.send_mesh_operation(MeshOperationMessage.register(unit.remote_id, mx.uid))
693
747
  await mx.send_oneip_source_list(sources) # the mesh's whole source list
694
- await mx.set_mesh_setup_completed(True)
748
+ await mx.set_mesh_setup_completed(True) # every unit that hears it
749
+ mx.set_setup_completed(True) # this client's own, in its hello
750
+ await mx.set_member_setup_completed(unit, True) # one member, 0x33 or later
695
751
  await mx.set_mesh_installer(0x1234)
696
752
 
697
753
  await output.set_oneip_mirroring(other_output) # None to stop
@@ -702,6 +758,13 @@ await unit.set_rc_settings(rc_target=RCType.CEC, ip=None, cec_enabled=True, cec_
702
758
  await unit.set_rc_settings(forward_ir=True) # the rest as the unit reported
703
759
  ```
704
760
 
761
+ `set_mesh_setup_completed()` names no unit, so every unit that hears it takes
762
+ it. `set_member_setup_completed()` reaches one member alone: a controller marks
763
+ the members of the moment its setup completes, and sends it again to one that
764
+ missed it until that member's hello carries `DeviceFeature.SETUP_COMPLETED`; a
765
+ replacement takes the mark of the unit it replaced. A member takes it only from
766
+ its own controller, and one whose mesh module predates it drops it.
767
+
705
768
  The tiling window is in pixels of the decoded source picture: the output crops
706
769
  it out and scales it up. A width or height of 0 shows the whole picture. An
707
770
  output ignores, without a word, a window whose `pos_x` is not a multiple of
@@ -763,10 +826,137 @@ input of its own or has not reported its stream addresses, or the other unit has
763
826
  not reported a usable audio return group.
764
827
 
765
828
  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
829
+ membership report as `mesh_master` and `mesh_membership_lost`. Every other mesh operation a device sends
767
830
  arrives at `MxrCallbacks.on_mesh_operation()`, one this library has no name for
768
831
  with its raw value.
769
832
 
833
+ ### Replacing a unit, and kept settings
834
+
835
+ Units with the mesh module (protocol 0x32 for replacing, 0x33 for kept
836
+ settings) let a unit outside any mesh take the place of an offline one. A
837
+ device without the module answers nothing, and the library sends neither
838
+ opcode to a unit below its version.
839
+
840
+ ```python
841
+ await mx.query_mesh_replaceable() # every device; at most every 2 s
842
+ for r in mx.mesh_replaceable: # listed in the last 30 s, each unit once
843
+ print(r.lister, r.offline_controller, r.unit.uid, r.unit.input_name)
844
+ await mx.request_mesh_replace(lister, r.unit.uid) # answered at on_mesh_replace_answered()
845
+ ```
846
+
847
+ Each controller lists its own offline units, once it has been up 120 s, and a
848
+ member whose controller is offline lists that controller. The device asked
849
+ takes the replacing unit in: the unit's bays, their names and routes, and its
850
+ kept settings. A member asked to replace its offline controller sends this
851
+ client the controller's kept settings instead, and taking over is then this
852
+ client's own: promoting itself to controller and having the members replace
853
+ the old one with `send_mesh_operation()`. A request is refused before sending
854
+ where the device would refuse it: the unit was not listed by that device in the
855
+ last 30 s, this client follows a mesh controller or is on a default serial
856
+ (one starting `P9SN0000` other than `P9SN00000000`), or the unit has a
857
+ `ONEIP_SOURCE`, `ONEIP_SINK` or `MULTIVIEWER` feature this client lacks. As a
858
+ controller this client answers with `send_mesh_replaceable()` and
859
+ `send_mesh_replace_result()`.
860
+
861
+ Every unit in a mesh keeps settings of its own, such as `led.status`, and
862
+ announces their length and crc32 as `device.mesh_config_digest`. The controller
863
+ keeps a copy of every member's, and every member one of its controller's, so a
864
+ replacing unit inherits them:
865
+
866
+ ```python
867
+ await mx.request_mesh_config(holder, subject.remote_id) # holder's copy, or its own
868
+ config = mx.mesh_config(subject.remote_id) # once every frame has arrived
869
+ await mx.send_mesh_config_hello(settings) # this client's own, in a mesh
870
+ await mx.send_mesh_config(to, subject_uid, settings) # the answer to a request
871
+ await mx.send_mesh_config_set(member, settings) # a member takes these, resets the rest
872
+ ```
873
+
874
+ Settings are taken only once every frame has arrived, in order, from one
875
+ sender, and their crc32 matches. A set reaches `on_mesh_config_set_requested()`
876
+ only from the controller this client follows; `send_mesh_config_set()` is
877
+ refused unless the member reports this client as its controller, and a member
878
+ whose settings changed restarts 3 s later. Names and values are at most 255
879
+ bytes with no NUL, and all of them at most `MESH_CONFIG_MAX` (1984) bytes.
880
+
881
+ A unit that lost its settings says so, and announces its defaults: `lost` is set
882
+ on its `mesh_config_digest`, and on a `MeshConfig` of its own settings (never on
883
+ a copy). A controller that keeps a record of it sends that record back as a
884
+ set. Its membership report flags the same, as `device.mesh_membership_lost`:
885
+ a controller that has it as a member adds it back rather than drop it for
886
+ naming no master. Units that predate the flags never set them.
887
+
888
+ A unit that takes over from a controller numbers its bays as that controller
889
+ did, so drivers keep addressing the same ports. A controller answers for its
890
+ own numbering, and a member with the copy it keeps of its controller's, also
891
+ once that controller is offline:
892
+
893
+ ```python
894
+ await mx.request_mesh_bays(member, controller.remote_id)
895
+ bays = mx.mesh_bays(controller.remote_id) # MeshBays, None until it arrived whole
896
+ for bay in bays.bays:
897
+ print('input' if bay.input else 'output', bay.bay, bay.unit)
898
+
899
+ # answering a request, MxrCallbacks.on_mesh_bays_requested(dev, subject)
900
+ await mx.send_mesh_bays(dev, mx.uid, [MeshBay(input=True, bay=1, features=DeviceFeature.ONEIP_SOURCE,
901
+ unit=source.remote_id)])
902
+ ```
903
+
904
+ A numbering arrives at `on_mesh_bays_received()`, put together as a dump is;
905
+ bay 0 is the controller's own. It is asked only of a unit at 0x33 or later,
906
+ and holds at most `MESH_BAYS_MAX` (512) bays.
907
+
908
+ ### Dedicated controllers
909
+
910
+ A mesh controller without bays of its own, such as a router, marks itself as
911
+ dedicated with a `CONTROLLER_STATE` (0x58) hello. A OneIP unit at protocol 0x34
912
+ or later then takes the controller role from none while one it follows is
913
+ online.
914
+
915
+ ```python
916
+ print(device.dedicated_controller) # True once it has sent such a hello
917
+ print(device.controller_hello) # ControllerHello: role, generation, primary, parts
918
+ ```
919
+
920
+ `MxrCallbacks.on_controller_hello_changed(dev, hello)` reports each hello that
921
+ differs from that device's last.
922
+
923
+ A client made with `Remote(dedicated_controller=True)` sends one itself, right
924
+ after its hello to every unit, at most every 10 s, in a mesh or not; its own
925
+ hello stays at the 54 bytes every unit takes.
926
+
927
+ ```python
928
+ mx = Remote(dedicated_controller=True)
929
+ mx.set_controller_role(ControllerRole.PRIMARY, primary=mx.uid) # sent at once when it changed
930
+ ```
931
+
932
+ It starts as `ControllerRole.NONE` with no primary, and any other client is
933
+ refused.
934
+
935
+ Dedicated controllers replicate their state between themselves: the library
936
+ does the transfer, and the application only gives and takes part bytes.
937
+
938
+ ```python
939
+ # serving: the hello lists each part's length and crc32 under the generation
940
+ mx.set_controller_parts(generation=3, parts=[(0, state_bytes), (1, logo_png)])
941
+
942
+ # pulling a part another controller's hello lists
943
+ part = router.controller_hello.parts[0]
944
+ await mx.pull_controller_part(router, part.part, router.controller_hello.generation, part.length, part.crc)
945
+ # arrives at MxrCallbacks.on_controller_part_received(dev, part, generation, data),
946
+ # or ends at on_controller_part_failed(dev, part, generation)
947
+
948
+ mx.allow_controller_state(new_router.remote_id) # also a controller of no mesh of this client's
949
+ ```
950
+
951
+ A request is answered with the window it asks, at most 8192 bytes, in frames of
952
+ at most 1024. A pull asks from the first byte it lacks, again after 2 s without
953
+ data and once on a frame out of order, and ends on a generation no longer held,
954
+ a part other than the one its hello described, the device going offline, or
955
+ ten asks without progress. Requests and data are taken only from a dedicated
956
+ controller of this client's mesh, or the one `allow_controller_state()` names,
957
+ and never from a sender that sent no hello. More than five parts, or one listed
958
+ twice, is refused.
959
+
770
960
  ### Time zone and time
771
961
 
772
962
  The mesh controller announces its time zone and clock with every periodic
@@ -2549,6 +2549,16 @@ class OsdSync:
2549
2549
  CRC differs from its own from http://source_ip:http_port/osd-sync/slotN.png,
2550
2550
  N being 1 to 4."""
2551
2551
 
2552
+ def logo_url(self, sender:str, slot:int) -> str|None:
2553
+ """Where a unit fetches the logo of slot, 1 to 4, of this sync sent from
2554
+ sender, the sender's address: source_ip, or sender when it is
2555
+ unspecified, at http_port, or 80 when it is zero. None for a slot
2556
+ outside 1 to 4."""
2557
+ if not (1 <= slot <= OSD_SYNC_SLOTS):
2558
+ return None
2559
+ host = sender if (self.source_ip == '0.0.0.0') else self.source_ip
2560
+ return f"http://{host}:{self.http_port or 80}/osd-sync/slot{slot}.png"
2561
+
2552
2562
  @dataclass(frozen=True)
2553
2563
  class OsdSyncRequest:
2554
2564
  """A unit asking for an overlay sync, or for settings of one it is taking,
@@ -2621,6 +2631,12 @@ class OsdSyncSetting:
2621
2631
  bits for a logo, and bit 0 for a background's colour."""
2622
2632
  value: OsdSyncValue
2623
2633
 
2634
+ @property
2635
+ def entry(self) -> 'OsdSyncEntry':
2636
+ """The setting as this client sends it on, holding the sync it took
2637
+ with Remote.send_osd_sync() or keep_osd_sync()."""
2638
+ return OsdSyncEntry(value=self.value, set=self.set, size=self.size, layers=self.layers)
2639
+
2624
2640
  @dataclass(frozen=True)
2625
2641
  class OsdSyncReport:
2626
2642
  """
@@ -2907,6 +2923,14 @@ class MeshOperationMessage:
2907
2923
  """The installer id a controller report carries."""
2908
2924
  return int.from_bytes(self.param.byte_value[4:6], 'little')
2909
2925
 
2926
+ @property
2927
+ def membership_lost(self) -> bool:
2928
+ """Whether a membership report flags that its sender lost its
2929
+ settings, and its membership possibly with them: bit 0 of the
2930
+ parameter's first 32-bit word. Senders that predate the flag leave it
2931
+ clear."""
2932
+ return bool(int.from_bytes(self.param.byte_value[0:4], 'little') & 1)
2933
+
2910
2934
  def __str__(self) -> str:
2911
2935
  return f"{self.operation} target={self.target} param={self.param}"
2912
2936
 
@@ -2918,6 +2942,145 @@ class MeshControllerReport:
2918
2942
  installer: int
2919
2943
  """The installer id its members take, 0 for none."""
2920
2944
 
2945
+ @dataclass(frozen=True)
2946
+ class MeshReplaceUnit:
2947
+ """A unit a controller can have replaced: it has bays on the controller, it
2948
+ is not the controller, and it is offline."""
2949
+ uid: MxrDeviceUid
2950
+ """The unit."""
2951
+ features: DeviceFeature|int = DeviceFeature(0)
2952
+ """Its features. A unit replacing it must have its ONEIP_SOURCE, ONEIP_SINK
2953
+ and MULTIVIEWER too."""
2954
+ input: int = 0
2955
+ """Its first input bay on the controller, 0 when it has none."""
2956
+ output: int = 0
2957
+ """Its first output bay on the controller, 0 when it has none."""
2958
+ input_name: str = ''
2959
+ """The name of that input, at most MESH_REPLACE_NAME_MAX bytes."""
2960
+ output_name: str = ''
2961
+ """The name of that output, at most MESH_REPLACE_NAME_MAX bytes."""
2962
+
2963
+ @dataclass(frozen=True)
2964
+ class MeshReplaceUnits:
2965
+ """The units one device listed as replaceable."""
2966
+ offline_controller: bool
2967
+ """The units are the sender's own controller, which is offline. A member
2968
+ lists its controller this way; a controller lists its own offline units."""
2969
+ units: tuple[MeshReplaceUnit, ...]
2970
+ """The units, as many of the list as has arrived."""
2971
+ total: int
2972
+ """How many units the whole list holds."""
2973
+
2974
+ @dataclass(frozen=True)
2975
+ class MeshReplaceable:
2976
+ """A unit this client can ask to replace, and who to ask."""
2977
+ lister: MxrDeviceUid
2978
+ """The device that listed it, which a request to replace it goes to."""
2979
+ offline_controller: bool
2980
+ """The unit is the lister's own controller, which is offline."""
2981
+ unit: MeshReplaceUnit
2982
+ """The unit."""
2983
+
2984
+ @dataclass(frozen=True)
2985
+ class MeshSetting:
2986
+ """One setting a unit keeps for its mesh: an application configuration name
2987
+ and its value, each at most 255 bytes. A unit carries only the settings
2988
+ that differ from their default."""
2989
+ name: str
2990
+ """The configuration name, such as led.status."""
2991
+ value: str
2992
+ """The value, as text."""
2993
+
2994
+ @dataclass(frozen=True)
2995
+ class MeshConfigDigest:
2996
+ """The length and crc32 of the settings a unit keeps for its mesh, as the
2997
+ unit announces them: every 60 seconds while it is in a mesh, and shortly
2998
+ after one of them changes."""
2999
+ length: int
3000
+ """The settings' length in bytes, as they are carried."""
3001
+ crc32: int
3002
+ """The crc32 of those bytes."""
3003
+ lost: bool = False
3004
+ """The unit lost its settings and announces defaults: a controller that
3005
+ keeps a record of it restores that record, whatever the length and crc32
3006
+ say. Never set by a unit that predates the flag."""
3007
+
3008
+ @dataclass(frozen=True)
3009
+ class MeshConfig:
3010
+ """A unit's kept settings, as a device sent them."""
3011
+ subject: MxrDeviceUid
3012
+ """The unit the settings belong to."""
3013
+ holder: MxrDeviceUid
3014
+ """The device that sent them."""
3015
+ copy: bool
3016
+ """They are a copy holder keeps of subject, not its own: the controller
3017
+ keeps one of every member, and a member one of its controller."""
3018
+ settings: tuple[MeshSetting, ...]
3019
+ """The settings, in the order they were sent."""
3020
+ lost: bool = False
3021
+ """The unit lost its settings, and these are its defaults: a controller
3022
+ that keeps a record of it drops them. Only ever set on a unit's own
3023
+ settings, never on a copy."""
3024
+
3025
+ @dataclass(frozen=True)
3026
+ class MeshBay:
3027
+ """One bay of a controller's numbering: the unit that owns it."""
3028
+ input: bool
3029
+ """An input, or else an output."""
3030
+ bay: int
3031
+ """The bay number the controller gives it, as its /port/list Bay; 0 is the
3032
+ controller's own."""
3033
+ features: DeviceFeature
3034
+ """The unit's ONEIP_SOURCE, ONEIP_SINK and MULTIVIEWER features as the
3035
+ sender knows them, none when it doesn't."""
3036
+ unit: MxrDeviceUid
3037
+ """The unit that owns the bay."""
3038
+
3039
+ @dataclass(frozen=True)
3040
+ class MeshBays:
3041
+ """A controller's numbering, as a device sent it: the controller its own, a
3042
+ member the copy it keeps of its controller's. A unit that takes over from
3043
+ a controller numbers its bays the same, so drivers keep addressing the
3044
+ same ports."""
3045
+ subject: MxrDeviceUid
3046
+ """The controller the numbering is of."""
3047
+ holder: MxrDeviceUid
3048
+ """The device that sent it."""
3049
+ copy: bool
3050
+ """It is a copy holder keeps of subject's numbering."""
3051
+ bays: tuple[MeshBay, ...]
3052
+ """The bays that name a unit, inputs first, each mode by bay number; a bay
3053
+ naming none is left out."""
3054
+
3055
+ @dataclass(frozen=True)
3056
+ class ControllerPart:
3057
+ """One part of a dedicated controller's state, as its hello lists it."""
3058
+ part: int
3059
+ """Which part: 0 the state, 1 to 4 the PNG of that overlay logo slot."""
3060
+ length: int
3061
+ """The part's length in bytes."""
3062
+ crc: int
3063
+ """The crc32 of the whole part."""
3064
+
3065
+ @dataclass(frozen=True)
3066
+ class ControllerHello:
3067
+ """
3068
+ A dedicated controller's CONTROLLER_STATE hello: its role, its state's
3069
+ generation, the primary in its state and the parts its state is made of.
3070
+
3071
+ Sending one marks the sender as a dedicated controller, such as a router,
3072
+ to every unit from protocol 0x34 on, which then lets no OneIP take the
3073
+ controller role from it while it is online.
3074
+ """
3075
+ role: ControllerRole|int = ControllerRole.NONE
3076
+ """Primary, failsafe or neither; a plain int where it has no name."""
3077
+ generation: int = 0
3078
+ """Raised on every change of the sender's state."""
3079
+ primary: MxrDeviceUid|None = None
3080
+ """The primary in the sender's state, None for none."""
3081
+ parts: tuple[ControllerPart, ...] = ()
3082
+ """The parts, at most CONTROLLER_PARTS_MAX."""
3083
+
2921
3084
  @dataclass(frozen=True)
2922
3085
  class ThirdPartyMessage:
2923
3086
  """
@@ -3809,6 +3972,24 @@ class DeviceBase(ABC):
3809
3972
  '''What this device last announced about itself as a mesh controller,
3810
3973
  None until it has.'''
3811
3974
 
3975
+ @property
3976
+ @abstractmethod
3977
+ def mesh_config_digest(self) -> MeshConfigDigest|None:
3978
+ '''The length and crc32 of this unit's kept settings, as it last
3979
+ announced them, None until it has.'''
3980
+
3981
+ @property
3982
+ @abstractmethod
3983
+ def controller_hello(self) -> ControllerHello|None:
3984
+ '''The CONTROLLER_STATE hello this device last sent, None until it
3985
+ has sent one.'''
3986
+
3987
+ @property
3988
+ def dedicated_controller(self) -> bool:
3989
+ '''This device is a dedicated mesh controller, such as a router: it has
3990
+ sent a CONTROLLER_STATE hello, which controller_hello reads.'''
3991
+ return (self.controller_hello is not None)
3992
+
3812
3993
  @property
3813
3994
  @abstractmethod
3814
3995
  def is_mesh_master(self) -> bool:
@@ -4571,6 +4752,16 @@ class BayLinks:
4571
4752
  class DeviceRegistry(ABC):
4572
4753
  ''' all mx_remote devices on the network '''
4573
4754
 
4755
+ def osd_sync_asked(self, at:float) -> bool:
4756
+ '''Whether a sync naming this client, arriving at at, answers its own
4757
+ request for one.'''
4758
+ return False
4759
+
4760
+ def osd_settings_asked(self, source:MxrDeviceUid, seq:int) -> bool:
4761
+ '''Whether a setting naming this client is of sync seq, which it asked
4762
+ source the settings of.'''
4763
+ return False
4764
+
4574
4765
  @property
4575
4766
  @abstractmethod
4576
4767
  def local_ip(self) -> str|None:
@@ -4914,6 +5105,74 @@ class MxrCallbacks:
4914
5105
  An operation this library has no name for arrives with its raw value. '''
4915
5106
  pass
4916
5107
 
5108
+ def on_mesh_replace_queried(self, dev:DeviceBase) -> None:
5109
+ ''' called when 'dev', a unit outside any mesh, asked which units it can
5110
+ replace. A controller answers with its offline units, and a member
5111
+ whose controller is offline with that controller; see
5112
+ Remote.send_mesh_replaceable(). '''
5113
+ pass
5114
+
5115
+ def on_mesh_replace_units_received(self, dev:DeviceBase, units:MeshReplaceUnits) -> None:
5116
+ ''' called when 'dev' listed units this client can ask it to have
5117
+ replaced: the whole list it has sent so far, which
5118
+ Remote.mesh_replaceable also reads. '''
5119
+ pass
5120
+
5121
+ def on_mesh_replace_requested(self, dev:DeviceBase, unit:MxrDeviceUid) -> None:
5122
+ ''' called when 'dev', a unit outside any mesh, asked to replace 'unit'
5123
+ with it; see Remote.send_mesh_replace_result(). '''
5124
+ pass
5125
+
5126
+ def on_mesh_replace_answered(self, dev:DeviceBase, unit:MxrDeviceUid, result:MeshReplaceResult|int) -> None:
5127
+ ''' called when 'dev' answered this client's request to replace 'unit'.
5128
+ A result this library has no name for arrives as a plain int. '''
5129
+ pass
5130
+
5131
+ def on_mesh_config_requested(self, dev:DeviceBase, subject:MxrDeviceUid) -> None:
5132
+ ''' called when 'dev' asked for the kept settings of 'subject': the
5133
+ addressee's own when it is 'subject', otherwise the copy the addressee
5134
+ keeps; see Remote.send_mesh_config(). '''
5135
+ pass
5136
+
5137
+ def on_mesh_config_received(self, dev:DeviceBase, config:MeshConfig) -> None:
5138
+ ''' called when 'dev' sent a unit's kept settings whole, which
5139
+ Remote.mesh_config() also reads. '''
5140
+ pass
5141
+
5142
+ def on_mesh_config_set_requested(self, dev:DeviceBase, settings:tuple[MeshSetting, ...]) -> None:
5143
+ ''' called when 'dev', the controller this client follows, told it to
5144
+ take these kept settings and reset every other one to its default. '''
5145
+ pass
5146
+
5147
+ def on_mesh_bays_requested(self, dev:DeviceBase, subject:MxrDeviceUid) -> None:
5148
+ ''' called when 'dev' asked for the numbering of controller 'subject':
5149
+ this client's own when it is that controller, otherwise the copy it
5150
+ keeps. Answer with Remote.send_mesh_bays(). '''
5151
+ pass
5152
+
5153
+ def on_mesh_bays_received(self, dev:DeviceBase, bays:MeshBays) -> None:
5154
+ ''' called when 'dev' sent a controller's numbering whole, which
5155
+ Remote.mesh_bays() also reads. '''
5156
+ pass
5157
+
5158
+ def on_controller_part_received(self, dev:DeviceBase, part:int, generation:int, data:bytes) -> None:
5159
+ ''' called when a part this client pulled from 'dev' with
5160
+ Remote.pull_controller_part() arrived whole, its crc32 matching. '''
5161
+ pass
5162
+
5163
+ def on_controller_part_failed(self, dev:DeviceBase, part:int, generation:int) -> None:
5164
+ ''' called when a pull of a part from 'dev' ended without it: the
5165
+ generation is no longer held, the part is not the one its hello
5166
+ described, 'dev' went offline, or ten asks went without progress. '''
5167
+ pass
5168
+
5169
+ def on_controller_hello_changed(self, dev:DeviceBase, hello:ControllerHello) -> None:
5170
+ ''' called when 'dev' sent a CONTROLLER_STATE hello other than its last:
5171
+ it is a dedicated controller, such as a router, which
5172
+ DeviceBase.dedicated_controller says from then on, and
5173
+ DeviceBase.controller_hello reads. '''
5174
+ pass
5175
+
4917
5176
  def on_third_party_message(self, dev:DeviceBase, message:ThirdPartyMessage) -> None:
4918
5177
  ''' called when the application on 'dev' sent a message under its own
4919
5178
  vendor id. A message from a sender this client has not yet heard