mx-remote 5.1.0__tar.gz → 5.3.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 (212) hide show
  1. {mx_remote-5.1.0 → mx_remote-5.3.0}/PKG-INFO +1 -1
  2. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/const.py +12 -1
  3. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/const.pyi +2 -0
  4. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/Constants.py +4 -0
  5. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/Factory.py +11 -4
  6. mx_remote-5.3.0/mx_remote/proto/FrameEDIDProfile.py +53 -0
  7. mx_remote-5.3.0/mx_remote/proto/FrameEDIDProfile.pyi +15 -0
  8. mx_remote-5.3.0/mx_remote/proto/FrameSetName.py +62 -0
  9. mx_remote-5.3.0/mx_remote/proto/FrameSetName.pyi +17 -0
  10. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/Remote.py +72 -19
  11. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/Remote.pyi +1 -1
  12. mx_remote-5.3.0/tests/entry.py +59 -0
  13. {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/handlers.py +28 -0
  14. mx_remote-5.3.0/tests/handlers_cover.py +268 -0
  15. mx_remote-5.3.0/tests/hello.py +214 -0
  16. mx_remote-5.3.0/tests/proto_stamp.py +122 -0
  17. mx_remote-5.3.0/tests/roundtrip.py +176 -0
  18. {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/run.py +4 -0
  19. mx_remote-5.1.0/mx_remote/proto/FrameEDIDProfile.py +0 -23
  20. mx_remote-5.1.0/mx_remote/proto/FrameEDIDProfile.pyi +0 -6
  21. mx_remote-5.1.0/mx_remote/proto/FramePDUState.py +0 -72
  22. mx_remote-5.1.0/mx_remote/proto/FramePDUState.pyi +0 -23
  23. mx_remote-5.1.0/mx_remote/proto/FrameSetName.py +0 -26
  24. mx_remote-5.1.0/mx_remote/proto/FrameSetName.pyi +0 -6
  25. mx_remote-5.1.0/mx_remote/proto/FrameV2IPStreamDetails.py +0 -60
  26. mx_remote-5.1.0/mx_remote/proto/FrameV2IPStreamDetails.pyi +0 -17
  27. mx_remote-5.1.0/mx_remote/proto/PDUState.py +0 -100
  28. mx_remote-5.1.0/mx_remote/proto/PDUState.pyi +0 -29
  29. mx_remote-5.1.0/mx_remote/remote/P8PDU.py +0 -156
  30. mx_remote-5.1.0/mx_remote/remote/P8PDU.pyi +0 -46
  31. mx_remote-5.1.0/tests/proto_stamp.py +0 -60
  32. {mx_remote-5.1.0 → mx_remote-5.3.0}/.gitignore +0 -0
  33. {mx_remote-5.1.0 → mx_remote-5.3.0}/CLAUDE.md +0 -0
  34. {mx_remote-5.1.0 → mx_remote-5.3.0}/LICENSE +0 -0
  35. {mx_remote-5.1.0 → mx_remote-5.3.0}/README.md +0 -0
  36. {mx_remote-5.1.0 → mx_remote-5.3.0}/hatch_build.py +0 -0
  37. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/Interface.py +0 -0
  38. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/Interface.pyi +0 -0
  39. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/Uid.py +0 -0
  40. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/Uid.pyi +0 -0
  41. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/__init__.py +0 -0
  42. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/__init__.pyi +0 -0
  43. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/main.py +0 -0
  44. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/main.pyi +0 -0
  45. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/BayConfig.py +0 -0
  46. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/BayConfig.pyi +0 -0
  47. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/Constants.pyi +0 -0
  48. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/Data.py +0 -0
  49. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/Data.pyi +0 -0
  50. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/Factory.pyi +0 -0
  51. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameAmpDolbySettings.py +0 -0
  52. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameAmpDolbySettings.pyi +0 -0
  53. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameAmpZoneSettings.py +0 -0
  54. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameAmpZoneSettings.pyi +0 -0
  55. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameAudioClip.py +0 -0
  56. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameAudioClip.pyi +0 -0
  57. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameAudioSetRoute.py +0 -0
  58. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameAudioSetRoute.pyi +0 -0
  59. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameBase.py +0 -0
  60. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameBase.pyi +0 -0
  61. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameBayConfig.py +0 -0
  62. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameBayConfig.pyi +0 -0
  63. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameBayConfigSecondary.py +0 -0
  64. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameBayConfigSecondary.pyi +0 -0
  65. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameBayHide.py +0 -0
  66. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameBayHide.pyi +0 -0
  67. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameBayStatus.py +0 -0
  68. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameBayStatus.pyi +0 -0
  69. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameConnectStatus.py +0 -0
  70. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameConnectStatus.pyi +0 -0
  71. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameDebug.py +0 -0
  72. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameDebug.pyi +0 -0
  73. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameDiscover.py +0 -0
  74. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameDiscover.pyi +0 -0
  75. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameEDID.py +0 -0
  76. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameEDID.pyi +0 -0
  77. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameFactoryReset.py +0 -0
  78. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameFactoryReset.pyi +0 -0
  79. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameFilterStatus.py +0 -0
  80. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameFilterStatus.pyi +0 -0
  81. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameFirmwareVersion.py +0 -0
  82. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameFirmwareVersion.pyi +0 -0
  83. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameHeader.py +0 -0
  84. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameHeader.pyi +0 -0
  85. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameHello.py +0 -0
  86. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameHello.pyi +0 -0
  87. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameLinks.py +0 -0
  88. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameLinks.pyi +0 -0
  89. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameMeshOperation.py +0 -0
  90. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameMeshOperation.pyi +0 -0
  91. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameMirrorStatus.py +0 -0
  92. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameMirrorStatus.pyi +0 -0
  93. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameMonitoringPulse.py +0 -0
  94. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameMonitoringPulse.pyi +0 -0
  95. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameNetworkStatus.py +0 -0
  96. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameNetworkStatus.pyi +0 -0
  97. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FramePowerChange.py +0 -0
  98. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FramePowerChange.pyi +0 -0
  99. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameRCAction.py +0 -0
  100. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameRCAction.pyi +0 -0
  101. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameRCIr.py +0 -0
  102. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameRCIr.pyi +0 -0
  103. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameRCKey.py +0 -0
  104. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameRCKey.pyi +0 -0
  105. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameRCSettings.py +0 -0
  106. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameRCSettings.pyi +0 -0
  107. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameReboot.py +0 -0
  108. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameReboot.pyi +0 -0
  109. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameRoutingChange.py +0 -0
  110. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameRoutingChange.pyi +0 -0
  111. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSetInstaller.py +0 -0
  112. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSetInstaller.pyi +0 -0
  113. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSetRoute.py +0 -0
  114. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSetRoute.pyi +0 -0
  115. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSetupStatus.py +0 -0
  116. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSetupStatus.pyi +0 -0
  117. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSignalStatus.py +0 -0
  118. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSignalStatus.pyi +0 -0
  119. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSignalStatusNew.py +0 -0
  120. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSignalStatusNew.pyi +0 -0
  121. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSysTemperature.py +0 -0
  122. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSysTemperature.pyi +0 -0
  123. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSystemStatus.py +0 -0
  124. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameSystemStatus.pyi +0 -0
  125. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameTXRCAction.py +0 -0
  126. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameTXRCAction.pyi +0 -0
  127. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameTXRCKey.py +0 -0
  128. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameTXRCKey.pyi +0 -0
  129. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameTopology.py +0 -0
  130. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameTopology.pyi +0 -0
  131. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameTxIR.py +0 -0
  132. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameTxIR.pyi +0 -0
  133. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameUpgradeFPGA.py +0 -0
  134. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameUpgradeFPGA.pyi +0 -0
  135. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPAudio.py +0 -0
  136. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPAudio.pyi +0 -0
  137. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPBayMapping.py +0 -0
  138. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPBayMapping.pyi +0 -0
  139. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPDetectBays.py +0 -0
  140. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPDetectBays.pyi +0 -0
  141. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.py +0 -0
  142. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.pyi +0 -0
  143. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPLink.py +0 -0
  144. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPLink.pyi +0 -0
  145. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.py +0 -0
  146. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.pyi +0 -0
  147. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPMultiviewer.py +0 -0
  148. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPMultiviewer.pyi +0 -0
  149. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPPowerSave.py +0 -0
  150. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPPowerSave.pyi +0 -0
  151. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPSetMaster.py +0 -0
  152. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPSetMaster.pyi +0 -0
  153. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPSourceSwitch.py +0 -0
  154. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPSourceSwitch.pyi +0 -0
  155. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPSources.py +0 -0
  156. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPSources.pyi +0 -0
  157. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPStats.py +0 -0
  158. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPStats.pyi +0 -0
  159. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPTiling.py +0 -0
  160. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPTiling.pyi +0 -0
  161. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPVideoWall.py +0 -0
  162. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameV2IPVideoWall.pyi +0 -0
  163. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameVolume.py +0 -0
  164. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameVolume.pyi +0 -0
  165. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameVolumeDown.py +0 -0
  166. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameVolumeDown.pyi +0 -0
  167. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameVolumeSet.py +0 -0
  168. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameVolumeSet.pyi +0 -0
  169. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameVolumeUp.py +0 -0
  170. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/FrameVolumeUp.pyi +0 -0
  171. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/LinkConfig.py +0 -0
  172. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/LinkConfig.pyi +0 -0
  173. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/Multiviewer.py +0 -0
  174. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/Multiviewer.pyi +0 -0
  175. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/Svd.py +0 -0
  176. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/Svd.pyi +0 -0
  177. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/V2IPConfig.py +0 -0
  178. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/V2IPConfig.pyi +0 -0
  179. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/V2IPStats.py +0 -0
  180. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/V2IPStats.pyi +0 -0
  181. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/__init__.py +0 -0
  182. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/__init__.pyi +0 -0
  183. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/proto/svd.csv +0 -0
  184. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/py.typed +0 -0
  185. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/Bay.py +0 -0
  186. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/Bay.pyi +0 -0
  187. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/ConnectionAsync.py +0 -0
  188. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/ConnectionAsync.pyi +0 -0
  189. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/Device.py +0 -0
  190. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/Device.pyi +0 -0
  191. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/Link.py +0 -0
  192. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/Link.pyi +0 -0
  193. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/State.py +0 -0
  194. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/State.pyi +0 -0
  195. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/V2IP.py +0 -0
  196. {mx_remote-5.1.0 → mx_remote-5.3.0}/mx_remote/remote/V2IP.pyi +0 -0
  197. {mx_remote-5.1.0 → mx_remote-5.3.0}/pyproject.toml +0 -0
  198. {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/audio_ir.py +0 -0
  199. {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/capture.py +0 -0
  200. {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/e2e.py +0 -0
  201. {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/merge.py +0 -0
  202. {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/newops.py +0 -0
  203. {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/offsets.py +0 -0
  204. {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/protogate.py +0 -0
  205. {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/rcsettings.py +0 -0
  206. {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/smoke.py +0 -0
  207. {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/sources.py +0 -0
  208. {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/stats.py +0 -0
  209. {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/structs.py +0 -0
  210. {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/txresult.py +0 -0
  211. {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/unknowns.py +0 -0
  212. {mx_remote-5.1.0 → mx_remote-5.3.0}/tests/vdetails.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: mx_remote
3
- Version: 5.1.0
3
+ Version: 5.3.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://github.com/opdenkamp/mx-remote/
@@ -14,7 +14,7 @@ Default communication modes:
14
14
  """
15
15
  import os
16
16
 
17
- VERSION = '5.1.0'
17
+ VERSION = '5.3.0'
18
18
  __version__ = VERSION
19
19
 
20
20
  MX_BCAST_UDP_PORT = 8811
@@ -26,6 +26,17 @@ MX_MCAST_UDP_IP = '224.8.8.8'
26
26
  MX_MCAST_UDP_PORT = 8812
27
27
  """UDP port used in multicast mode (default)."""
28
28
 
29
+ MXR_HELLO_INTERVAL_MIN = 2.5
30
+ """Shortest gap between hello announcements, in seconds (firmware ``hello_timeout`` minimum)."""
31
+
32
+ MXR_HELLO_INTERVAL_RAND = 2.5
33
+ """Random spread added to each hello interval, in seconds.
34
+
35
+ Mirrors ``rand_timeout_init(&mxr_class->hello_timeout, 2500, 2500)``: the delay is
36
+ re-drawn after every hello that is sent, so devices announcing on the same mesh do
37
+ not converge into synchronised bursts.
38
+ """
39
+
29
40
  V2IP_UDP_PORT_VIDEO = 50020
30
41
  """Default destination UDP port for a V2IP video stream (firmware ``V2IP_UDP_PORT_VIDEO``)."""
31
42
 
@@ -5,6 +5,8 @@ __version__ = VERSION
5
5
  MX_BCAST_UDP_PORT: int
6
6
  MX_MCAST_UDP_IP: str
7
7
  MX_MCAST_UDP_PORT: int
8
+ MXR_HELLO_INTERVAL_MIN: float
9
+ MXR_HELLO_INTERVAL_RAND: float
8
10
  V2IP_UDP_PORT_VIDEO: int
9
11
  V2IP_UDP_PORT_ANC: int
10
12
  V2IP_UDP_PORT_AUDIO: int
@@ -651,6 +651,10 @@ class EdidProfile(IntEnum):
651
651
  return '4K HDR Dolby Atmos'
652
652
  if (self.value >= EdidProfile.SINK_1.value) and (self.value <= EdidProfile.SINK_32.value):
653
653
  return f'copy from sink #{self.value - EdidProfile.SINK_1.value + 1}'
654
+ if self.value == EdidProfile.UNKNOWN.value:
655
+ # decode_enum lands here for any value this build has no member for,
656
+ # so it is a profile we cannot name, not one somebody configured
657
+ return 'unknown'
654
658
  return f"custom #{self.value}"
655
659
 
656
660
  TEMPLATE_1080P_STEREO = 0
@@ -108,9 +108,6 @@ def _mxr_frame_factory(hdr:FrameHeader, timestamp:float) -> FrameBase|None:
108
108
  if hdr.opcode == 0x1F:
109
109
  from .FrameV2IPSourceSwitch import FrameV2IPSourceSwitch
110
110
  return FrameV2IPSourceSwitch(header=hdr, timestamp=timestamp)
111
- if hdr.opcode == 0x16:
112
- from .FramePDUState import FramePDUState
113
- return FramePDUState(header=hdr, timestamp=timestamp)
114
111
  if hdr.opcode == 0x20:
115
112
  from .FrameV2IPLink import FrameV2IPLinkStatus
116
113
  return FrameV2IPLinkStatus(header=hdr, timestamp=timestamp)
@@ -225,11 +222,21 @@ def _mxr_frame_factory(hdr:FrameHeader, timestamp:float) -> FrameBase|None:
225
222
 
226
223
  # Opcodes reaching here are ones current firmware does not emit:
227
224
  #
225
+ # 0x16 PDU_STATE dead; no current MatrixOS build
226
+ # supports it
228
227
  # 0x17..0x1E the CEC range never implemented
229
- # 0x25, 0x33 RESERVED_0 / RESERVED_1 retired; never reuse the numbers
228
+ # 0x25 SYS_BAY_V2IP_DETAILS retired 2024-09, replaced by 0x3C
230
229
  # 0x2D VIDEO_CLOCK_RATE_OLD superseded by clock_rate in 0x31
231
230
  # 0x2E, 0x2F V2IP_BLIST_* behind V2IP_SUPPORT_BLACKLIST, which
232
231
  # no project defines
232
+ # 0x33 RESERVED_1 was MX_OP_NB, the table's end sentinel
233
+ #
234
+ # Never reuse 0x16 or 0x25. Both carried a real payload - 0x16 a PDU's state,
235
+ # 0x25 v2ip_device_sources, with video, audio and anc at 0, 8 and 16,
236
+ # audio_return at 24 and source_clock at 32 - so a unit old enough to decode
237
+ # them reads anything reissued under those numbers as the old payload rather
238
+ # than ignoring it. 0x33 is reserved for a different reason: it was only ever
239
+ # the table's end marker, so no unit ever read a payload from it.
233
240
  #
234
241
  # Add decoders here rather than removing them: a unit on older firmware still
235
242
  # emits superseded opcodes, which is what 0x06, 0x36 and 0x47 are for. An
@@ -0,0 +1,53 @@
1
+ ######################################################
2
+ ## MX Remote Python Interface ##
3
+ ## ##
4
+ ## author: Lars Op den Kamp (lars@opdenkamp-it.nl) ##
5
+ ## copyright (c) 2021-2026 Op den Kamp IT Solutions ##
6
+ ######################################################
7
+ '''Protocol frame for EDID profile change commands.'''
8
+
9
+ from .FrameBase import FrameBase
10
+ from ..Interface import DeviceRegistry, DeviceBase, EdidProfile, MxrDeviceUid
11
+ from .Constants import decode_enum
12
+
13
+ class FrameEDIDProfile(FrameBase):
14
+ '''Change an EDID profile on a target device.'''
15
+ @staticmethod
16
+ def construct(mxr:DeviceRegistry, target:DeviceBase, profile:EdidProfile) -> FrameBase|None:
17
+ '''Build an EDID profile change frame for transmission.'''
18
+ payload = target.remote_id.byte_value + \
19
+ bytes([(profile.value >> 0) & 0xFF, (profile.value >> 8) & 0xFF]) + \
20
+ bytes([0 for _ in range(6)])
21
+ return FrameBase.construct_base(target=target, mxr=mxr, opcode=0x34, payload=payload)
22
+
23
+ @property
24
+ def target_uid(self) -> MxrDeviceUid|None:
25
+ '''Device whose EDID profile is being set.'''
26
+ return self.payload_uuid(0)
27
+
28
+ @property
29
+ def target_device(self) -> DeviceBase|None:
30
+ return self.mxr.get_by_uid(self.target_uid)
31
+
32
+ @property
33
+ def profile_raw(self) -> int|None:
34
+ '''Profile as carried on the wire, before mapping to a local one.'''
35
+ return self.payload_u16(16)
36
+
37
+ @property
38
+ def profile(self) -> EdidProfile|None:
39
+ '''Requested EDID profile.
40
+
41
+ A value this build has no member for reads as EdidProfile.UNKNOWN rather
42
+ than raising or being folded into a real profile. Read profile_raw when
43
+ the distinction matters.
44
+ '''
45
+ return decode_enum(EdidProfile, self.profile_raw)
46
+
47
+ def __str__(self) -> str:
48
+ # The frame names no bay because it never needs to: the receiver applies it
49
+ # to MBAY_ID_0, its own input bay, so a device is the whole address.
50
+ target = self.target_device if (self.target_device is not None) else self.target_uid
51
+ profile = f"{self.profile} ({self.profile_raw})" \
52
+ if (self.profile == EdidProfile.UNKNOWN) else str(self.profile)
53
+ return f"set edid profile: {target} input 0 -> {profile}"
@@ -0,0 +1,15 @@
1
+ from ..Interface import DeviceBase as DeviceBase, DeviceRegistry as DeviceRegistry, EdidProfile as EdidProfile, MxrDeviceUid as MxrDeviceUid
2
+ from .Constants import decode_enum as decode_enum
3
+ from .FrameBase import FrameBase as FrameBase
4
+
5
+ class FrameEDIDProfile(FrameBase):
6
+ @staticmethod
7
+ def construct(mxr: DeviceRegistry, target: DeviceBase, profile: EdidProfile) -> FrameBase | None: ...
8
+ @property
9
+ def target_uid(self) -> MxrDeviceUid | None: ...
10
+ @property
11
+ def target_device(self) -> DeviceBase | None: ...
12
+ @property
13
+ def profile_raw(self) -> int | None: ...
14
+ @property
15
+ def profile(self) -> EdidProfile | None: ...
@@ -0,0 +1,62 @@
1
+ ######################################################
2
+ ## MX Remote Python Interface ##
3
+ ## ##
4
+ ## author: Lars Op den Kamp (lars@opdenkamp-it.nl) ##
5
+ ## copyright (c) 2021-2026 Op den Kamp IT Solutions ##
6
+ ######################################################
7
+ '''Protocol frame for changing a bay name on a device.'''
8
+
9
+ from .Constants import MXR_DEVICE_NAME_LEN
10
+ from .FrameBase import FrameBase
11
+ from ..Interface import DeviceRegistry, BayBase, DeviceBase, MxrDeviceUid
12
+
13
+ class FrameSetName(FrameBase):
14
+ '''Change a bay name.'''
15
+ @staticmethod
16
+ def construct(mxr:DeviceRegistry, target:BayBase, name:str) -> FrameBase|None:
17
+ '''Build a bay name change frame for transmission.'''
18
+ # 15, not 16: the receiver copies MXR_DEVICE_NAME_LEN - 1 bytes and
19
+ # terminates, so a name filling the field is stored one short. Sending
20
+ # all 16 means the bay ends up named something other than what was
21
+ # asked for, with success reported either way.
22
+ name = name[:MXR_DEVICE_NAME_LEN - 1]
23
+ name_bytes = name.encode(encoding='ascii', errors='replace').ljust(MXR_DEVICE_NAME_LEN, b'\x00')
24
+ payload = target.device.remote_id.byte_value + \
25
+ bytes([(target.port >> 0) & 0xFF, (target.port >> 8) & 0xFF]) + \
26
+ name_bytes
27
+ return FrameBase.construct_base(target=target, mxr=mxr, opcode=0x22, protocol=0x11, payload=payload)
28
+
29
+ @property
30
+ def target_uid(self) -> MxrDeviceUid|None:
31
+ '''Device whose bay is being renamed.'''
32
+ return self.payload_uuid(0)
33
+
34
+ @property
35
+ def target_device(self) -> DeviceBase|None:
36
+ return self.mxr.get_by_uid(self.target_uid)
37
+
38
+ @property
39
+ def port(self) -> int|None:
40
+ '''Port id of the bay being renamed (mbay_port_id, a u16).'''
41
+ return self.payload_u16(16)
42
+
43
+ @property
44
+ def bay(self) -> BayBase|None:
45
+ return self.payload_bay(device=self.target_device, idx=16, u16=True)
46
+
47
+ @property
48
+ def name(self) -> str|None:
49
+ '''The new name, read from its fixed 16-byte field.
50
+
51
+ The field carries no terminator of its own, so a name that fills it has
52
+ none and must not be read as a C string: read it at its width. A sender
53
+ that fills all MXR_DEVICE_NAME_LEN bytes is readable here, even though a
54
+ receiver keeps only the first MXR_DEVICE_NAME_LEN - 1 of them.
55
+ '''
56
+ return self.payload_str(18, 16)
57
+
58
+ def __str__(self) -> str:
59
+ target = self.bay if (self.bay is not None) else self.target_device
60
+ if (target is None):
61
+ return f"set bay name: port {self.port} -> '{self.name}'"
62
+ return f"set bay name: {target} -> '{self.name}'"
@@ -0,0 +1,17 @@
1
+ from ..Interface import BayBase as BayBase, DeviceBase as DeviceBase, DeviceRegistry as DeviceRegistry, MxrDeviceUid as MxrDeviceUid
2
+ from .Constants import MXR_DEVICE_NAME_LEN as MXR_DEVICE_NAME_LEN
3
+ from .FrameBase import FrameBase as FrameBase
4
+
5
+ class FrameSetName(FrameBase):
6
+ @staticmethod
7
+ def construct(mxr: DeviceRegistry, target: BayBase, name: str) -> FrameBase | None: ...
8
+ @property
9
+ def target_uid(self) -> MxrDeviceUid | None: ...
10
+ @property
11
+ def target_device(self) -> DeviceBase | None: ...
12
+ @property
13
+ def port(self) -> int | None: ...
14
+ @property
15
+ def bay(self) -> BayBase | None: ...
16
+ @property
17
+ def name(self) -> str | None: ...
@@ -14,10 +14,11 @@ import aiofiles
14
14
  import aiohttp
15
15
  import os
16
16
  from pathlib import Path
17
+ import random
17
18
  import time
18
19
 
19
20
  from .ConnectionAsync import ConnectionAsync
20
- from ..const import __version__
21
+ from ..const import MXR_HELLO_INTERVAL_MIN, MXR_HELLO_INTERVAL_RAND, __version__
21
22
  from .Device import Device
22
23
  from ..Interface import ConnectionCallbacks, DeviceRegistry, MxrDeviceUid, BayLinks, BayBase, DeviceBase, MxrCallbacks, AudioEndpoint, mxr_broadcast_address
23
24
  from ..proto.Constants import MXR_PROTOCOL_VERSION
@@ -60,7 +61,8 @@ class Remote(DeviceRegistry, ConnectionCallbacks):
60
61
  self._callbacks = State(callbacks, http_session)
61
62
  self.remotes:dict[MxrDeviceUid,Device] = {}
62
63
  self._links = BayLinks(self)
63
- self._last_hello = 0
64
+ self._hello_due = 0.0
65
+ self._probe_failures = 0
64
66
  self._tasks:set[asyncio.Task[None]] = set()
65
67
  self._uid:bytes|None = None
66
68
  self._uid_path = uid_path
@@ -218,19 +220,56 @@ class Remote(DeviceRegistry, ConnectionCallbacks):
218
220
  return True
219
221
  return False
220
222
 
223
+ def _on_task_done(self, task:'asyncio.Task[None]') -> None:
224
+ '''Drop a finished background task, and report it if something killed it.
225
+
226
+ asyncio only surfaces an unretrieved task exception when the task object is
227
+ garbage collected, which is a different moment and may never come, so a task
228
+ that dies otherwise does so in silence. Retrieving it here is what makes any
229
+ future background task fail loudly, including one whose own guard misses.
230
+ '''
231
+ self._tasks.discard(task)
232
+ if task.cancelled():
233
+ return
234
+ exc = task.exception()
235
+ if (exc is not None):
236
+ _LOGGER.error(f"background task {task.get_name()} died: {exc!r}")
237
+
221
238
  async def _background_probe(self) -> None:
222
239
  while not self._closing:
223
240
  await asyncio.sleep(1)
224
- tx_discover = False
225
- if (not self.has_completed_devices()):
226
- tx_discover = True
227
- else:
228
- for _, device in self.remotes.items():
229
- device.check_online()
230
- if not device.check_configuration_complete_timeout():
231
- tx_discover = True
232
- if tx_discover and ((time.time() - self._discover_timeout) >= 5):
233
- self.tx_discover()
241
+ try:
242
+ self._probe_once()
243
+ self._probe_failures = 0
244
+ except Exception as e:
245
+ # A send raises on a closed or unreachable socket. Letting that
246
+ # escape ends the task, and this loop is the only thing that
247
+ # announces this client and drives discovery, so one transient
248
+ # failure would silence it permanently.
249
+ #
250
+ # A disconnected interface fails every tick, so only the first
251
+ # carries a traceback: a full one per second buries everything
252
+ # else in the log, and the repeats say nothing the first did not.
253
+ self._probe_failures += 1
254
+ if (self._probe_failures == 1):
255
+ _LOGGER.warning(f"probe failed: {traceback.format_exc()}")
256
+ else:
257
+ _LOGGER.debug(f"probe still failing ({self._probe_failures}): {e!r}")
258
+
259
+ def _probe_once(self) -> None:
260
+ '''One pass of the probe loop: announce if due, then discover if needed.'''
261
+ if (time.time() >= self._hello_due):
262
+ self.tx_hello()
263
+ tx_discover = False
264
+ if (not self.has_completed_devices()):
265
+ tx_discover = True
266
+ else:
267
+ for _, device in self.remotes.items():
268
+ device.check_online()
269
+ if not device.check_configuration_complete_timeout():
270
+ tx_discover = True
271
+ if tx_discover and ((time.time() - self._discover_timeout) >= 5):
272
+ self.tx_discover()
234
273
 
235
274
  async def start_async(self) -> None:
236
275
  '''Start the server that listens for mx_remote frames from other devices.'''
@@ -240,7 +279,7 @@ class Remote(DeviceRegistry, ConnectionCallbacks):
240
279
  await self.conn.start_srv()
241
280
  checker = asyncio.create_task(self._background_probe())
242
281
  self._tasks.add(checker)
243
- checker.add_done_callback(self._tasks.discard)
282
+ checker.add_done_callback(self._on_task_done)
244
283
 
245
284
  async def stop_async(self) -> None:
246
285
  '''Stop the server and close all connections.'''
@@ -335,14 +374,32 @@ class Remote(DeviceRegistry, ConnectionCallbacks):
335
374
  _LOGGER.debug("discovering devices")
336
375
  return self.transmit(pkt.frame)
337
376
 
377
+ def _arm_hello(self) -> None:
378
+ '''Schedule the next announcement, mirroring the firmware's hello timer.
379
+
380
+ MatrixOS re-draws min 2.5s + random(0..2.5s) after each hello it sends,
381
+ so a mesh of devices does not synchronise into bursts. The interval is
382
+ re-drawn per send rather than fixed for the same reason.
383
+ '''
384
+ self._hello_due = time.time() + MXR_HELLO_INTERVAL_MIN \
385
+ + (random.random() * MXR_HELLO_INTERVAL_RAND)
386
+
338
387
  def tx_hello(self) -> int:
339
388
  '''Transmit a hello frame to announce this device on the network.'''
340
389
  pkt = constructFrameHello(self)
341
390
  if (pkt is None):
342
391
  return 0
343
392
  _LOGGER.debug("sending hello")
344
- self._last_hello = time.time()
345
- return self.transmit(pkt.frame)
393
+ sent = self.transmit(pkt.frame)
394
+ if (sent > 0):
395
+ # Only a hello that went out buys the next interval. A send that wrote
396
+ # nothing leaves _hello_due where it was, so the next probe tick tries
397
+ # again, which is what firmware does - it resets hello_timeout only in
398
+ # the success branch of mxr_transmit. Arming here on failure instead
399
+ # would silence a client for a full interval every time a write failed,
400
+ # and on an interface that never writes, permanently.
401
+ self._arm_hello()
402
+ return sent
346
403
 
347
404
  def on_connection_made(self) -> None:
348
405
  '''Callback invoked after ConnectionAsync has started the server.'''
@@ -374,10 +431,6 @@ class Remote(DeviceRegistry, ConnectionCallbacks):
374
431
  timestamp = time.time()
375
432
  self.process_frame(timestamp=timestamp, data=data, addr=addr)
376
433
 
377
- if (self.conn is not None):
378
- if (timestamp - self._last_hello >= 30):
379
- self.tx_hello()
380
-
381
434
  @override
382
435
  def on_mxr_update(self, data:Any) -> None:
383
436
  if isinstance(data, FrameHello):
@@ -1,7 +1,7 @@
1
1
  import aiohttp
2
2
  from ..Interface import AudioEndpoint as AudioEndpoint, BayBase as BayBase, BayLinks as BayLinks, ConnectionCallbacks as ConnectionCallbacks, DeviceBase as DeviceBase, DeviceRegistry as DeviceRegistry, MxrCallbacks as MxrCallbacks, mxr_broadcast_address as mxr_broadcast_address
3
3
  from ..Uid import MxrDeviceUid as MxrDeviceUid
4
- from ..const import MX_BCAST_UDP_PORT as MX_BCAST_UDP_PORT, MX_MCAST_UDP_IP as MX_MCAST_UDP_IP, MX_MCAST_UDP_PORT as MX_MCAST_UDP_PORT, __version__ as __version__
4
+ from ..const import MXR_HELLO_INTERVAL_MIN as MXR_HELLO_INTERVAL_MIN, MXR_HELLO_INTERVAL_RAND as MXR_HELLO_INTERVAL_RAND, MX_BCAST_UDP_PORT as MX_BCAST_UDP_PORT, MX_MCAST_UDP_IP as MX_MCAST_UDP_IP, MX_MCAST_UDP_PORT as MX_MCAST_UDP_PORT, __version__ as __version__
5
5
  from ..proto.Constants import MXR_PROTOCOL_VERSION as MXR_PROTOCOL_VERSION
6
6
  from ..proto.Factory import process_mxr_frame as process_mxr_frame
7
7
  from ..proto.FrameDiscover import constructFrameDiscover as constructFrameDiscover
@@ -0,0 +1,59 @@
1
+ """Enter where the runtime enters.
2
+
3
+ on_datagram_received is what ConnectionAsync calls. process_frame is one level
4
+ below it and is what a test reaches for, because it takes the arguments a test
5
+ already has - so a layer that only the runtime touches goes uncovered while the
6
+ suite looks like it exercises the receive path.
7
+
8
+ Here that layer re-announces this client's hello. It has one other caller, at
9
+ startup, so if it stopped working the client would announce once and then go
10
+ silent to every peer that started later.
11
+ """
12
+ import os
13
+ import struct
14
+ import sys
15
+ import time
16
+
17
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
18
+ import logging
19
+ logging.disable(logging.CRITICAL)
20
+
21
+ import mx_remote
22
+ from mx_remote.Uid import MxrDeviceUid
23
+ from mx_remote.proto.Factory import create_mxr_frame
24
+
25
+ UID = bytes(range(1, 17))
26
+ ADDR = ('192.0.2.9', 8812)
27
+
28
+ class RecordingConn:
29
+ """Stands in for ConnectionAsync so transmits can be counted."""
30
+ def __init__(self):
31
+ self.sent = []
32
+ def transmit(self, data):
33
+ self.sent.append(data)
34
+ return len(data)
35
+
36
+ def nm(s, sz=16):
37
+ b = s.encode('ascii')[:sz]
38
+ return b + bytes(sz - len(b))
39
+
40
+ HELLO = (struct.pack('<H', 0x28) + nm('MX-1') + nm('P8SN12345678') + nm('4.7.9')
41
+ + struct.pack('<I', 1 << 5))
42
+
43
+ mx = mx_remote.Remote(open_connection=False)
44
+ mx._uid = bytes(range(100, 116))
45
+ conn = RecordingConn()
46
+ mx.conn = conn
47
+
48
+ # a datagram arriving must be decoded, not merely counted
49
+ mx.on_datagram_received(create_mxr_frame(UID, 0x00, HELLO), ADDR)
50
+ assert mx.get_by_uid(MxrDeviceUid(UID)) is not None, 'the datagram was not processed'
51
+ print('entry : a datagram reaching on_datagram_received is decoded')
52
+
53
+ # Hello timing is not asserted here. It belongs to whatever drives the
54
+ # announcement, and pinning it to a datagram arriving would encode the trigger
55
+ # rather than the behaviour - an assertion that no datagram sends a hello
56
+ # passes trivially once nothing does.
57
+
58
+ print()
59
+ print('ALL OK')
@@ -32,26 +32,54 @@ import mx_remote.proto as proto_pkg # noqa: E402
32
32
  from mx_remote.proto.FrameBase import FrameBase # noqa: E402
33
33
 
34
34
  BASELINE = {
35
+ 'FrameAmpDolbySettings.FrameAmpDolbySettings',
35
36
  'FrameAmpZoneSettings.FrameAmpZoneSettings',
36
37
  'FrameAudioClip.FrameAudioClip',
37
38
  'FrameAudioSetRoute.FrameAudioSetRoute',
38
39
  'FrameBayConfig.FrameBayConfig',
40
+ 'FrameBayConfigSecondary.FrameBayConfigSecondary',
41
+ 'FrameBayHide.FrameBayHide',
39
42
  'FrameBayStatus.FrameBayStatus',
43
+ 'FrameConnectStatus.FrameConnectStatus',
44
+ 'FrameDebug.FrameDebug',
45
+ 'FrameFilterStatus.FrameFilterStatus',
40
46
  'FrameFirmwareVersion.FrameFirmwareVersion',
41
47
  'FrameHello.FrameHello',
42
48
  'FrameLinks.FrameLinks',
49
+ 'FrameMeshOperation.FrameMeshOperation',
50
+ 'FrameMirrorStatus.FrameMirrorStatus',
51
+ 'FrameNetworkStatus.FrameNetworkStatus',
52
+ 'FramePowerChange.FramePowerChange',
43
53
  'FrameRCAction.FrameRCAction',
44
54
  'FrameRCKey.FrameRCKey',
55
+ 'FrameRCSettings.FrameRCSettings',
45
56
  'FrameRoutingChange.FrameRoutingChange',
57
+ 'FrameSignalStatus.FrameSignalStatus',
46
58
  'FrameSignalStatusNew.FrameSignalStatusNew',
59
+ 'FrameSysTemperature.FrameSysTemperature',
60
+ 'FrameSystemStatus.FrameSystemStatus',
47
61
  'FrameTXRCAction.FrameTXRCAction',
48
62
  'FrameTXRCKey.FrameTXRCKey',
63
+ 'FrameTopology.FrameTopology',
64
+ 'FrameV2IPAudio.FrameV2IPAudio',
65
+ 'FrameV2IPAudio.FrameV2IPAudioChangeSource',
66
+ 'FrameV2IPAudio.FrameV2IPAudioConfig',
67
+ 'FrameV2IPAudio.FrameV2IPAudioLinks',
68
+ 'FrameV2IPBayMapping.FrameV2IPBayMapping',
49
69
  'FrameV2IPDetectBays.FrameV2IPDetectBays',
50
70
  'FrameV2IPDeviceConfiguration.FrameV2IPDeviceConfiguration',
71
+ 'FrameV2IPManualSourceSwitch.FrameV2IPManualSourceSwitch',
51
72
  'FrameV2IPMultiviewer.FrameV2IPMultiviewer',
73
+ 'FrameV2IPPowerSave.FrameV2IPPowerSave',
74
+ 'FrameV2IPSourceSwitch.FrameV2IPSourceSwitch',
52
75
  'FrameV2IPSources.FrameV2IPSources',
53
76
  'FrameV2IPStats.FrameV2IPStats',
77
+ 'FrameV2IPTiling.FrameV2IPTiling',
54
78
  'FrameV2IPVideoWall.FrameV2IPVideoWall',
79
+ 'FrameVolume.FrameVolume',
80
+ 'FrameVolumeDown.FrameVolumeDown',
81
+ 'FrameVolumeSet.FrameVolumeSet',
82
+ 'FrameVolumeUp.FrameVolumeUp',
55
83
  }
56
84
 
57
85
  def main() -> int: