mx-remote 8.0.2__tar.gz → 9.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 (269) hide show
  1. {mx_remote-8.0.2 → mx_remote-9.0.0}/PKG-INFO +1 -1
  2. {mx_remote-8.0.2 → mx_remote-9.0.0}/docs/api.md +31 -0
  3. {mx_remote-8.0.2 → mx_remote-9.0.0}/docs/oneip.md +59 -1
  4. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/Interface.py +151 -13
  5. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/Interface.pyi +45 -1
  6. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/const.py +1 -1
  7. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/Constants.py +43 -6
  8. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/Constants.pyi +16 -0
  9. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/Factory.py +3 -0
  10. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.py +25 -8
  11. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPOsdSync.py +45 -12
  12. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPOsdSync.pyi +10 -2
  13. mx_remote-9.0.0/mx_remote/proto/FrameV2IPPrivacy.py +83 -0
  14. mx_remote-9.0.0/mx_remote/proto/FrameV2IPPrivacy.pyi +22 -0
  15. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/remote/Device.py +106 -8
  16. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/remote/Device.pyi +14 -2
  17. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/remote/OsdSync.py +1 -11
  18. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/remote/Remote.py +14 -3
  19. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/remote/Remote.pyi +3 -1
  20. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/handlers.py +1 -0
  21. mx_remote-9.0.0/tests/osdmessage.py +114 -0
  22. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/osdsync.py +12 -13
  23. mx_remote-9.0.0/tests/privacy.py +112 -0
  24. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/proto_stamp.py +1 -0
  25. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/run.py +2 -0
  26. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/smoke.py +1 -0
  27. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/v2ipsettings.py +59 -0
  28. {mx_remote-8.0.2 → mx_remote-9.0.0}/.gitignore +0 -0
  29. {mx_remote-8.0.2 → mx_remote-9.0.0}/AGENTS.md +0 -0
  30. {mx_remote-8.0.2 → mx_remote-9.0.0}/CLAUDE.md +0 -0
  31. {mx_remote-8.0.2 → mx_remote-9.0.0}/LICENSE +0 -0
  32. {mx_remote-8.0.2 → mx_remote-9.0.0}/README.md +0 -0
  33. {mx_remote-8.0.2 → mx_remote-9.0.0}/docs/README.md +0 -0
  34. {mx_remote-8.0.2 → mx_remote-9.0.0}/docs/_static/brand.css +0 -0
  35. {mx_remote-8.0.2 → mx_remote-9.0.0}/docs/_static/favicon.svg +0 -0
  36. {mx_remote-8.0.2 → mx_remote-9.0.0}/docs/_static/logo-dark.svg +0 -0
  37. {mx_remote-8.0.2 → mx_remote-9.0.0}/docs/_static/logo.svg +0 -0
  38. {mx_remote-8.0.2 → mx_remote-9.0.0}/docs/audio.md +0 -0
  39. {mx_remote-8.0.2 → mx_remote-9.0.0}/docs/callbacks.md +0 -0
  40. {mx_remote-8.0.2 → mx_remote-9.0.0}/docs/conf.py +0 -0
  41. {mx_remote-8.0.2 → mx_remote-9.0.0}/docs/configuration.md +0 -0
  42. {mx_remote-8.0.2 → mx_remote-9.0.0}/docs/devices-and-bays.md +0 -0
  43. {mx_remote-8.0.2 → mx_remote-9.0.0}/docs/diagnostics.md +0 -0
  44. {mx_remote-8.0.2 → mx_remote-9.0.0}/docs/index.md +0 -0
  45. {mx_remote-8.0.2 → mx_remote-9.0.0}/docs/multiviewer.md +0 -0
  46. {mx_remote-8.0.2 → mx_remote-9.0.0}/docs/requirements.txt +0 -0
  47. {mx_remote-8.0.2 → mx_remote-9.0.0}/docs/routing.md +0 -0
  48. {mx_remote-8.0.2 → mx_remote-9.0.0}/hatch_build.py +0 -0
  49. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/Uid.py +0 -0
  50. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/Uid.pyi +0 -0
  51. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/__init__.py +0 -0
  52. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/__init__.pyi +0 -0
  53. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/compat.py +0 -0
  54. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/compat.pyi +0 -0
  55. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/const.pyi +0 -0
  56. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/deprecated.py +0 -0
  57. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/deprecated.pyi +0 -0
  58. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/main.py +0 -0
  59. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/main.pyi +0 -0
  60. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/BayConfig.py +0 -0
  61. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/BayConfig.pyi +0 -0
  62. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/Data.py +0 -0
  63. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/Data.pyi +0 -0
  64. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/Factory.pyi +0 -0
  65. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameAmpDolbySettings.py +0 -0
  66. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameAmpDolbySettings.pyi +0 -0
  67. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameAmpZoneSettings.py +0 -0
  68. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameAmpZoneSettings.pyi +0 -0
  69. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameAudioClip.py +0 -0
  70. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameAudioClip.pyi +0 -0
  71. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameAudioSetRoute.py +0 -0
  72. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameAudioSetRoute.pyi +0 -0
  73. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameBase.py +0 -0
  74. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameBase.pyi +0 -0
  75. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameBayConfig.py +0 -0
  76. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameBayConfig.pyi +0 -0
  77. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameBayConfigSecondary.py +0 -0
  78. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameBayConfigSecondary.pyi +0 -0
  79. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameBayContent.py +0 -0
  80. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameBayContent.pyi +0 -0
  81. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameBayHide.py +0 -0
  82. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameBayHide.pyi +0 -0
  83. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameBayStatus.py +0 -0
  84. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameBayStatus.pyi +0 -0
  85. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameConnectStatus.py +0 -0
  86. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameConnectStatus.pyi +0 -0
  87. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameDebug.py +0 -0
  88. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameDebug.pyi +0 -0
  89. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameDiscover.py +0 -0
  90. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameDiscover.pyi +0 -0
  91. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameEDID.py +0 -0
  92. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameEDID.pyi +0 -0
  93. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameEDIDProfile.py +0 -0
  94. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameEDIDProfile.pyi +0 -0
  95. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameFactoryReset.py +0 -0
  96. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameFactoryReset.pyi +0 -0
  97. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameFilterStatus.py +0 -0
  98. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameFilterStatus.pyi +0 -0
  99. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameFirmwareVersion.py +0 -0
  100. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameFirmwareVersion.pyi +0 -0
  101. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameHeader.py +0 -0
  102. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameHeader.pyi +0 -0
  103. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameHello.py +0 -0
  104. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameHello.pyi +0 -0
  105. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameLinks.py +0 -0
  106. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameLinks.pyi +0 -0
  107. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameMeshOperation.py +0 -0
  108. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameMeshOperation.pyi +0 -0
  109. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameMirrorStatus.py +0 -0
  110. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameMirrorStatus.pyi +0 -0
  111. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameMonitoringPulse.py +0 -0
  112. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameMonitoringPulse.pyi +0 -0
  113. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameNetworkStatus.py +0 -0
  114. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameNetworkStatus.pyi +0 -0
  115. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FramePing.py +0 -0
  116. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FramePing.pyi +0 -0
  117. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FramePowerChange.py +0 -0
  118. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FramePowerChange.pyi +0 -0
  119. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameRCAction.py +0 -0
  120. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameRCAction.pyi +0 -0
  121. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameRCIr.py +0 -0
  122. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameRCIr.pyi +0 -0
  123. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameRCKey.py +0 -0
  124. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameRCKey.pyi +0 -0
  125. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameRCSettings.py +0 -0
  126. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameRCSettings.pyi +0 -0
  127. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameReboot.py +0 -0
  128. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameReboot.pyi +0 -0
  129. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameRoutingChange.py +0 -0
  130. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameRoutingChange.pyi +0 -0
  131. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameSetInstaller.py +0 -0
  132. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameSetInstaller.pyi +0 -0
  133. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameSetName.py +0 -0
  134. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameSetName.pyi +0 -0
  135. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameSetRoute.py +0 -0
  136. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameSetRoute.pyi +0 -0
  137. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameSetupStatus.py +0 -0
  138. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameSetupStatus.pyi +0 -0
  139. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameSignalStatus.py +0 -0
  140. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameSignalStatus.pyi +0 -0
  141. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameSignalStatusNew.py +0 -0
  142. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameSignalStatusNew.pyi +0 -0
  143. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameSysTemperature.py +0 -0
  144. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameSysTemperature.pyi +0 -0
  145. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameSystemStatus.py +0 -0
  146. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameSystemStatus.pyi +0 -0
  147. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameTXRCAction.py +0 -0
  148. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameTXRCAction.pyi +0 -0
  149. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameTXRCKey.py +0 -0
  150. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameTXRCKey.pyi +0 -0
  151. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameTime.py +0 -0
  152. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameTime.pyi +0 -0
  153. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameTimeZone.py +0 -0
  154. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameTimeZone.pyi +0 -0
  155. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameTopology.py +0 -0
  156. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameTopology.pyi +0 -0
  157. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameTxIR.py +0 -0
  158. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameTxIR.pyi +0 -0
  159. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameUnitCommand.py +0 -0
  160. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameUnitCommand.pyi +0 -0
  161. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameUnitStatus.py +0 -0
  162. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameUnitStatus.pyi +0 -0
  163. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameUpgradeFPGA.py +0 -0
  164. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameUpgradeFPGA.pyi +0 -0
  165. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPAudio.py +0 -0
  166. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPAudio.pyi +0 -0
  167. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPBayMapping.py +0 -0
  168. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPBayMapping.pyi +0 -0
  169. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPDetectBays.py +0 -0
  170. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPDetectBays.pyi +0 -0
  171. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.pyi +0 -0
  172. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPLink.py +0 -0
  173. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPLink.pyi +0 -0
  174. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.py +0 -0
  175. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.pyi +0 -0
  176. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPMultiviewer.py +0 -0
  177. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPMultiviewer.pyi +0 -0
  178. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPPowerSave.py +0 -0
  179. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPPowerSave.pyi +0 -0
  180. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPSetMaster.py +0 -0
  181. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPSetMaster.pyi +0 -0
  182. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPSettingsAll.py +0 -0
  183. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPSettingsAll.pyi +0 -0
  184. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPSinkSdp.py +0 -0
  185. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPSinkSdp.pyi +0 -0
  186. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPSourceSwitch.py +0 -0
  187. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPSourceSwitch.pyi +0 -0
  188. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPSources.py +0 -0
  189. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPSources.pyi +0 -0
  190. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPStats.py +0 -0
  191. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPStats.pyi +0 -0
  192. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPTestcard.py +0 -0
  193. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPTestcard.pyi +0 -0
  194. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPTiling.py +0 -0
  195. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPTiling.pyi +0 -0
  196. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPVideoWall.py +0 -0
  197. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameV2IPVideoWall.pyi +0 -0
  198. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameVolume.py +0 -0
  199. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameVolume.pyi +0 -0
  200. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameVolumeDown.py +0 -0
  201. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameVolumeDown.pyi +0 -0
  202. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameVolumeSet.py +0 -0
  203. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameVolumeSet.pyi +0 -0
  204. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameVolumeUp.py +0 -0
  205. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/FrameVolumeUp.pyi +0 -0
  206. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/LinkConfig.py +0 -0
  207. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/LinkConfig.pyi +0 -0
  208. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/Multiviewer.py +0 -0
  209. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/Multiviewer.pyi +0 -0
  210. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/Svd.py +0 -0
  211. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/Svd.pyi +0 -0
  212. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/V2IPConfig.py +0 -0
  213. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/V2IPConfig.pyi +0 -0
  214. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/V2IPStats.py +0 -0
  215. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/V2IPStats.pyi +0 -0
  216. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/__init__.py +0 -0
  217. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/__init__.pyi +0 -0
  218. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/proto/svd.csv +0 -0
  219. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/py.typed +0 -0
  220. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/remote/Bay.py +0 -0
  221. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/remote/Bay.pyi +0 -0
  222. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/remote/ConnectionAsync.py +0 -0
  223. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/remote/ConnectionAsync.pyi +0 -0
  224. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/remote/Link.py +0 -0
  225. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/remote/Link.pyi +0 -0
  226. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/remote/OsdSync.pyi +0 -0
  227. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/remote/State.py +0 -0
  228. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/remote/State.pyi +0 -0
  229. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/remote/V2IP.py +0 -0
  230. {mx_remote-8.0.2 → mx_remote-9.0.0}/mx_remote/remote/V2IP.pyi +0 -0
  231. {mx_remote-8.0.2 → mx_remote-9.0.0}/pyproject.toml +0 -0
  232. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/audio_ir.py +0 -0
  233. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/audiolock.py +0 -0
  234. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/baycontent.py +0 -0
  235. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/capture.py +0 -0
  236. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/complete.py +0 -0
  237. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/deprecated.py +0 -0
  238. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/e2e.py +0 -0
  239. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/entry.py +0 -0
  240. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/handlers_cover.py +0 -0
  241. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/hello.py +0 -0
  242. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/idempotent.py +0 -0
  243. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/lengths.py +0 -0
  244. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/merge.py +0 -0
  245. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/mesh.py +0 -0
  246. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/meshbays.py +0 -0
  247. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/newops.py +0 -0
  248. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/offline.py +0 -0
  249. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/offsets.py +0 -0
  250. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/ping.py +0 -0
  251. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/protogate.py +0 -0
  252. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/rcsettings.py +0 -0
  253. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/roundtrip.py +0 -0
  254. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/routes.py +0 -0
  255. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/sources.py +0 -0
  256. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/staterequest.py +0 -0
  257. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/stats.py +0 -0
  258. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/structs.py +0 -0
  259. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/txresult.py +0 -0
  260. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/unit.py +0 -0
  261. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/unknowns.py +0 -0
  262. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/v2ipptp.py +0 -0
  263. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/v2ipscaling.py +0 -0
  264. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/v2ipsdp.py +0 -0
  265. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/v2ipsubject.py +0 -0
  266. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/v2iptestcard.py +0 -0
  267. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/v2ipvlan.py +0 -0
  268. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/vdetails.py +0 -0
  269. {mx_remote-8.0.2 → mx_remote-9.0.0}/tests/wirefix.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: mx_remote
3
- Version: 8.0.2
3
+ Version: 9.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/
@@ -85,6 +85,23 @@ place, backgrounds and logo images.
85
85
  .. autodata:: mx_remote.proto.Constants.OSD_SYNC_PERCENT_MAX
86
86
  ```
87
87
 
88
+ ## Overlay messages
89
+
90
+ The message on a unit's output, which the overlay sync opcode carries too.
91
+
92
+ ```{eval-rst}
93
+ .. autoclass:: mx_remote.OsdMessage
94
+ :members:
95
+
96
+ .. autoclass:: mx_remote.proto.Constants.OsdMessageFlag
97
+ :members:
98
+
99
+ .. autoclass:: mx_remote.proto.Constants.OsdMessagePriority
100
+ :members:
101
+
102
+ .. autodata:: mx_remote.proto.Constants.OSD_MESSAGE_TEXT_MAX
103
+ ```
104
+
88
105
  ## Device identity
89
106
 
90
107
  ```{eval-rst}
@@ -180,6 +197,8 @@ state only the device reports.
180
197
 
181
198
  .. autodata:: mx_remote.proto.Constants.ONEIP_STREAM_TTL_DEFAULT
182
199
 
200
+ .. autodata:: mx_remote.proto.Constants.ONEIP_SINK_ROTATION_MAX
201
+
183
202
  .. autodata:: mx_remote.proto.Constants.ONEIP_PTP_DOMAIN_MAX
184
203
  ```
185
204
 
@@ -240,6 +259,18 @@ What an output plays from a session description, and how it is timed.
240
259
  .. autodata:: mx_remote.proto.Constants.ONEIP_SDP_MAX
241
260
  ```
242
261
 
262
+ ## OneIP stream encryption
263
+
264
+ How a source unit encrypts its streams, as it reports it.
265
+
266
+ ```{eval-rst}
267
+ .. autoclass:: mx_remote.OneIPPrivacy
268
+ :members:
269
+
270
+ .. autoclass:: mx_remote.OneIPPrivacyStream
271
+ :members:
272
+ ```
273
+
243
274
  ## OneIP test card
244
275
 
245
276
  A sink's test pattern, tone and lip-sync flash, and the ranges it accepts.
@@ -247,6 +247,10 @@ that takes a power save level switches it off, so a level write, this client's
247
247
  or another sender's, clears it unless the same frame states it. Firmware
248
248
  without the bit never reports it, and the switch is refused there.
249
249
 
250
+ `OneIPDeviceSetting.CLOUD` is a switch for whether a device connects to the
251
+ cloud service. A device that does not report it connects regardless, and the
252
+ switch is refused there.
253
+
250
254
  ### PTP
251
255
 
252
256
  A device's PTP mode, domain and grandmaster priority1 are settings like the
@@ -284,10 +288,23 @@ same block, behind `SAP_EXPIRE` and `SAP_INTERVAL`, and are set the same way;
284
288
  `SAP_LISTEN` and `SAP_ANNOUNCE` are switches like the other on/off settings.
285
289
  A device with an encoder reports its streams' TTL there too, behind
286
290
  `STREAM_TTL`; a TTL of 0 is refused, as a device changes nothing for one.
291
+ A device with an HDMI output reports the angle its output is turned by,
292
+ clockwise, behind `SINK_ROTATION`, with the step its video processor turns by.
293
+ It takes 0 or a multiple of that step up to `ONEIP_SINK_ROTATION_MAX`, so any
294
+ other angle is refused before it is sent, and the step is never written.
295
+ `set_all_oneip_device_settings` can only check the range; each device judges
296
+ the step.
297
+
298
+ ```python
299
+ m = settings.ptp_messages
300
+ print(m.sink_rotation, m.sink_rotation_step) # e.g. 0 and 90
301
+ await device.set_oneip_ptp_messages(OneIPDeviceSetting.SINK_ROTATION,
302
+ OneIPPtpMessages(sink_rotation=180))
303
+ ```
287
304
 
288
305
  Only the values behind the bits named are sent. A DSCP above `ONEIP_DSCP_MAX` is
289
306
  refused, and not cached from another controller's write. A write carrying one of
290
- these runs on to 224 bytes, or 232 with the TTL; every other write keeps the 176
307
+ these runs on to 224 bytes, or 232 with the TTL or the rotation; every other write keeps the 176
291
308
  every device knows.
292
309
 
293
310
  What the device's clock is actually doing is its PTP state, which only the
@@ -412,6 +429,47 @@ save then takes this content rather than the controller's. Logo images are
412
429
  served over HTTP from this client, on `logo_port` or a free port. Content a unit
413
430
  would refuse is refused before anything is sent; see `send_osd_sync`.
414
431
 
432
+ ### Overlay messages
433
+
434
+ A unit shows a message on its output, over the picture, until it restarts. The
435
+ text is markup of at most `OSD_MESSAGE_TEXT_MAX` bytes with no control
436
+ character, shown with a logo: 0 the built-in one, -1 none, 1 to 5 a slot. Its
437
+ priority places it against the unit's own messages, such as Locate Device.
438
+
439
+ ```python
440
+ from mx_remote.proto.Constants import OsdMessagePriority
441
+
442
+ await sink.show_osd_message('Back at <b>3</b>', logo=-1, priority=OsdMessagePriority.TOP)
443
+ await sink.request_osd_message() # the unit answers with what it shows
444
+ await sink.clear_osd_message()
445
+
446
+ print(sink.osd_message) # None until the unit has answered
447
+ ```
448
+
449
+ A unit takes a message only from management, which this client announces
450
+ itself as, and answers every write and request with the message it then has;
451
+ `on_osd_message_changed` reports a change. The answer carries
452
+ `OsdMessageFlag.REFUSED` when the unit did not take the write, as when its
453
+ video processor has no message layer. A text, logo or priority a unit would
454
+ refuse is refused before it is sent, as is a device that is not a OneIP output.
455
+
456
+ ### Stream encryption
457
+
458
+ A source unit from protocol 0x30 encrypts its video, audio and ancillary
459
+ streams, and reports what a receiver needs to decrypt them whenever that
460
+ changes and at least once a minute. The key itself never travels: every unit of
461
+ a mesh holds the same one, and this library holds none.
462
+
463
+ ```python
464
+ await source.request_oneip_privacy() # report now
465
+ await mx.request_all_oneip_privacy() # every source unit of the mesh
466
+
467
+ print(source.oneip_privacy) # None until the unit has reported
468
+ ```
469
+
470
+ A request to a unit that is not a source, or that is below protocol 0x30, is
471
+ refused before it is sent.
472
+
415
473
  ### Every device at once
416
474
 
417
475
  One broadcast changes settings on every OneIP device of the mesh. Each device
@@ -6,7 +6,7 @@
6
6
  ######################################################
7
7
 
8
8
  from abc import ABC, abstractmethod
9
- from dataclasses import dataclass
9
+ from dataclasses import dataclass, replace
10
10
  from datetime import datetime, timezone
11
11
  from enum import IntEnum
12
12
  import time
@@ -2215,8 +2215,8 @@ def _wire_enum(cls:Any, value:int) -> Any:
2215
2215
  @dataclass(frozen=True)
2216
2216
  class OneIPPtpMessages:
2217
2217
  """
2218
- How a OneIP device paces its PTP messages, its SAP timings and its streams'
2219
- TTL, which a frame carries in the same block. Each value is behind its own bit of
2218
+ How a OneIP device paces its PTP messages, its SAP timings, its streams'
2219
+ TTL and its output's rotation, which a frame carries in the same block. Each value is behind its own bit of
2220
2220
  OneIPDeviceSettings.valid, one of ONEIP_DEVICE_SETTINGS_BLOCK.
2221
2221
 
2222
2222
  Intervals are base-2 logarithms of seconds, so -3 is 125 ms and 1 is 2 s.
@@ -2241,6 +2241,19 @@ class OneIPPtpMessages:
2241
2241
  """The TTL of the device's streams, 1 to 255, behind STREAM_TTL. A device
2242
2242
  whose video processor cannot change it keeps the setting and sends
2243
2243
  ONEIP_STREAM_TTL_DEFAULT."""
2244
+ sink_rotation: int = 0
2245
+ """The degrees the device's HDMI output is turned by, clockwise, at most
2246
+ ONEIP_SINK_ROTATION_MAX, behind SINK_ROTATION."""
2247
+ sink_rotation_step: int = 0
2248
+ """The step the output turns by: the device takes only an angle that is a
2249
+ multiple of it, and 0 alone when it is 0. Only the device reports it,
2250
+ behind the same bit."""
2251
+
2252
+ def takes_sink_rotation(self, degrees:int) -> bool:
2253
+ """Whether a device reporting this step takes degrees as its output's
2254
+ rotation."""
2255
+ return (degrees == 0) or ((0 < degrees <= ONEIP_SINK_ROTATION_MAX) and (self.sink_rotation_step != 0)
2256
+ and (degrees % self.sink_rotation_step == 0))
2244
2257
 
2245
2258
  def merge(self, other:'OneIPPtpMessages', which:OneIPDeviceSetting) -> 'OneIPPtpMessages':
2246
2259
  """The values behind the bits of which copied from other, the rest kept."""
@@ -2253,7 +2266,9 @@ class OneIPPtpMessages:
2253
2266
  dscp=(other if (S.PTP_DSCP in which) else self).dscp,
2254
2267
  sap_expire_min=(other if (S.SAP_EXPIRE in which) else self).sap_expire_min,
2255
2268
  sap_interval_s=(other if (S.SAP_INTERVAL in which) else self).sap_interval_s,
2256
- stream_ttl=(other if (S.STREAM_TTL in which) else self).stream_ttl)
2269
+ stream_ttl=(other if (S.STREAM_TTL in which) else self).stream_ttl,
2270
+ sink_rotation=(other if (S.SINK_ROTATION in which) else self).sink_rotation,
2271
+ sink_rotation_step=(other if (S.SINK_ROTATION in which) else self).sink_rotation_step)
2257
2272
 
2258
2273
  def describe(self, valid:OneIPDeviceSetting) -> list[str]:
2259
2274
  """The values behind the bits of valid, one "name=value" each."""
@@ -2266,7 +2281,8 @@ class OneIPPtpMessages:
2266
2281
  (S.PTP_DSCP, 'ptp_dscp', self.dscp),
2267
2282
  (S.SAP_EXPIRE, 'sap_expire_min', self.sap_expire_min),
2268
2283
  (S.SAP_INTERVAL, 'sap_interval_s', self.sap_interval_s),
2269
- (S.STREAM_TTL, 'stream_ttl', self.stream_ttl))
2284
+ (S.STREAM_TTL, 'stream_ttl', self.stream_ttl),
2285
+ (S.SINK_ROTATION, 'sink_rotation', self.sink_rotation))
2270
2286
  if (bit in valid)]
2271
2287
 
2272
2288
  @dataclass(frozen=True)
@@ -2317,8 +2333,7 @@ class OsdSyncRequest:
2317
2333
  missing: bytes
2318
2334
  """The settings it lacks, bit i standing for setting i, 16 bytes."""
2319
2335
  source: MxrDeviceUid
2320
- """Who sent the sync it asks about. Empty when it asks for a new sync, and
2321
- from a unit whose module does not name the source."""
2336
+ """Who sent the sync it asks about. Empty when it asks for a new sync."""
2322
2337
 
2323
2338
  def is_missing(self, index:int) -> bool:
2324
2339
  """Whether the unit lacks setting index."""
@@ -2394,6 +2409,28 @@ class OsdSyncReport:
2394
2409
  reason: str
2395
2410
  """Why it failed or refused any."""
2396
2411
 
2412
+ @dataclass(frozen=True)
2413
+ class OsdMessage:
2414
+ """
2415
+ The message on a unit's output, as the unit last reported it. The unit
2416
+ holds it until it restarts.
2417
+ """
2418
+ flags: OsdMessageFlag
2419
+ """Whether a message is set and shown, whether the unit can show one, and
2420
+ whether it refused the write it answers. Without SET the other fields are
2421
+ empty."""
2422
+ logo: int
2423
+ """The logo shown with it: 0 the built-in logo, -1 none, 1 to 5 a slot."""
2424
+ priority: OsdMessagePriority|int
2425
+ """Where it stands against the unit's own messages."""
2426
+ text: str
2427
+ """Its markup."""
2428
+
2429
+ def __str__(self) -> str:
2430
+ if (OsdMessageFlag.SET not in self.flags):
2431
+ return f"none{' (refused)' if (OsdMessageFlag.REFUSED in self.flags) else ''}"
2432
+ return f"'{self.text}' logo {self.logo} {self.flags!r}"
2433
+
2397
2434
  @dataclass(frozen=True)
2398
2435
  class OsdSyncEntry:
2399
2436
  """One setting of an overlay sync this client sends."""
@@ -2471,6 +2508,59 @@ class OneIPSinkSdp:
2471
2508
  return (f"'{self.name}' {self.group}:{self.port} {encoding} {self.channels}ch {self.rate_hz}Hz"
2472
2509
  f"{' audio only' if (OneIPSdpFlag.AUDIO_ONLY in self.flags) else ''}")
2473
2510
 
2511
+ @dataclass(frozen=True)
2512
+ class OneIPPrivacyStream:
2513
+ """
2514
+ How one of a OneIP source unit's streams is encrypted, as the unit reports
2515
+ it: the values of the stream's ``a=privacy`` and ``a=extmap`` session
2516
+ description attributes.
2517
+
2518
+ A receiver needs these to decrypt the stream. The key itself never travels:
2519
+ every unit of a mesh holds the same one. This library holds no key and
2520
+ decrypts nothing.
2521
+ """
2522
+ group: str = '0.0.0.0'
2523
+ """The stream's destination address."""
2524
+ port: int = 0
2525
+ """The stream's UDP port."""
2526
+ version: int = 0
2527
+ """Steps whenever the parameters change."""
2528
+ on: bool = False
2529
+ """Whether the stream is encrypted. Without it the stream goes out in the
2530
+ clear, and the fields below mean nothing."""
2531
+ full_id: int = 0
2532
+ """The RTP header extension id of the full IV counter."""
2533
+ short_id: int = 0
2534
+ """The RTP header extension id of the short IV counter."""
2535
+ iv: bytes = bytes(8)
2536
+ """The initialisation vector, 8 bytes."""
2537
+ key_generator: bytes = bytes(16)
2538
+ """The key generator, 16 bytes."""
2539
+ key_id: bytes = bytes(8)
2540
+ """Names the mesh's key, 8 bytes. A receiver holding another drops the stream."""
2541
+ key_version: int = 0
2542
+ """The key version."""
2543
+
2544
+ def __str__(self) -> str:
2545
+ if not self.on:
2546
+ return f"{self.group}:{self.port} clear"
2547
+ return f"{self.group}:{self.port} encrypted, key {self.key_id.hex()} v{self.key_version}"
2548
+
2549
+ @dataclass(frozen=True)
2550
+ class OneIPPrivacy:
2551
+ """
2552
+ How a OneIP source unit's streams are encrypted, as it last reported it. A
2553
+ unit reports this whenever it changes, at least once a minute, and when
2554
+ asked. A unit that is in no mesh sends in the clear.
2555
+ """
2556
+ video: OneIPPrivacyStream
2557
+ audio: OneIPPrivacyStream
2558
+ anc: OneIPPrivacyStream
2559
+ """The ancillary data stream."""
2560
+
2561
+ def __str__(self) -> str:
2562
+ return f"video {self.video}, audio {self.audio}, anc {self.anc}"
2563
+
2474
2564
  @dataclass(frozen=True)
2475
2565
  class OneIPTestTone:
2476
2566
  """A OneIP sink's test tone."""
@@ -2714,16 +2804,25 @@ class OneIPDeviceSettings:
2714
2804
  else previous._power_save_level),
2715
2805
  ptp_messages=previous._ptp_messages.merge(self._ptp_messages, valid))
2716
2806
 
2717
- def as_applied_to(self, reported:OneIPDeviceSetting) -> 'OneIPDeviceSettings':
2807
+ def as_applied_to(self, reported:'OneIPDeviceSettings|OneIPDeviceSetting|None') -> 'OneIPDeviceSettings':
2718
2808
  """
2719
- This block limited to what a device takes from a write about it.
2809
+ This block limited to what a device that reported reported takes from a
2810
+ write about it.
2720
2811
 
2721
2812
  A device applies a setting only if it has it, a profile, PTP mode, PTP
2722
2813
  domain, power save level or PTP DSCP only within its range, a stream
2723
- TTL only when it is not 0, and never what only it can report about
2724
- itself.
2814
+ TTL only when it is not 0, a rotation only by its own step, and never
2815
+ what only it can report about itself. The step stays the device's.
2816
+
2817
+ reported may be only the bits the device reported, which says nothing
2818
+ of its step, so a rotation other than 0 is then left out.
2725
2819
  """
2726
- valid = (self._valid & reported) & ~ONEIP_DEVICE_SETTINGS_REPORTED_ONLY
2820
+ if (reported is None):
2821
+ reported = OneIPDeviceSettings()
2822
+ elif not isinstance(reported, OneIPDeviceSettings):
2823
+ reported = OneIPDeviceSettings(valid=OneIPDeviceSetting(reported))
2824
+ own = reported._ptp_messages
2825
+ valid = (self._valid & reported._valid) & ~ONEIP_DEVICE_SETTINGS_REPORTED_ONLY
2727
2826
  if not (0 <= self._ir_profile < ONEIP_IR_PROFILE_MAX):
2728
2827
  valid &= ~OneIPDeviceSetting.IR_PROFILE
2729
2828
  if not (ONEIP_IR_PROFILE_NOT_SET <= self._ir_profile_sink < ONEIP_IR_PROFILE_MAX):
@@ -2738,13 +2837,16 @@ class OneIPDeviceSettings:
2738
2837
  valid &= ~OneIPDeviceSetting.PTP_DSCP
2739
2838
  if (self._ptp_messages.stream_ttl == 0):
2740
2839
  valid &= ~OneIPDeviceSetting.STREAM_TTL
2840
+ if not own.takes_sink_rotation(self._ptp_messages.sink_rotation):
2841
+ valid &= ~OneIPDeviceSetting.SINK_ROTATION
2741
2842
  return OneIPDeviceSettings(valid=valid, flags=self._flags, ir_profiles=self._ir_profiles,
2742
2843
  ir_profile=self._ir_profile, ir_profile_sink=self._ir_profile_sink,
2743
2844
  auto_power_save=self._auto_power_save, power_save=self._power_save,
2744
2845
  ptp_mode=self._ptp_mode, ptp_domain=self._ptp_domain,
2745
2846
  ptp_priority1=self._ptp_priority1,
2746
2847
  power_save_level=self._power_save_level,
2747
- ptp_messages=self._ptp_messages)
2848
+ ptp_messages=replace(self._ptp_messages,
2849
+ sink_rotation_step=own.sink_rotation_step))
2748
2850
 
2749
2851
  def __eq__(self, other:Any) -> bool:
2750
2852
  if not isinstance(other, OneIPDeviceSettings):
@@ -3452,6 +3554,26 @@ class DeviceBase(ABC):
3452
3554
  def osd_sync_report(self) -> OsdSyncReport|None:
3453
3555
  '''This unit's last report on taking an overlay sync, None until it has.'''
3454
3556
 
3557
+ @property
3558
+ @abstractmethod
3559
+ def osd_message(self) -> OsdMessage|None:
3560
+ '''The message on this unit's output, as it last reported it to this
3561
+ client, None until it has. A unit reports it only when asked, or when
3562
+ this client writes it.'''
3563
+
3564
+ @abstractmethod
3565
+ async def request_osd_message(self) -> bool:
3566
+ '''ask this unit for the message on its output'''
3567
+
3568
+ @abstractmethod
3569
+ async def show_osd_message(self, text:str, logo:int=-1,
3570
+ priority:OsdMessagePriority=OsdMessagePriority.NORMAL) -> bool:
3571
+ '''show a message on this unit's output, in place of any it has'''
3572
+
3573
+ @abstractmethod
3574
+ async def clear_osd_message(self) -> bool:
3575
+ '''take the message down from this unit's output'''
3576
+
3455
3577
  @property
3456
3578
  @abstractmethod
3457
3579
  def oneip_sink_sdp(self) -> OneIPSinkSdp|None:
@@ -3459,6 +3581,17 @@ class DeviceBase(ABC):
3459
3581
  reported it, None until it has. It reports when that changes and at
3460
3582
  least once a minute.'''
3461
3583
 
3584
+ @property
3585
+ @abstractmethod
3586
+ def oneip_privacy(self) -> OneIPPrivacy|None:
3587
+ '''How this OneIP source unit's streams are encrypted, as it last
3588
+ reported it, None until it has. It reports when that changes and at
3589
+ least once a minute.'''
3590
+
3591
+ @abstractmethod
3592
+ async def request_oneip_privacy(self) -> bool:
3593
+ '''ask this OneIP source unit how its streams are encrypted now'''
3594
+
3462
3595
  @abstractmethod
3463
3596
  async def play_oneip_sdp(self, sdp:str) -> bool:
3464
3597
  '''have this OneIP output play the stream a session description describes'''
@@ -4056,6 +4189,11 @@ class MxrCallbacks:
4056
4189
  _LOGGER.debug(f"{dev} overlay sync {report.seq}: {report.state!r} {report.reason}")
4057
4190
  self.on_device_update(dev)
4058
4191
 
4192
+ def on_osd_message_changed(self, dev:DeviceBase, message:OsdMessage) -> None:
4193
+ ''' called when unit 'dev' reported something different about the message on its output '''
4194
+ _LOGGER.debug(f"{dev} overlay message: {message}")
4195
+ self.on_device_update(dev)
4196
+
4059
4197
  def on_osd_sync_requested(self, dev:DeviceBase, request:OsdSyncRequest) -> None:
4060
4198
  ''' called when 'dev' asked for an overlay sync, or for settings of one '''
4061
4199
  pass
@@ -787,6 +787,9 @@ class OneIPPtpMessages:
787
787
  sap_expire_min: int = ...
788
788
  sap_interval_s: int = ...
789
789
  stream_ttl: int = ...
790
+ sink_rotation: int = ...
791
+ sink_rotation_step: int = ...
792
+ def takes_sink_rotation(self, degrees: int) -> bool: ...
790
793
  def merge(self, other: OneIPPtpMessages, which: OneIPDeviceSetting) -> OneIPPtpMessages: ...
791
794
  def describe(self, valid: OneIPDeviceSetting) -> list[str]: ...
792
795
 
@@ -853,6 +856,13 @@ class OsdSyncReport:
853
856
  refused: int
854
857
  reason: str
855
858
 
859
+ @dataclass(frozen=True)
860
+ class OsdMessage:
861
+ flags: OsdMessageFlag
862
+ logo: int
863
+ priority: OsdMessagePriority | int
864
+ text: str
865
+
856
866
  @dataclass(frozen=True)
857
867
  class OsdSyncEntry:
858
868
  value: OsdSyncValue
@@ -891,6 +901,25 @@ class OneIPSinkSdp:
891
901
  @property
892
902
  def active(self) -> bool: ...
893
903
 
904
+ @dataclass(frozen=True)
905
+ class OneIPPrivacyStream:
906
+ group: str = ...
907
+ port: int = ...
908
+ version: int = ...
909
+ on: bool = ...
910
+ full_id: int = ...
911
+ short_id: int = ...
912
+ iv: bytes = ...
913
+ key_generator: bytes = ...
914
+ key_id: bytes = ...
915
+ key_version: int = ...
916
+
917
+ @dataclass(frozen=True)
918
+ class OneIPPrivacy:
919
+ video: OneIPPrivacyStream
920
+ audio: OneIPPrivacyStream
921
+ anc: OneIPPrivacyStream
922
+
894
923
  @dataclass(frozen=True)
895
924
  class OneIPTestTone:
896
925
  mode: OneIPToneMode | int = ...
@@ -949,7 +978,7 @@ class OneIPDeviceSettings:
949
978
  @property
950
979
  def ptp_messages(self) -> OneIPPtpMessages | None: ...
951
980
  def merge(self, previous: OneIPDeviceSettings | None) -> OneIPDeviceSettings: ...
952
- def as_applied_to(self, reported: OneIPDeviceSetting) -> OneIPDeviceSettings: ...
981
+ def as_applied_to(self, reported: OneIPDeviceSettings | OneIPDeviceSetting | None) -> OneIPDeviceSettings: ...
953
982
  def __eq__(self, other: Any) -> bool: ...
954
983
  def __ne__(self, other: Any) -> bool: ...
955
984
 
@@ -1305,7 +1334,21 @@ class DeviceBase(ABC):
1305
1334
  def osd_sync_report(self) -> OsdSyncReport | None: ...
1306
1335
  @property
1307
1336
  @abstractmethod
1337
+ def osd_message(self) -> OsdMessage | None: ...
1338
+ @abstractmethod
1339
+ async def request_osd_message(self) -> bool: ...
1340
+ @abstractmethod
1341
+ async def show_osd_message(self, text: str, logo: int = -1, priority: OsdMessagePriority = ...) -> bool: ...
1342
+ @abstractmethod
1343
+ async def clear_osd_message(self) -> bool: ...
1344
+ @property
1345
+ @abstractmethod
1308
1346
  def oneip_sink_sdp(self) -> OneIPSinkSdp | None: ...
1347
+ @property
1348
+ @abstractmethod
1349
+ def oneip_privacy(self) -> OneIPPrivacy | None: ...
1350
+ @abstractmethod
1351
+ async def request_oneip_privacy(self) -> bool: ...
1309
1352
  @abstractmethod
1310
1353
  async def play_oneip_sdp(self, sdp: str) -> bool: ...
1311
1354
  @abstractmethod
@@ -1537,6 +1580,7 @@ class MxrCallbacks:
1537
1580
  def on_status_hdbt_connected_changed(self, bay: BayBase, val: bool) -> None: ...
1538
1581
  def on_osd_sync(self, dev: DeviceBase, sync: OsdSync) -> None: ...
1539
1582
  def on_osd_sync_report(self, dev: DeviceBase, report: OsdSyncReport) -> None: ...
1583
+ def on_osd_message_changed(self, dev: DeviceBase, message: OsdMessage) -> None: ...
1540
1584
  def on_osd_sync_requested(self, dev: DeviceBase, request: OsdSyncRequest) -> None: ...
1541
1585
  def on_osd_sync_setting(self, dev: DeviceBase, setting: OsdSyncSetting) -> None: ...
1542
1586
  def on_bay_content_changed(self, bay: BayBase, content: BayContent) -> None: ...
@@ -14,7 +14,7 @@ Default communication modes:
14
14
  """
15
15
  import os
16
16
 
17
- VERSION = '8.0.2'
17
+ VERSION = '9.0.0'
18
18
  __version__ = VERSION
19
19
 
20
20
  MX_BCAST_UDP_PORT = 8811
@@ -10,7 +10,7 @@
10
10
  from enum import IntEnum, IntFlag
11
11
  from typing import TypeVar
12
12
 
13
- MXR_PROTOCOL_VERSION = 0x2F
13
+ MXR_PROTOCOL_VERSION = 0x30
14
14
  """Highest mx_remote protocol version this library understands.
15
15
 
16
16
  Mirrors the firmware's own protocol version. Bump
@@ -31,7 +31,8 @@ every 20-30s once every device it knows is on 0x2A.
31
31
  0x2B.
32
32
 
33
33
  0x2E is the row of V2IP_SINK_SDP, so an output's session description report
34
- arrives stamped 0x2E, and 0x2F the row of V2IP_OSD_SYNC."""
34
+ arrives stamped 0x2E, 0x2F the row of V2IP_OSD_SYNC and 0x30 the row of
35
+ V2IP_PRIVACY."""
35
36
 
36
37
  MXR_PROTOCOL_VERSION_SLOW_HELLO = 0x2A
37
38
  """First protocol version whose devices may hello only every 20-30s."""
@@ -121,6 +122,7 @@ MXR_OPCODE_VERSIONS: dict[int, int] = {
121
122
  0x51: 0x2D, # BAY_CONTENT
122
123
  0x52: 0x2E, # V2IP_SINK_SDP
123
124
  0x53: 0x2F, # V2IP_OSD_SYNC
125
+ 0x54: 0x30, # V2IP_PRIVACY
124
126
  }
125
127
  """Per-opcode minimum compatible protocol version, mirroring the firmware's
126
128
  own opcode table. Transmitters stamp the
@@ -374,9 +376,16 @@ class OneIPDeviceSetting(IntFlag):
374
376
  STREAM_TTL = (1 << 29)
375
377
  '''The TTL of the device's streams, video, audio and ancillary alike.
