mx-remote 6.3.0__tar.gz → 7.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 (252) hide show
  1. {mx_remote-6.3.0 → mx_remote-7.0.0}/PKG-INFO +1 -1
  2. {mx_remote-6.3.0 → mx_remote-7.0.0}/docs/api.md +46 -0
  3. {mx_remote-6.3.0 → mx_remote-7.0.0}/docs/oneip.md +67 -1
  4. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/Interface.py +228 -8
  5. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/Interface.pyi +72 -1
  6. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/const.py +1 -1
  7. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/Constants.py +105 -3
  8. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/Constants.pyi +51 -0
  9. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/Factory.py +6 -0
  10. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameBayStatus.py +36 -8
  11. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameBayStatus.pyi +6 -0
  12. mx_remote-7.0.0/mx_remote/proto/FrameUnitCommand.py +70 -0
  13. mx_remote-7.0.0/mx_remote/proto/FrameUnitCommand.pyi +20 -0
  14. mx_remote-7.0.0/mx_remote/proto/FrameUnitStatus.py +64 -0
  15. mx_remote-7.0.0/mx_remote/proto/FrameUnitStatus.pyi +15 -0
  16. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.py +52 -9
  17. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPDeviceConfiguration.pyi +4 -2
  18. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/remote/Device.py +120 -2
  19. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/remote/Device.pyi +17 -2
  20. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/remote/Remote.py +13 -4
  21. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/remote/Remote.pyi +1 -1
  22. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/handlers.py +1 -0
  23. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/mesh.py +8 -2
  24. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/proto_stamp.py +1 -0
  25. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/run.py +2 -0
  26. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/smoke.py +2 -1
  27. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/structs.py +20 -0
  28. mx_remote-7.0.0/tests/unit.py +181 -0
  29. mx_remote-7.0.0/tests/v2ipptp.py +93 -0
  30. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/v2ipsettings.py +53 -0
  31. {mx_remote-6.3.0 → mx_remote-7.0.0}/.gitignore +0 -0
  32. {mx_remote-6.3.0 → mx_remote-7.0.0}/AGENTS.md +0 -0
  33. {mx_remote-6.3.0 → mx_remote-7.0.0}/CLAUDE.md +0 -0
  34. {mx_remote-6.3.0 → mx_remote-7.0.0}/LICENSE +0 -0
  35. {mx_remote-6.3.0 → mx_remote-7.0.0}/README.md +0 -0
  36. {mx_remote-6.3.0 → mx_remote-7.0.0}/docs/README.md +0 -0
  37. {mx_remote-6.3.0 → mx_remote-7.0.0}/docs/_static/brand.css +0 -0
  38. {mx_remote-6.3.0 → mx_remote-7.0.0}/docs/_static/favicon.svg +0 -0
  39. {mx_remote-6.3.0 → mx_remote-7.0.0}/docs/_static/logo-dark.svg +0 -0
  40. {mx_remote-6.3.0 → mx_remote-7.0.0}/docs/_static/logo.svg +0 -0
  41. {mx_remote-6.3.0 → mx_remote-7.0.0}/docs/audio.md +0 -0
  42. {mx_remote-6.3.0 → mx_remote-7.0.0}/docs/callbacks.md +0 -0
  43. {mx_remote-6.3.0 → mx_remote-7.0.0}/docs/conf.py +0 -0
  44. {mx_remote-6.3.0 → mx_remote-7.0.0}/docs/configuration.md +0 -0
  45. {mx_remote-6.3.0 → mx_remote-7.0.0}/docs/devices-and-bays.md +0 -0
  46. {mx_remote-6.3.0 → mx_remote-7.0.0}/docs/diagnostics.md +0 -0
  47. {mx_remote-6.3.0 → mx_remote-7.0.0}/docs/index.md +0 -0
  48. {mx_remote-6.3.0 → mx_remote-7.0.0}/docs/multiviewer.md +0 -0
  49. {mx_remote-6.3.0 → mx_remote-7.0.0}/docs/requirements.txt +0 -0
  50. {mx_remote-6.3.0 → mx_remote-7.0.0}/docs/routing.md +0 -0
  51. {mx_remote-6.3.0 → mx_remote-7.0.0}/hatch_build.py +0 -0
  52. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/Uid.py +0 -0
  53. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/Uid.pyi +0 -0
  54. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/__init__.py +0 -0
  55. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/__init__.pyi +0 -0
  56. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/compat.py +0 -0
  57. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/compat.pyi +0 -0
  58. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/const.pyi +0 -0
  59. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/deprecated.py +0 -0
  60. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/deprecated.pyi +0 -0
  61. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/main.py +0 -0
  62. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/main.pyi +0 -0
  63. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/BayConfig.py +0 -0
  64. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/BayConfig.pyi +0 -0
  65. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/Data.py +0 -0
  66. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/Data.pyi +0 -0
  67. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/Factory.pyi +0 -0
  68. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameAmpDolbySettings.py +0 -0
  69. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameAmpDolbySettings.pyi +0 -0
  70. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameAmpZoneSettings.py +0 -0
  71. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameAmpZoneSettings.pyi +0 -0
  72. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameAudioClip.py +0 -0
  73. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameAudioClip.pyi +0 -0
  74. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameAudioSetRoute.py +0 -0
  75. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameAudioSetRoute.pyi +0 -0
  76. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameBase.py +0 -0
  77. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameBase.pyi +0 -0
  78. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameBayConfig.py +0 -0
  79. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameBayConfig.pyi +0 -0
  80. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameBayConfigSecondary.py +0 -0
  81. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameBayConfigSecondary.pyi +0 -0
  82. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameBayHide.py +0 -0
  83. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameBayHide.pyi +0 -0
  84. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameConnectStatus.py +0 -0
  85. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameConnectStatus.pyi +0 -0
  86. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameDebug.py +0 -0
  87. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameDebug.pyi +0 -0
  88. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameDiscover.py +0 -0
  89. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameDiscover.pyi +0 -0
  90. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameEDID.py +0 -0
  91. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameEDID.pyi +0 -0
  92. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameEDIDProfile.py +0 -0
  93. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameEDIDProfile.pyi +0 -0
  94. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameFactoryReset.py +0 -0
  95. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameFactoryReset.pyi +0 -0
  96. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameFilterStatus.py +0 -0
  97. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameFilterStatus.pyi +0 -0
  98. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameFirmwareVersion.py +0 -0
  99. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameFirmwareVersion.pyi +0 -0
  100. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameHeader.py +0 -0
  101. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameHeader.pyi +0 -0
  102. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameHello.py +0 -0
  103. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameHello.pyi +0 -0
  104. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameLinks.py +0 -0
  105. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameLinks.pyi +0 -0
  106. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameMeshOperation.py +0 -0
  107. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameMeshOperation.pyi +0 -0
  108. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameMirrorStatus.py +0 -0
  109. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameMirrorStatus.pyi +0 -0
  110. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameMonitoringPulse.py +0 -0
  111. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameMonitoringPulse.pyi +0 -0
  112. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameNetworkStatus.py +0 -0
  113. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameNetworkStatus.pyi +0 -0
  114. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FramePing.py +0 -0
  115. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FramePing.pyi +0 -0
  116. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FramePowerChange.py +0 -0
  117. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FramePowerChange.pyi +0 -0
  118. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameRCAction.py +0 -0
  119. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameRCAction.pyi +0 -0
  120. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameRCIr.py +0 -0
  121. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameRCIr.pyi +0 -0
  122. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameRCKey.py +0 -0
  123. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameRCKey.pyi +0 -0
  124. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameRCSettings.py +0 -0
  125. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameRCSettings.pyi +0 -0
  126. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameReboot.py +0 -0
  127. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameReboot.pyi +0 -0
  128. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameRoutingChange.py +0 -0
  129. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameRoutingChange.pyi +0 -0
  130. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameSetInstaller.py +0 -0
  131. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameSetInstaller.pyi +0 -0
  132. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameSetName.py +0 -0
  133. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameSetName.pyi +0 -0
  134. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameSetRoute.py +0 -0
  135. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameSetRoute.pyi +0 -0
  136. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameSetupStatus.py +0 -0
  137. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameSetupStatus.pyi +0 -0
  138. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameSignalStatus.py +0 -0
  139. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameSignalStatus.pyi +0 -0
  140. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameSignalStatusNew.py +0 -0
  141. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameSignalStatusNew.pyi +0 -0
  142. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameSysTemperature.py +0 -0
  143. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameSysTemperature.pyi +0 -0
  144. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameSystemStatus.py +0 -0
  145. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameSystemStatus.pyi +0 -0
  146. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameTXRCAction.py +0 -0
  147. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameTXRCAction.pyi +0 -0
  148. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameTXRCKey.py +0 -0
  149. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameTXRCKey.pyi +0 -0
  150. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameTime.py +0 -0
  151. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameTime.pyi +0 -0
  152. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameTimeZone.py +0 -0
  153. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameTimeZone.pyi +0 -0
  154. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameTopology.py +0 -0
  155. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameTopology.pyi +0 -0
  156. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameTxIR.py +0 -0
  157. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameTxIR.pyi +0 -0
  158. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameUpgradeFPGA.py +0 -0
  159. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameUpgradeFPGA.pyi +0 -0
  160. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPAudio.py +0 -0
  161. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPAudio.pyi +0 -0
  162. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPBayMapping.py +0 -0
  163. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPBayMapping.pyi +0 -0
  164. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPDetectBays.py +0 -0
  165. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPDetectBays.pyi +0 -0
  166. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPLink.py +0 -0
  167. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPLink.pyi +0 -0
  168. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.py +0 -0
  169. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPManualSourceSwitch.pyi +0 -0
  170. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPMultiviewer.py +0 -0
  171. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPMultiviewer.pyi +0 -0
  172. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPPowerSave.py +0 -0
  173. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPPowerSave.pyi +0 -0
  174. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPSetMaster.py +0 -0
  175. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPSetMaster.pyi +0 -0
  176. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPSettingsAll.py +0 -0
  177. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPSettingsAll.pyi +0 -0
  178. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPSourceSwitch.py +0 -0
  179. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPSourceSwitch.pyi +0 -0
  180. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPSources.py +0 -0
  181. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPSources.pyi +0 -0
  182. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPStats.py +0 -0
  183. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPStats.pyi +0 -0
  184. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPTestcard.py +0 -0
  185. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPTestcard.pyi +0 -0
  186. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPTiling.py +0 -0
  187. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPTiling.pyi +0 -0
  188. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPVideoWall.py +0 -0
  189. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameV2IPVideoWall.pyi +0 -0
  190. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameVolume.py +0 -0
  191. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameVolume.pyi +0 -0
  192. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameVolumeDown.py +0 -0
  193. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameVolumeDown.pyi +0 -0
  194. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameVolumeSet.py +0 -0
  195. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameVolumeSet.pyi +0 -0
  196. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameVolumeUp.py +0 -0
  197. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/FrameVolumeUp.pyi +0 -0
  198. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/LinkConfig.py +0 -0
  199. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/LinkConfig.pyi +0 -0
  200. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/Multiviewer.py +0 -0
  201. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/Multiviewer.pyi +0 -0
  202. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/Svd.py +0 -0
  203. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/Svd.pyi +0 -0
  204. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/V2IPConfig.py +0 -0
  205. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/V2IPConfig.pyi +0 -0
  206. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/V2IPStats.py +0 -0
  207. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/V2IPStats.pyi +0 -0
  208. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/__init__.py +0 -0
  209. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/__init__.pyi +0 -0
  210. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/proto/svd.csv +0 -0
  211. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/py.typed +0 -0
  212. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/remote/Bay.py +0 -0
  213. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/remote/Bay.pyi +0 -0
  214. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/remote/ConnectionAsync.py +0 -0
  215. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/remote/ConnectionAsync.pyi +0 -0
  216. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/remote/Link.py +0 -0
  217. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/remote/Link.pyi +0 -0
  218. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/remote/State.py +0 -0
  219. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/remote/State.pyi +0 -0
  220. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/remote/V2IP.py +0 -0
  221. {mx_remote-6.3.0 → mx_remote-7.0.0}/mx_remote/remote/V2IP.pyi +0 -0
  222. {mx_remote-6.3.0 → mx_remote-7.0.0}/pyproject.toml +0 -0
  223. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/audio_ir.py +0 -0
  224. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/audiolock.py +0 -0
  225. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/capture.py +0 -0
  226. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/complete.py +0 -0
  227. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/deprecated.py +0 -0
  228. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/e2e.py +0 -0
  229. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/entry.py +0 -0
  230. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/handlers_cover.py +0 -0
  231. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/hello.py +0 -0
  232. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/idempotent.py +0 -0
  233. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/lengths.py +0 -0
  234. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/merge.py +0 -0
  235. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/newops.py +0 -0
  236. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/offline.py +0 -0
  237. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/offsets.py +0 -0
  238. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/ping.py +0 -0
  239. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/protogate.py +0 -0
  240. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/rcsettings.py +0 -0
  241. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/roundtrip.py +0 -0
  242. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/sources.py +0 -0
  243. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/staterequest.py +0 -0
  244. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/stats.py +0 -0
  245. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/txresult.py +0 -0
  246. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/unknowns.py +0 -0
  247. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/v2ipscaling.py +0 -0
  248. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/v2ipsubject.py +0 -0
  249. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/v2iptestcard.py +0 -0
  250. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/v2ipvlan.py +0 -0
  251. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/vdetails.py +0 -0
  252. {mx_remote-6.3.0 → mx_remote-7.0.0}/tests/wirefix.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: mx_remote