376
378
  Reported only by a device with an encoder.'''
379
+ CLOUD = (1 << 30)
380
+ '''Connects to the cloud service. A device that does not report it
381
+ connects regardless.'''
382
+ SINK_ROTATION = (1 << 31)
383
+ '''The angle the device's HDMI output is turned by. Reported only by a
384
+ device with an HDMI output.'''
377
385
 
378
386
  ONEIP_DEVICE_SETTING_SWITCHES = OneIPDeviceSetting(0xFF) | OneIPDeviceSetting.AES67 \
379
- | OneIPDeviceSetting.SAP_LISTEN | OneIPDeviceSetting.SAP_ANNOUNCE | OneIPDeviceSetting.POWER_SAVE_DISABLED
387
+ | OneIPDeviceSetting.SAP_LISTEN | OneIPDeviceSetting.SAP_ANNOUNCE | OneIPDeviceSetting.POWER_SAVE_DISABLED \
388
+ | OneIPDeviceSetting.CLOUD
380
389
  """The settings that are on or off, as opposed to carrying a value, and that
381
390
  can be written."""
382
391
 
@@ -392,14 +401,17 @@ ONEIP_DEVICE_SETTINGS_PTP_MESSAGES = OneIPDeviceSetting.PTP_ANNOUNCE | OneIPDevi
392
401
  """The PTP message settings."""
393
402
 
394
403
  ONEIP_DEVICE_SETTINGS_BLOCK = ONEIP_DEVICE_SETTINGS_PTP_MESSAGES | OneIPDeviceSetting.SAP_EXPIRE \
395
- | OneIPDeviceSetting.SAP_INTERVAL | OneIPDeviceSetting.STREAM_TTL
404
+ | OneIPDeviceSetting.SAP_INTERVAL | OneIPDeviceSetting.STREAM_TTL | OneIPDeviceSetting.SINK_ROTATION
396
405
  """The settings whose values a frame carries in a block of their own behind the
397
- rest, OneIPPtpMessages: the PTP message settings, the two SAP values and the
398
- stream TTL."""
406
+ rest, OneIPPtpMessages: the PTP message settings, the two SAP values, the
407
+ stream TTL and the output's rotation."""
399
408
 
400
409
  ONEIP_STREAM_TTL_DEFAULT = 64
401
410
  """The TTL a OneIP device's streams carry until it is set."""
402
411
 
412
+ ONEIP_SINK_ROTATION_MAX = 359
413
+ """The largest angle a OneIP device's HDMI output is turned by, in degrees."""
414
+
403
415
  class OneIPPtpMode(IntEnum):
404
416
  '''How a OneIP device takes part in PTP.'''
405
417
  OFF = 0
@@ -428,6 +440,31 @@ class OsdSyncWhat(IntFlag):
428
440
  LOGO_SLOTS = (1 << 3)
429
441
  '''The logo images in the four slots.'''
430
442
 
443
+ class OsdMessageFlag(IntFlag):
444
+ '''What a unit says of the message on its output.'''
445
+ SET = (1 << 0)
446
+ '''A message is set. In a write: show it, rather than take it down.'''
447
+ SHOWN = (1 << 1)
448
+ '''The output shows it, rather than another message ahead of it.'''
449
+ SUPPORTED = (1 << 2)
450
+ '''The unit's video processor composes the message layer.'''
451
+ REFUSED = (1 << 3)
452
+ '''The unit did not take the write it answers, and reports the message it
453
+ kept.'''
454
+
455
+ class OsdMessagePriority(IntEnum):
456
+ '''Where a message stands against the unit's own messages: its upgrade,
457
+ Locate Device and encrypted source messages.'''
458
+ LOW = 0
459
+ '''After them.'''
460
+ NORMAL = 1
461
+ '''Between them.'''
462
+ TOP = 2
463
+ '''Before them.'''
464
+
465
+ OSD_MESSAGE_TEXT_MAX = 255
466
+ """The longest message text a unit takes, in bytes."""
467
+
431
468
  class OsdSyncState(IntEnum):
432
469
  '''How far a unit has got with taking an overlay sync.'''
433
470
  TAKING = 0
@@ -108,6 +108,8 @@ class OneIPDeviceSetting(IntFlag):
108
108
  SAP_INTERVAL: Incomplete
109
109
  POWER_SAVE_DISABLED: Incomplete
110
110
  STREAM_TTL: Incomplete
111
+ CLOUD: Incomplete
112
+ SINK_ROTATION: Incomplete
111
113
 
112
114
  ONEIP_DEVICE_SETTING_SWITCHES: Incomplete
113
115
  ONEIP_DEVICE_SETTINGS_REPORTED_ONLY: Incomplete
@@ -115,6 +117,7 @@ ONEIP_DEVICE_SETTINGS_PTP: Incomplete
115
117
  ONEIP_DEVICE_SETTINGS_PTP_MESSAGES: Incomplete
116
118
  ONEIP_DEVICE_SETTINGS_BLOCK: Incomplete
117
119
  ONEIP_STREAM_TTL_DEFAULT: int
120
+ ONEIP_SINK_ROTATION_MAX: int
118
121
 
119
122
  class OneIPPtpMode(IntEnum):
120
123
  OFF: int
@@ -131,6 +134,19 @@ class OsdSyncWhat(IntFlag):
131
134
  BACKGROUNDS: Incomplete
132
135
  LOGO_SLOTS: Incomplete
133
136
 
137
+ class OsdMessageFlag(IntFlag):
138
+ SET: Incomplete
139
+ SHOWN: Incomplete
140
+ SUPPORTED: Incomplete
141
+ REFUSED: Incomplete
142
+
143
+ class OsdMessagePriority(IntEnum):
144
+ LOW: int
145
+ NORMAL: int
146
+ TOP: int
147
+
148
+ OSD_MESSAGE_TEXT_MAX: int
149
+
134
150
  class OsdSyncState(IntEnum):
135
151
  TAKING: int
136
152
  DONE: int
@@ -246,6 +246,9 @@ def _mxr_frame_factory(hdr:FrameHeader, timestamp:float) -> FrameBase|None:
246
246
  if hdr.opcode == 0x53:
247
247
  from .FrameV2IPOsdSync import FrameV2IPOsdSync
248
248
  return FrameV2IPOsdSync(header=hdr, timestamp=timestamp)
249
+ if hdr.opcode == 0x54:
250
+ from .FrameV2IPPrivacy import FrameV2IPPrivacy
251
+ return FrameV2IPPrivacy(header=hdr, timestamp=timestamp)
249
252
  if hdr.opcode == 0x50:
250
253
  from .FrameUnitCommand import FrameUnitCommand
251
254
  return FrameUnitCommand(header=hdr, timestamp=timestamp)