3
- Version: 6.3.0
3
+ Version: 7.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/
@@ -94,6 +94,52 @@ bit, and the range of the infrared profiles.
94
94
  .. autodata:: mx_remote.proto.Constants.ONEIP_VLAN_ID_MAX
95
95
  ```
96
96
 
97
+ ## OneIP PTP
98
+
99
+ A device's PTP settings, which it reports and a controller writes, and the PTP
100
+ state only the device reports.
101
+
102
+ ```{eval-rst}
103
+ .. autoclass:: mx_remote.OneIPPtpState
104
+ :members:
105
+
106
+ .. autoclass:: mx_remote.proto.Constants.OneIPPtpMode
107
+ :members:
108
+
109
+ .. autoclass:: mx_remote.proto.Constants.OneIPPtpFlag
110
+ :members:
111
+
112
+ .. autoclass:: mx_remote.proto.Constants.OneIPPtpLock
113
+ :members:
114
+
115
+ .. autodata:: mx_remote.proto.Constants.ONEIP_DEVICE_SETTINGS_PTP
116
+
117
+ .. autodata:: mx_remote.proto.Constants.ONEIP_PTP_DOMAIN_MAX
118
+ ```
119
+
120
+ ## Unit status and commands
121
+
122
+ A unit's health and IP configuration, and the commands sent to one unit.
123
+
124
+ ```{eval-rst}
125
+ .. autoclass:: mx_remote.UnitStatus
126
+ :members:
127
+
128
+ .. autoclass:: mx_remote.UnitNetConfig
129
+ :members:
130
+
131
+ .. autoclass:: mx_remote.proto.Constants.UnitFlag
132
+ :members:
133
+
134
+ .. autoclass:: mx_remote.proto.Constants.UnitCommandKind
135
+ :members:
136
+
137
+ .. autoclass:: mx_remote.proto.Constants.UnitCommandFlag
138
+ :members:
139
+
140
+ .. autodata:: mx_remote.proto.Constants.UNIT_MAX_TEMPERATURES
141
+ ```
142
+
97
143
  ## OneIP test card
98
144
 
99
145
  A sink's test pattern, tone and lip-sync flash, and the ranges it accepts.
@@ -222,6 +222,35 @@ goes out at 176 bytes: a receiver with the schedule ignores the settings of a
222
222
  shorter frame, and one whose settings end at the idle minutes takes it all the
223
223
  same.
224
224
 
225
+ ### PTP
226
+
227
+ A device's PTP mode, domain and grandmaster priority1 are settings like the
228
+ others, each behind its own bit.
229
+
230
+ ```python
231
+ from mx_remote import OneIPPtpMode
232
+
233
+ print(settings.ptp_mode, settings.ptp_domain, settings.ptp_priority1)
234
+
235
+ await device.set_oneip_ptp_mode(OneIPPtpMode.AUTO) # follow, or lead when no one does
236
+ await device.set_oneip_ptp_domain(0) # up to ONEIP_PTP_DOMAIN_MAX
237
+ await device.set_oneip_ptp_priority1(128) # lower wins
238
+ ```
239
+
240
+ A mode this library does not name and a domain above `ONEIP_PTP_DOMAIN_MAX` are
241
+ refused before they are sent, and not cached from another controller's write.
242
+
243
+ What the device's clock is actually doing is its PTP state, which only the
244
+ device reports about itself and nothing writes:
245
+
246
+ ```python
247
+ ptp = device.oneip_ptp # None: no report yet, no PTP, or in power save
248
+ if ptp is not None:
249
+ print(ptp.mode, ptp.domain, ptp.lock, ptp.port_state) # port_state per IEEE 1588
250
+ print(ptp.followed) # locked to a master since PTP started
251
+ print(ptp.grandmaster) # its clock identity, None while there is none
252
+ ```
253
+
225
254
  ### VLAN
226
255
 
227
256
  A device that announces `DeviceFeature.VLAN` tags its uplink by a VLAN
@@ -300,7 +329,44 @@ await mx.set_all_oneip_device_settings(OneIPDeviceSettings(
300
329
  Nothing is cached from it: each device that applies a change reports its
301
330
  settings, and its `oneip_settings` reads that. What every device would ignore is
302
331
  refused rather than sent - no setting at all, one only a device reports about
303
- itself, a profile out of range, or a schedule time that is not a time of day.
332
+ itself, a profile, PTP mode or PTP domain out of range, or a schedule time that
333
+ is not a time of day.
334
+
335
+ ## Unit status and commands
336
+
337
+ A unit on protocol 0x2C reports its health and IP configuration with its
338
+ periodic broadcast, and within a few seconds of a change to its flags, status
339
+ or addresses.
340
+
341
+ ```python
342
+ from mx_remote import UnitFlag
343
+
344
+ status = device.unit_status # None until the unit has reported
345
+ print(status.uptime, status.boot_count, status.reboot_count)
346
+ print(status.network) # address, netmask, gateway, two DNS servers
347
+ print(status.fan_rpm, status.temperatures, status.status_message)
348
+ print(UnitFlag.DHCP in status.flags, status.network_pending)
349
+ ```
350
+
351
+ A unit takes a command only from its mesh controller or a management
352
+ application, which this client announces itself as:
353
+
354
+ ```python
355
+ from mx_remote import UnitNetConfig
356
+
357
+ await device.clear_unit_status()
358
+ await device.upgrade_unit_video_processor(force=False)
359
+ await device.set_unit_network(UnitNetConfig(
360
+ ip='10.1.2.3', netmask='255.255.255.0', gateway='10.1.2.254', dns1='10.1.2.254'))
361
+ await device.confirm_unit_network() # keep it, once the unit reports it
362
+ await device.set_unit_dhcp()
363
+ ```
364
+
365
+ A changed IP configuration applies at once and reverts unless the mesh
366
+ controller confirms it in time; `status.network_pending` says whether it still
367
+ waits. Each command returns False without sending for a device that is not a
368
+ OneIP unit, one below 0x2C, or an address and netmask a unit cannot hold.
369
+ Nothing acknowledges a command: `unit_status` reads what the unit reports next.
304
370
 
305
371
  ## Mesh and firmware
306
372
 
@@ -42,7 +42,7 @@ def mxr_valid_addresses() -> list[str]:
42
42
  Get the list of valid local_ip addresses that can be used
43
43
 
44
44
  Returns:
45
- addresses (list[str]): list of IP addressses that can be used for the local_ip parameter
45
+ list[str]: the IP addresses that can be used for the local_ip parameter
46
46
  """
47
47
  addresses:list[str] = []
48
48
  for iface in netifaces.interfaces():
@@ -1946,6 +1946,133 @@ def _vlan_port_index(wire:int) -> int|None:
1946
1946
  '''A port index from its 1-based wire form.'''
1947
1947
  return (wire - 1) if (1 <= wire <= ONEIP_VLAN_PORTS) else None
1948
1948
 
1949
+ @dataclass(frozen=True)
1950
+ class UnitNetConfig:
1951
+ """An IP configuration: the addresses a unit uses, or is told to use."""
1952
+ ip: str = '0.0.0.0'
1953
+ """The address."""
1954
+ netmask: str = '0.0.0.0'
1955
+ """The netmask."""
1956
+ gateway: str = '0.0.0.0'
1957
+ """The gateway."""
1958
+ dns1: str = '0.0.0.0'
1959
+ """The primary DNS server."""
1960
+ dns2: str = '0.0.0.0'
1961
+ """The secondary DNS server."""
1962
+
1963
+ @property
1964
+ def addresses(self) -> tuple[str, str, str, str, str]:
1965
+ """The five addresses, in the order the wire carries them."""
1966
+ return (self.ip, self.netmask, self.gateway, self.dns1, self.dns2)
1967
+
1968
+ def is_valid(self) -> bool:
1969
+ """Whether every address parses, the address is one a unit can hold,
1970
+ and the netmask is a run of ones from the top."""
1971
+ try:
1972
+ for address in self.addresses:
1973
+ ipaddress.IPv4Address(address)
1974
+ ip = ipaddress.IPv4Address(self.ip)
1975
+ mask = int(ipaddress.IPv4Address(self.netmask))
1976
+ except ValueError:
1977
+ return False
1978
+ host = ~mask & 0xFFFFFFFF # all ones from the bottom for a valid netmask
1979
+ return not (ip.is_unspecified or ip.is_multicast or (int(ip) == 0xFFFFFFFF)) \
1980
+ and (mask != 0) and ((host & (host + 1)) == 0)
1981
+
1982
+ def __str__(self) -> str:
1983
+ return f"{self.ip}/{self.netmask} gw {self.gateway} dns {self.dns1} {self.dns2}"
1984
+
1985
+ @dataclass(frozen=True)
1986
+ class UnitStatus:
1987
+ """
1988
+ A unit's health and IP configuration, as it last reported them.
1989
+
1990
+ A unit reports this about itself with its periodic broadcast, and within a
1991
+ few seconds of a change to its flags, its status or its addresses.
1992
+ """
1993
+ flags: UnitFlag = UnitFlag(0)
1994
+ """The report's flags."""
1995
+ uptime: int = 0
1996
+ """Seconds since the unit booted."""
1997
+ boot_count: int|None = None
1998
+ """How often the unit has booted, None while it does not report it."""
1999
+ reboot_count: int = 0
2000
+ """How often the unit has rebooted."""
2001
+ encoder_health: int = 0
2002
+ """The encoder's health, a mask of the unit's encoder status bits."""
2003
+ decoder_health: int = 0
2004
+ """The decoder's health, a mask of the unit's decoder status and state bits."""
2005
+ network: UnitNetConfig = UnitNetConfig()
2006
+ """The IP configuration in use."""
2007
+ fan_rpm: int|None = None
2008
+ """The fan speed in RPM, None while the unit does not report it."""
2009
+ fan_duty: int = 0
2010
+ """The fan duty cycle, in percent."""
2011
+ status: int = 0
2012
+ """The unit's system status."""
2013
+ net_revert_s: int = 0
2014
+ """Seconds before a pending IP configuration is reverted."""
2015
+ temperatures: tuple[int, ...] = ()
2016
+ """Temperatures in degrees Celsius, at most UNIT_MAX_TEMPERATURES of them."""
2017
+ status_message: str = ''
2018
+ """The message of the current system status."""
2019
+
2020
+ @property
2021
+ def network_pending(self) -> bool:
2022
+ """Whether an IP configuration changed through the mesh waits for the
2023
+ mesh controller to confirm it."""
2024
+ return UnitFlag.NET_PENDING in self.flags
2025
+
2026
+ def __str__(self) -> str:
2027
+ return (f"up {self.uptime}s boots {self.boot_count} reboots {self.reboot_count} "
2028
+ f"encoder {self.encoder_health:#x} decoder {self.decoder_health:#x} net [{self.network}] "
2029
+ f"fan {self.fan_rpm} rpm {self.fan_duty}% status {self.status} '{self.status_message}' "
2030
+ f"temperatures {list(self.temperatures)} flags {self.flags!r}")
2031
+
2032
+ @dataclass(frozen=True)
2033
+ class OneIPPtpState:
2034
+ """
2035
+ A OneIP device's PTP state, as it reports it about itself.
2036
+
2037
+ Only the device can read its own video processor, so this is never written
2038
+ to a device, and a controller's frame about one carries none.
2039
+ """
2040
+ flags: OneIPPtpFlag = OneIPPtpFlag(0)
2041
+ """The block's flags."""
2042
+ mode: OneIPPtpMode|int = OneIPPtpMode.OFF
2043
+ """The mode the video processor runs; a raw int for one this library does
2044
+ not name."""
2045
+ domain: int = 0
2046
+ """The domain the video processor runs in."""
2047
+ port_state: int = 0
2048
+ """The IEEE 1588 portState."""
2049
+ lock: OneIPPtpLock|int = OneIPPtpLock.UNLOCKED
2050
+ """How far the clock has locked; a raw int for a value this library does
2051
+ not name."""
2052
+ clock_identity: bytes = bytes(8)
2053
+ """The device's own clock identity."""
2054
+ grandmaster_identity: bytes = bytes(8)
2055
+ """The clock identity of the grandmaster the device runs on, all zero
2056
+ while it runs on none."""
2057
+
2058
+ @property
2059
+ def followed(self) -> bool:
2060
+ """Whether the device locked to a master since its PTP started, so its
2061
+ mesh holds a time."""
2062
+ return OneIPPtpFlag.FOLLOWED in self.flags
2063
+
2064
+ @property
2065
+ def grandmaster(self) -> bytes|None:
2066
+ """The grandmaster the device runs on, None while it runs on none."""
2067
+ return self.grandmaster_identity if any(self.grandmaster_identity) else None
2068
+
2069
+ def __str__(self) -> str:
2070
+ mode = self.mode.name.lower() if isinstance(self.mode, OneIPPtpMode) else self.mode
2071
+ lock = self.lock.name.lower() if isinstance(self.lock, OneIPPtpLock) else self.lock
2072
+ gm = self.grandmaster.hex(':') if (self.grandmaster is not None) else 'none'
2073
+ return (f"{mode} domain {self.domain} port state {self.port_state} {lock}"
2074
+ f"{' followed' if self.followed else ''} clock {self.clock_identity.hex(':')} grandmaster {gm}")
2075
+
1949
2076
  @dataclass(frozen=True)
1950
2077
  class OneIPVlan:
1951
2078
  """
@@ -2117,7 +2244,8 @@ class OneIPDeviceSettings:
2117
2244
  def __init__(self, valid:OneIPDeviceSetting=OneIPDeviceSetting(0),
2118
2245
  flags:OneIPDeviceSetting=OneIPDeviceSetting(0), ir_profiles:int=0,
2119
2246
  ir_profile:int=0, ir_profile_sink:int=0, auto_power_save:int=0,
2120
- power_save:OneIPPowerSaveSchedule|None=None) -> None:
2247
+ power_save:OneIPPowerSaveSchedule|None=None, ptp_mode:OneIPPtpMode|int=OneIPPtpMode.OFF,
2248
+ ptp_domain:int=0, ptp_priority1:int=0) -> None:
2121
2249
  self._valid = OneIPDeviceSetting(valid)
2122
2250
  self._flags = OneIPDeviceSetting(flags)
2123
2251
  self._ir_profiles = ir_profiles
@@ -2125,6 +2253,9 @@ class OneIPDeviceSettings:
2125
2253
  self._ir_profile_sink = ir_profile_sink
2126
2254
  self._auto_power_save = auto_power_save
2127
2255
  self._power_save = power_save if (power_save is not None) else OneIPPowerSaveSchedule()
2256
+ self._ptp_mode = int(ptp_mode)
2257
+ self._ptp_domain = ptp_domain
2258
+ self._ptp_priority1 = ptp_priority1
2128
2259
 
2129
2260
  @property
2130
2261
  def valid(self) -> OneIPDeviceSetting:
@@ -2180,6 +2311,29 @@ class OneIPDeviceSettings:
2180
2311
  return None
2181
2312
  return self._power_save
2182
2313
 
2314
+ @property
2315
+ def ptp_mode(self) -> OneIPPtpMode|int|None:
2316
+ """The PTP mode, a raw int for one this library does not name, None
2317
+ while it is not reported."""
2318
+ if (OneIPDeviceSetting.PTP_MODE not in self._valid):
2319
+ return None
2320
+ return _wire_enum(OneIPPtpMode, self._ptp_mode)
2321
+
2322
+ @property
2323
+ def ptp_domain(self) -> int|None:
2324
+ """The PTP domain, up to ONEIP_PTP_DOMAIN_MAX, None while it is not reported."""
2325
+ if (OneIPDeviceSetting.PTP_DOMAIN not in self._valid):
2326
+ return None
2327
+ return self._ptp_domain
2328
+
2329
+ @property
2330
+ def ptp_priority1(self) -> int|None:
2331
+ """The priority1 the device announces as a PTP grandmaster, lower
2332
+ winning, None while it is not reported."""
2333
+ if (OneIPDeviceSetting.PTP_PRIORITY1 not in self._valid):
2334
+ return None
2335
+ return self._ptp_priority1
2336
+
2183
2337
  def merge(self, previous:'OneIPDeviceSettings|None') -> 'OneIPDeviceSettings':
2184
2338
  """
2185
2339
  Fold this received block onto the cached one.
@@ -2203,31 +2357,46 @@ class OneIPDeviceSettings:
2203
2357
  auto_power_save=(self._auto_power_save if (OneIPDeviceSetting.AUTO_POWER_SAVE in valid)
2204
2358
  else previous._auto_power_save),
2205
2359
  power_save=(self._power_save if (OneIPDeviceSetting.POWER_SAVE_SCHEDULE in valid)
2206
- else previous._power_save))
2360
+ else previous._power_save),
2361
+ ptp_mode=(self._ptp_mode if (OneIPDeviceSetting.PTP_MODE in valid)
2362
+ else previous._ptp_mode),
2363
+ ptp_domain=(self._ptp_domain if (OneIPDeviceSetting.PTP_DOMAIN in valid)
2364
+ else previous._ptp_domain),
2365
+ ptp_priority1=(self._ptp_priority1 if (OneIPDeviceSetting.PTP_PRIORITY1 in valid)
2366
+ else previous._ptp_priority1))
2207
2367
 
2208
2368
  def as_applied_to(self, reported:OneIPDeviceSetting) -> 'OneIPDeviceSettings':
2209
2369
  """
2210
2370
  This block limited to what a device takes from a write about it.
2211
2371
 
2212
- A device applies a setting only if it has it, a profile only within its
2213
- range, and never what only it can report about itself.
2372
+ A device applies a setting only if it has it, a profile, PTP mode or PTP
2373
+ domain only within its range, and never what only it can report about
2374
+ itself.
2214
2375
  """
2215
2376
  valid = (self._valid & reported) & ~ONEIP_DEVICE_SETTINGS_REPORTED_ONLY
2216
2377
  if not (0 <= self._ir_profile < ONEIP_IR_PROFILE_MAX):
2217
2378
  valid &= ~OneIPDeviceSetting.IR_PROFILE
2218
2379
  if not (ONEIP_IR_PROFILE_NOT_SET <= self._ir_profile_sink < ONEIP_IR_PROFILE_MAX):
2219
2380
  valid &= ~OneIPDeviceSetting.IR_PROFILE_SINK
2381
+ if (self._ptp_mode > OneIPPtpMode.AUTO):
2382
+ valid &= ~OneIPDeviceSetting.PTP_MODE
2383
+ if (self._ptp_domain > ONEIP_PTP_DOMAIN_MAX):
2384
+ valid &= ~OneIPDeviceSetting.PTP_DOMAIN
2220
2385
  return OneIPDeviceSettings(valid=valid, flags=self._flags, ir_profiles=self._ir_profiles,
2221
2386
  ir_profile=self._ir_profile, ir_profile_sink=self._ir_profile_sink,
2222
- auto_power_save=self._auto_power_save, power_save=self._power_save)
2387
+ auto_power_save=self._auto_power_save, power_save=self._power_save,
2388
+ ptp_mode=self._ptp_mode, ptp_domain=self._ptp_domain,
2389
+ ptp_priority1=self._ptp_priority1)
2223
2390
 
2224
2391
  def __eq__(self, other:Any) -> bool:
2225
2392
  if not isinstance(other, OneIPDeviceSettings):
2226
2393
  return NotImplemented
2227
2394
  return ((self._valid, self._flags, self._ir_profiles, self._ir_profile, self._ir_profile_sink,
2228
- self._auto_power_save, self._power_save)
2395
+ self._auto_power_save, self._power_save, self._ptp_mode, self._ptp_domain,
2396
+ self._ptp_priority1)
2229
2397
  == (other._valid, other._flags, other._ir_profiles, other._ir_profile, other._ir_profile_sink,
2230
- other._auto_power_save, other._power_save))
2398
+ other._auto_power_save, other._power_save, other._ptp_mode, other._ptp_domain,
2399
+ other._ptp_priority1))
2231
2400
 
2232
2401
  def __ne__(self, other:Any) -> bool:
2233
2402
  result = self.__eq__(other)
@@ -2249,6 +2418,12 @@ class OneIPDeviceSettings:
2249
2418
  parts.append(f"auto_power_save={self.auto_power_save}")
2250
2419
  if (self.power_save_schedule is not None):
2251
2420
  parts.append(f"power_save=[{self.power_save_schedule}]")
2421
+ if ((mode := self.ptp_mode) is not None):
2422
+ parts.append(f"ptp_mode={mode.name.lower() if isinstance(mode, OneIPPtpMode) else mode}")
2423
+ if (self.ptp_domain is not None):
2424
+ parts.append(f"ptp_domain={self.ptp_domain}")
2425
+ if (self.ptp_priority1 is not None):
2426
+ parts.append(f"ptp_priority1={self.ptp_priority1}")
2252
2427
  return " ".join(parts)
2253
2428
 
2254
2429
  def __repr__(self) -> str:
@@ -2575,6 +2750,39 @@ class DeviceBase(ABC):
2575
2750
  '''The VLAN configuration this OneIP device last reported about itself,
2576
2751
  None until it has reported one.'''
2577
2752
 
2753
+ @property
2754
+ @abstractmethod
2755
+ def unit_status(self) -> UnitStatus|None:
2756
+ '''The health and IP configuration this unit last reported, None until
2757
+ it has reported them.'''
2758
+
2759
+ @abstractmethod
2760
+ async def clear_unit_status(self) -> bool:
2761
+ '''clear the unit's current system status'''
2762
+
2763
+ @abstractmethod
2764
+ async def upgrade_unit_video_processor(self, force:bool=False) -> bool:
2765
+ '''upgrade the unit's video processor to the latest image it holds'''
2766
+
2767
+ @abstractmethod
2768
+ async def set_unit_dhcp(self) -> bool:
2769
+ '''switch the unit to an IP configuration from DHCP'''
2770
+
2771
+ @abstractmethod
2772
+ async def set_unit_network(self, network:UnitNetConfig) -> bool:
2773
+ '''give the unit a static IP configuration'''
2774
+
2775
+ @abstractmethod
2776
+ async def confirm_unit_network(self) -> bool:
2777
+ '''keep the unit's pending IP configuration'''
2778
+
2779
+ @property
2780
+ @abstractmethod
2781
+ def oneip_ptp(self) -> OneIPPtpState|None:
2782
+ '''The PTP state this OneIP device last reported about itself, None
2783
+ until it has reported one, and while it has no PTP to report: none on
2784
+ its video processor, or in power save.'''
2785
+
2578
2786
  @property
2579
2787
  @abstractmethod
2580
2788
  def oneip_testcard(self) -> OneIPTestcard|None:
@@ -2848,6 +3056,18 @@ class DeviceBase(ABC):
2848
3056
  async def set_oneip_power_save_schedule(self, schedule:OneIPPowerSaveSchedule) -> bool:
2849
3057
  '''set the device's daily power save windows'''
2850
3058
 
3059
+ @abstractmethod
3060
+ async def set_oneip_ptp_mode(self, mode:OneIPPtpMode) -> bool:
3061
+ '''set the PTP mode the device runs'''
3062
+
3063
+ @abstractmethod
3064
+ async def set_oneip_ptp_domain(self, domain:int) -> bool:
3065
+ '''set the PTP domain the device runs in'''
3066
+
3067
+ @abstractmethod
3068
+ async def set_oneip_ptp_priority1(self, priority1:int) -> bool:
3069
+ '''set the priority1 the device announces as a PTP grandmaster'''
3070
+
2851
3071
  @abstractmethod
2852
3072
  async def set_oneip_vlan(self, vlan:OneIPVlan) -> bool:
2853
3073
  '''change the device's VLAN configuration'''
@@ -696,6 +696,49 @@ class OneIPPowerSaveSchedule:
696
696
  def __eq__(self, other: Any) -> bool: ...
697
697
  def __hash__(self) -> int: ...
698
698
 
699
+ @dataclass(frozen=True)
700
+ class UnitNetConfig:
701
+ ip: str = ...
702
+ netmask: str = ...
703
+ gateway: str = ...
704
+ dns1: str = ...
705
+ dns2: str = ...
706
+ @property
707
+ def addresses(self) -> tuple[str, str, str, str, str]: ...
708
+ def is_valid(self) -> bool: ...
709
+
710
+ @dataclass(frozen=True)
711
+ class UnitStatus:
712
+ flags: UnitFlag = ...
713
+ uptime: int = ...
714
+ boot_count: int | None = ...
715
+ reboot_count: int = ...
716
+ encoder_health: int = ...
717
+ decoder_health: int = ...
718
+ network: UnitNetConfig = ...
719
+ fan_rpm: int | None = ...
720
+ fan_duty: int = ...
721
+ status: int = ...
722
+ net_revert_s: int = ...
723
+ temperatures: tuple[int, ...] = ...
724
+ status_message: str = ...
725
+ @property
726
+ def network_pending(self) -> bool: ...
727
+
728
+ @dataclass(frozen=True)
729
+ class OneIPPtpState:
730
+ flags: OneIPPtpFlag = ...
731
+ mode: OneIPPtpMode | int = ...
732
+ domain: int = ...
733
+ port_state: int = ...
734
+ lock: OneIPPtpLock | int = ...
735
+ clock_identity: bytes = ...
736
+ grandmaster_identity: bytes = ...
737
+ @property
738
+ def followed(self) -> bool: ...
739
+ @property
740
+ def grandmaster(self) -> bytes | None: ...
741
+
699
742
  @dataclass(frozen=True)
700
743
  class OneIPVlan:
701
744
  flags: OneIPVlanFlag = ...
@@ -747,7 +790,7 @@ class OneIPTestcard:
747
790
  def supported(self) -> bool: ...
748
791
 
749
792
  class OneIPDeviceSettings:
750
- def __init__(self, valid: OneIPDeviceSetting = ..., flags: OneIPDeviceSetting = ..., ir_profiles: int = 0, ir_profile: int = 0, ir_profile_sink: int = 0, auto_power_save: int = 0, power_save: OneIPPowerSaveSchedule | None = None) -> None: ...
793
+ def __init__(self, valid: OneIPDeviceSetting = ..., flags: OneIPDeviceSetting = ..., ir_profiles: int = 0, ir_profile: int = 0, ir_profile_sink: int = 0, auto_power_save: int = 0, power_save: OneIPPowerSaveSchedule | None = None, ptp_mode: OneIPPtpMode | int = ..., ptp_domain: int = 0, ptp_priority1: int = 0) -> None: ...
751
794
  @property
752
795
  def valid(self) -> OneIPDeviceSetting: ...
753
796
  @property
@@ -763,6 +806,12 @@ class OneIPDeviceSettings:
763
806
  def auto_power_save(self) -> int | None: ...
764
807
  @property
765
808
  def power_save_schedule(self) -> OneIPPowerSaveSchedule | None: ...
809
+ @property
810
+ def ptp_mode(self) -> OneIPPtpMode | int | None: ...
811
+ @property
812
+ def ptp_domain(self) -> int | None: ...
813
+ @property
814
+ def ptp_priority1(self) -> int | None: ...
766
815
  def merge(self, previous: OneIPDeviceSettings | None) -> OneIPDeviceSettings: ...
767
816
  def as_applied_to(self, reported: OneIPDeviceSetting) -> OneIPDeviceSettings: ...
768
817
  def __eq__(self, other: Any) -> bool: ...
@@ -956,6 +1005,22 @@ class DeviceBase(ABC):
956
1005
  def oneip_vlan(self) -> OneIPVlan | None: ...
957
1006
  @property
958
1007
  @abstractmethod
1008
+ def unit_status(self) -> UnitStatus | None: ...
1009
+ @abstractmethod
1010
+ async def clear_unit_status(self) -> bool: ...
1011
+ @abstractmethod
1012
+ async def upgrade_unit_video_processor(self, force: bool = False) -> bool: ...
1013
+ @abstractmethod
1014
+ async def set_unit_dhcp(self) -> bool: ...
1015
+ @abstractmethod
1016
+ async def set_unit_network(self, network: UnitNetConfig) -> bool: ...
1017
+ @abstractmethod
1018
+ async def confirm_unit_network(self) -> bool: ...
1019
+ @property
1020
+ @abstractmethod
1021
+ def oneip_ptp(self) -> OneIPPtpState | None: ...
1022
+ @property
1023
+ @abstractmethod
959
1024
  def oneip_testcard(self) -> OneIPTestcard | None: ...
960
1025
  @property
961
1026
  @abstractmethod
@@ -1081,6 +1146,12 @@ class DeviceBase(ABC):
1081
1146
  @abstractmethod
1082
1147
  async def set_oneip_power_save_schedule(self, schedule: OneIPPowerSaveSchedule) -> bool: ...
1083
1148
  @abstractmethod
1149
+ async def set_oneip_ptp_mode(self, mode: OneIPPtpMode) -> bool: ...
1150
+ @abstractmethod
1151
+ async def set_oneip_ptp_domain(self, domain: int) -> bool: ...
1152
+ @abstractmethod
1153
+ async def set_oneip_ptp_priority1(self, priority1: int) -> bool: ...
1154
+ @abstractmethod
1084
1155
  async def set_oneip_vlan(self, vlan: OneIPVlan) -> bool: ...
1085
1156
  @abstractmethod
1086
1157
  async def set_audio_endpoint_locked(self, endpoint: int, locked: bool) -> bool: ...
@@ -14,7 +14,7 @@ Default communication modes:
14
14
  """
15
15
  import os
16
16
 
17
- VERSION = '6.3.0'
17
+ VERSION = '7.0.0'
18
18
  __version__ = VERSION
19
19
 
20
20
  MX_BCAST_UDP_PORT = 8811