esp-rainmaker-cli 1.12.0__tar.gz → 1.13.1__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 (126) hide show
  1. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/PKG-INFO +25 -2
  2. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/esp_rainmaker_cli.egg-info/PKG-INFO +25 -2
  3. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/esp_rainmaker_cli.egg-info/SOURCES.txt +4 -0
  4. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/esp_rainmaker_cli.egg-info/requires.txt +6 -1
  5. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rainmaker/rainmaker.py +121 -1
  6. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rainmaker/version.py +1 -1
  7. esp_rainmaker_cli-1.13.1/rmaker_cmd/automation.py +217 -0
  8. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_cmd/node.py +10 -2
  9. esp_rainmaker_cli-1.13.1/rmaker_cmd/stream.py +203 -0
  10. esp_rainmaker_cli-1.13.1/rmaker_lib/aws_credentials.py +220 -0
  11. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_lib/configmanager.py +1 -1
  12. esp_rainmaker_cli-1.13.1/rmaker_lib/kvs_streaming.py +2014 -0
  13. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_lib/session.py +79 -0
  14. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/setup.py +2 -0
  15. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/LICENSE +0 -0
  16. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/README.md +0 -0
  17. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/esp_rainmaker_cli.egg-info/dependency_links.txt +0 -0
  18. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/esp_rainmaker_cli.egg-info/entry_points.txt +0 -0
  19. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/esp_rainmaker_cli.egg-info/top_level.txt +0 -0
  20. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rainmaker/__init__.py +0 -0
  21. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_cmd/__init__.py +0 -0
  22. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_cmd/browserlogin.py +0 -0
  23. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_cmd/cache.py +0 -0
  24. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_cmd/cmd_response.py +0 -0
  25. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_cmd/group.py +0 -0
  26. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_cmd/html/welcome.html +0 -0
  27. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_cmd/provision.py +0 -0
  28. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_cmd/test.py +0 -0
  29. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_cmd/user.py +0 -0
  30. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_lib/__init__.py +0 -0
  31. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_lib/cmd_response.py +0 -0
  32. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_lib/constants.py +0 -0
  33. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_lib/device.py +0 -0
  34. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_lib/envval.py +0 -0
  35. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_lib/exceptions.py +0 -0
  36. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_lib/local_control.py +0 -0
  37. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_lib/logger.py +0 -0
  38. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_lib/node.py +0 -0
  39. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_lib/node_cache.py +0 -0
  40. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_lib/profile_manager.py +0 -0
  41. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_lib/profile_utils.py +0 -0
  42. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_lib/schedule_utils.py +0 -0
  43. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_lib/serverconfig.py +0 -0
  44. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_lib/service.py +0 -0
  45. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_lib/session_store.py +0 -0
  46. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_lib/simple_local_control.py +0 -0
  47. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_lib/user.py +0 -0
  48. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/__init__.py +0 -0
  49. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/common/__init__.py +0 -0
  50. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/common/discovery/__init__.py +0 -0
  51. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/common/discovery/mdns_discovery.py +0 -0
  52. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/common/prov/__init__.py +0 -0
  53. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/common/prov/wifi_ctrl.py +0 -0
  54. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/common/prov/wifi_prov.py +0 -0
  55. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/common/prov/wifi_scan.py +0 -0
  56. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/common/security/__init__.py +0 -0
  57. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/common/security/security.py +0 -0
  58. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/common/security/security0.py +0 -0
  59. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/common/security/security1.py +0 -0
  60. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/common/security/security2.py +0 -0
  61. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/common/security/srp6a.py +0 -0
  62. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/common/transport/__init__.py +0 -0
  63. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/common/transport/ble_cli.py +0 -0
  64. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/common/transport/transport.py +0 -0
  65. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/common/transport/transport_ble.py +0 -0
  66. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/common/transport/transport_console.py +0 -0
  67. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/common/transport/transport_http.py +0 -0
  68. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/common/utils/__init__.py +0 -0
  69. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/common/utils/convenience.py +0 -0
  70. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_claim/__init__.py +0 -0
  71. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_claim/claim.py +0 -0
  72. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_claim/claim_config.py +0 -0
  73. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_local_ctrl/__init__.py +0 -0
  74. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_local_ctrl/constants_pb2.py +0 -0
  75. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_local_ctrl/esp_local_ctrl.py +0 -0
  76. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_local_ctrl/esp_local_ctrl_pb2.py +0 -0
  77. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_local_ctrl/esp_prov.py +0 -0
  78. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_local_ctrl/esp_rainmaker_ctrl.py +0 -0
  79. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_local_ctrl/esp_rmaker_prov_local_ctrl_pb2.py +0 -0
  80. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_local_ctrl/integration.py +0 -0
  81. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_local_ctrl/proto/__init__.py +0 -0
  82. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_local_ctrl/proto/proto_lc.py +0 -0
  83. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_local_ctrl/prov/__init__.py +0 -0
  84. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_local_ctrl/prov/custom_prov.py +0 -0
  85. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_local_ctrl/prov/wifi_ctrl.py +0 -0
  86. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_local_ctrl/raw_config.py +0 -0
  87. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_local_ctrl/raw_params.py +0 -0
  88. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_local_ctrl/sec0_pb2.py +0 -0
  89. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_local_ctrl/sec1_pb2.py +0 -0
  90. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_local_ctrl/sec2_pb2.py +0 -0
  91. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_local_ctrl/security/__init__.py +0 -0
  92. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_local_ctrl/session_pb2.py +0 -0
  93. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_local_ctrl/transport/__init__.py +0 -0
  94. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_local_ctrl/utils/__init__.py +0 -0
  95. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/__init__.py +0 -0
  96. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/challenge_response.py +0 -0
  97. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/config/__init__.py +0 -0
  98. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/config/custom_cloud_config_pb2.py +0 -0
  99. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/config/esp_rmaker_chal_resp_pb2.py +0 -0
  100. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/config/esp_rmaker_claim_pb2.py +0 -0
  101. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/config/esp_rmaker_user_mapping_pb2.py +0 -0
  102. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/esp_rainmaker_prov.py +0 -0
  103. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/on_network_chal_resp.py +0 -0
  104. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/proto/__init__.py +0 -0
  105. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/protocomm/__init__.py +0 -0
  106. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/protocomm/python/__init__.py +0 -0
  107. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/protocomm/python/constants_pb2.py +0 -0
  108. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/protocomm/python/sec0_pb2.py +0 -0
  109. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/protocomm/python/sec1_pb2.py +0 -0
  110. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/protocomm/python/sec2_pb2.py +0 -0
  111. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/protocomm/python/session_pb2.py +0 -0
  112. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/prov/__init__.py +0 -0
  113. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/prov/prov_util.py +0 -0
  114. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/prov/user_mapping.py +0 -0
  115. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/security/__init__.py +0 -0
  116. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/transport/__init__.py +0 -0
  117. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/utils/__init__.py +0 -0
  118. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/wifi_provisioning/__init__.py +0 -0
  119. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/wifi_provisioning/python/__init__.py +0 -0
  120. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_config_pb2.py +0 -0
  121. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_constants_pb2.py +0 -0
  122. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_ctrl_pb2.py +0 -0
  123. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_scan_pb2.py +0 -0
  124. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/server_cert/__init__.py +0 -0
  125. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/server_cert/server_cert.pem +0 -0
  126. {esp_rainmaker_cli-1.12.0 → esp_rainmaker_cli-1.13.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: esp-rainmaker-cli
3
- Version: 1.12.0
3
+ Version: 1.13.1
4
4
  Summary: A python utility to perform host based claiming
5
5
  Home-page: https://github.com/espressif/esp-rainmaker-cli
6
6
  Author: Espressif Systems
@@ -18,6 +18,8 @@ Classifier: Topic :: Software Development :: Embedded Systems
18
18
  Classifier: Programming Language :: Python :: 3.9
19
19
  Classifier: Programming Language :: Python :: 3.10
20
20
  Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
21
23
  Requires-Python: >=3.9
22
24
  Description-Content-Type: text/markdown
23
25
  License-File: LICENSE
@@ -32,11 +34,16 @@ Requires-Dist: future>=0.15.2
32
34
  Requires-Dist: pyparsing>=2.4.0
33
35
  Requires-Dist: pyelftools>=0.22
34
36
  Requires-Dist: esptool>=4.4
35
- Requires-Dist: esp-secure-cert-tool==1.0.1
37
+ Requires-Dist: esp-secure-cert-tool>=2.3.6
36
38
  Requires-Dist: setuptools
37
39
  Requires-Dist: esp_idf_nvs_partition_gen>=0.1.6
38
40
  Requires-Dist: bleak>=0.20.0
39
41
  Requires-Dist: zeroconf>=0.131.0
42
+ Requires-Dist: aiortc>=1.6.0
43
+ Requires-Dist: boto3>=1.28.0
44
+ Requires-Dist: websockets>=11.0
45
+ Requires-Dist: opencv-python>=4.8.0
46
+ Requires-Dist: numpy>=1.24.0
40
47
  Dynamic: author
41
48
  Dynamic: classifier
42
49
  Dynamic: description
@@ -115,6 +122,22 @@ Changelog
115
122
 
116
123
  All major changes to ESP RainMaker CLI will be documented in this file.
117
124
 
125
+ ## [1.13.1] - 23-Apr-2026
126
+ ### Added
127
+ - Node sharing enhancements:
128
+ - Support for sharing as "primary"
129
+ - Transferring nodes
130
+ - Adding sub-roles
131
+
132
+ ## [1.13.0] - 08-Apr-2026
133
+ ### Added
134
+ - New `stream` command for WebRTC video streaming from ESP RainMaker camera devices via Amazon Kinesis Video Streams (KVS) signaling:
135
+ - Live video display with optional recording to file (`--output` / `-o`)
136
+ - Duration-limited streaming (`--duration`)
137
+ - Parallel ICE server fetch and WebSocket connection for faster stream setup
138
+ - Graceful handling of device disconnects, reboots, and frame timeouts
139
+ - Suppression of macOS FFmpeg duplicate library warnings during import
140
+
118
141
  ## [1.12.0] - 19-Feb-2026
119
142
  ### Added
120
143
  - Local node cache layer for faster `--local` control operations:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: esp-rainmaker-cli
3
- Version: 1.12.0
3
+ Version: 1.13.1
4
4
  Summary: A python utility to perform host based claiming
5
5
  Home-page: https://github.com/espressif/esp-rainmaker-cli
6
6
  Author: Espressif Systems
@@ -18,6 +18,8 @@ Classifier: Topic :: Software Development :: Embedded Systems
18
18
  Classifier: Programming Language :: Python :: 3.9
19
19
  Classifier: Programming Language :: Python :: 3.10
20
20
  Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
21
23
  Requires-Python: >=3.9
22
24
  Description-Content-Type: text/markdown
23
25
  License-File: LICENSE
@@ -32,11 +34,16 @@ Requires-Dist: future>=0.15.2
32
34
  Requires-Dist: pyparsing>=2.4.0
33
35
  Requires-Dist: pyelftools>=0.22
34
36
  Requires-Dist: esptool>=4.4
35
- Requires-Dist: esp-secure-cert-tool==1.0.1
37
+ Requires-Dist: esp-secure-cert-tool>=2.3.6
36
38
  Requires-Dist: setuptools
37
39
  Requires-Dist: esp_idf_nvs_partition_gen>=0.1.6
38
40
  Requires-Dist: bleak>=0.20.0
39
41
  Requires-Dist: zeroconf>=0.131.0
42
+ Requires-Dist: aiortc>=1.6.0
43
+ Requires-Dist: boto3>=1.28.0
44
+ Requires-Dist: websockets>=11.0
45
+ Requires-Dist: opencv-python>=4.8.0
46
+ Requires-Dist: numpy>=1.24.0
40
47
  Dynamic: author
41
48
  Dynamic: classifier
42
49
  Dynamic: description
@@ -115,6 +122,22 @@ Changelog
115
122
 
116
123
  All major changes to ESP RainMaker CLI will be documented in this file.
117
124
 
125
+ ## [1.13.1] - 23-Apr-2026
126
+ ### Added
127
+ - Node sharing enhancements:
128
+ - Support for sharing as "primary"
129
+ - Transferring nodes
130
+ - Adding sub-roles
131
+
132
+ ## [1.13.0] - 08-Apr-2026
133
+ ### Added
134
+ - New `stream` command for WebRTC video streaming from ESP RainMaker camera devices via Amazon Kinesis Video Streams (KVS) signaling:
135
+ - Live video display with optional recording to file (`--output` / `-o`)
136
+ - Duration-limited streaming (`--duration`)
137
+ - Parallel ICE server fetch and WebSocket connection for faster stream setup
138
+ - Graceful handling of device disconnects, reboots, and frame timeouts
139
+ - Suppression of macOS FFmpeg duplicate library warnings during import
140
+
118
141
  ## [1.12.0] - 19-Feb-2026
119
142
  ### Added
120
143
  - Local node cache layer for faster `--local` control operations:
@@ -11,22 +11,26 @@ rainmaker/__init__.py
11
11
  rainmaker/rainmaker.py
12
12
  rainmaker/version.py
13
13
  rmaker_cmd/__init__.py
14
+ rmaker_cmd/automation.py
14
15
  rmaker_cmd/browserlogin.py
15
16
  rmaker_cmd/cache.py
16
17
  rmaker_cmd/cmd_response.py
17
18
  rmaker_cmd/group.py
18
19
  rmaker_cmd/node.py
19
20
  rmaker_cmd/provision.py
21
+ rmaker_cmd/stream.py
20
22
  rmaker_cmd/test.py
21
23
  rmaker_cmd/user.py
22
24
  rmaker_cmd/html/welcome.html
23
25
  rmaker_lib/__init__.py
26
+ rmaker_lib/aws_credentials.py
24
27
  rmaker_lib/cmd_response.py
25
28
  rmaker_lib/configmanager.py
26
29
  rmaker_lib/constants.py
27
30
  rmaker_lib/device.py
28
31
  rmaker_lib/envval.py
29
32
  rmaker_lib/exceptions.py
33
+ rmaker_lib/kvs_streaming.py
30
34
  rmaker_lib/local_control.py
31
35
  rmaker_lib/logger.py
32
36
  rmaker_lib/node.py
@@ -9,8 +9,13 @@ future>=0.15.2
9
9
  pyparsing>=2.4.0
10
10
  pyelftools>=0.22
11
11
  esptool>=4.4
12
- esp-secure-cert-tool==1.0.1
12
+ esp-secure-cert-tool>=2.3.6
13
13
  setuptools
14
14
  esp_idf_nvs_partition_gen>=0.1.6
15
15
  bleak>=0.20.0
16
16
  zeroconf>=0.131.0
17
+ aiortc>=1.6.0
18
+ boto3>=1.28.0
19
+ websockets>=11.0
20
+ opencv-python>=4.8.0
21
+ numpy>=1.24.0
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  #
3
- # SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
3
+ # SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD
4
4
  #
5
5
  # SPDX-License-Identifier: Apache-2.0
6
6
 
@@ -16,7 +16,9 @@ from rmaker_cmd.provision import provision
16
16
  from rmaker_cmd.test import test
17
17
  from rmaker_lib.logger import log
18
18
  from rmaker_cmd.group import group_add, group_remove, group_edit, group_list, group_show, group_add_nodes, group_remove_nodes, group_list_nodes
19
+ from rmaker_cmd.automation import automation_add, automation_edit, automation_remove, automation_get
19
20
  from rmaker_cmd.cache import cache_manage
21
+ from rmaker_cmd.stream import stream_video
20
22
 
21
23
  # Import the version
22
24
  from rainmaker.version import VERSION
@@ -666,6 +668,29 @@ def main():
666
668
  "format: <nodeid1>,<nodeid2>,...",
667
669
  required=True)
668
670
 
671
+ add_op_parser.add_argument('--sub_role',
672
+ type=int,
673
+ metavar='<sub_role>',
674
+ choices=range(1, 5),
675
+ help='Sub-role for the secondary user (1-4)',
676
+ default=None)
677
+
678
+ add_op_parser.add_argument('--primary',
679
+ action='store_true',
680
+ help='Set "primary":true in the sharing request')
681
+
682
+ add_op_parser.add_argument('--transfer',
683
+ action='store_true',
684
+ help='Transfer node ownership to the specified user')
685
+
686
+ add_op_parser.add_argument('--new_role',
687
+ type=str,
688
+ metavar='<new_role>',
689
+ choices=['primary', 'secondary'],
690
+ help='New role for the current user after transfer (primary/secondary).\n'
691
+ 'Only used with --transfer',
692
+ default=None)
693
+
669
694
  add_profile_argument(add_op_parser)
670
695
  add_op_parser.set_defaults(func=node_sharing_ops, parser=add_op_parser)
671
696
 
@@ -909,6 +934,76 @@ def main():
909
934
  add_profile_argument(group_list_nodes_parser)
910
935
  group_list_nodes_parser.set_defaults(func=group_list_nodes)
911
936
 
937
+ # Automation Management
938
+ automation_parser = subparsers.add_parser('automations',
939
+ help='Manage automation triggers')
940
+ automation_parser.set_defaults(func=lambda vars=None: automation_parser.print_help())
941
+ automation_subparsers = automation_parser.add_subparsers(dest='automation_command',
942
+ help='Automation operations')
943
+
944
+ # automations add
945
+ auto_add_parser = automation_subparsers.add_parser('add', help='Create a new automation trigger')
946
+ auto_add_parser.add_argument('--name', type=str, required=True, help='Name of the automation')
947
+ auto_add_parser.add_argument('--event-type', type=str, required=True,
948
+ choices=['params', 'weather', 'daylight'],
949
+ help='Event type (params, weather, daylight)')
950
+ auto_add_parser.add_argument('--event', type=str, required=True, action='append',
951
+ help='Event JSON object (repeatable). '
952
+ 'E.g., \'{"params": {"Light": {"Brightness": 100}}, "check": "=="}\'')
953
+ auto_add_parser.add_argument('--action', type=str, required=True,
954
+ help='Action JSON (object or array of objects). '
955
+ 'E.g., \'{"node_id": "xxx", "params": {"Light": {"Output": true}}}\'')
956
+ auto_add_parser.add_argument('--node-id', type=str,
957
+ help='Node ID (required for event-type params)')
958
+ auto_add_parser.add_argument('--location', type=str,
959
+ help='Location as lat,long (required for event-type weather/daylight). '
960
+ 'E.g., 18.521428,73.8544541')
961
+ auto_add_parser.add_argument('--event-operator', type=str, choices=['AND', 'OR'],
962
+ help='Logical operator for multiple events (AND/OR)')
963
+ auto_add_parser.add_argument('--retrigger', action='store_true', default=False,
964
+ help='Allow retriggering for the same event')
965
+ auto_add_parser.add_argument('--metadata', type=str, help='Metadata as JSON string')
966
+ add_profile_argument(auto_add_parser)
967
+ auto_add_parser.set_defaults(func=automation_add)
968
+
969
+ # automations edit
970
+ auto_edit_parser = automation_subparsers.add_parser('edit', help='Edit an existing automation trigger')
971
+ auto_edit_parser.add_argument('--id', type=str, required=True, help='Automation ID')
972
+ auto_edit_parser.add_argument('--name', type=str, help='New name for the automation')
973
+ auto_edit_parser.add_argument('--event', type=str, action='append',
974
+ help='Event JSON object (repeatable)')
975
+ auto_edit_parser.add_argument('--action', type=str,
976
+ help='Action JSON (object or array of objects)')
977
+ auto_edit_parser.add_argument('--node-id', type=str, help='Node ID')
978
+ auto_edit_parser.add_argument('--location', type=str,
979
+ help='Location as lat,long')
980
+ auto_edit_parser.add_argument('--event-operator', type=str, choices=['AND', 'OR'],
981
+ help='Logical operator for multiple events (AND/OR)')
982
+ auto_edit_parser.add_argument('--retrigger', action='store_true', default=False,
983
+ help='Enable retriggering')
984
+ auto_edit_parser.add_argument('--no-retrigger', action='store_true', default=False,
985
+ help='Disable retriggering')
986
+ auto_edit_parser.add_argument('--enabled', action='store_true', default=False,
987
+ help='Enable the automation')
988
+ auto_edit_parser.add_argument('--disabled', action='store_true', default=False,
989
+ help='Disable the automation')
990
+ auto_edit_parser.add_argument('--metadata', type=str, help='Metadata as JSON string')
991
+ add_profile_argument(auto_edit_parser)
992
+ auto_edit_parser.set_defaults(func=automation_edit)
993
+
994
+ # automations remove
995
+ auto_remove_parser = automation_subparsers.add_parser('remove', help='Remove an automation trigger')
996
+ auto_remove_parser.add_argument('--id', type=str, required=True, help='Automation ID to remove')
997
+ add_profile_argument(auto_remove_parser)
998
+ auto_remove_parser.set_defaults(func=automation_remove)
999
+
1000
+ # automations get
1001
+ auto_get_parser = automation_subparsers.add_parser('get', help='Get automation triggers')
1002
+ auto_get_parser.add_argument('--id', type=str, help='Automation ID (omit to list all)')
1003
+ auto_get_parser.add_argument('--node-id', type=str, help='Filter by node ID')
1004
+ add_profile_argument(auto_get_parser)
1005
+ auto_get_parser.set_defaults(func=automation_get)
1006
+
912
1007
  # Node Management (tags, metadata)
913
1008
  node_parser = subparsers.add_parser('node',
914
1009
  help='Manage node properties (tags, metadata)')
@@ -1018,6 +1113,31 @@ def main():
1018
1113
  add_profile_argument(raw_api_parser)
1019
1114
  raw_api_parser.set_defaults(func=raw_api_call)
1020
1115
 
1116
+ # Video Streaming
1117
+ stream_parser = subparsers.add_parser('stream',
1118
+ help='Stream video from a camera device')
1119
+ stream_parser.add_argument('nodeid',
1120
+ type=str,
1121
+ metavar='<nodeid>',
1122
+ help='Node ID for the camera device')
1123
+ stream_parser.add_argument('--output', '-o',
1124
+ type=str,
1125
+ metavar='<path>',
1126
+ help='Save video to file (MP4)')
1127
+ stream_parser.add_argument('--region',
1128
+ type=str,
1129
+ metavar='<region>',
1130
+ help='AWS region override')
1131
+ stream_parser.add_argument('--duration',
1132
+ type=int,
1133
+ metavar='<seconds>',
1134
+ help='Stream duration in seconds')
1135
+ stream_parser.add_argument('--stats-only',
1136
+ action='store_true',
1137
+ help='Show statistics only, no video display')
1138
+ add_profile_argument(stream_parser)
1139
+ stream_parser.set_defaults(func=stream_video)
1140
+
1021
1141
  args = parser.parse_args()
1022
1142
 
1023
1143
  if args.func is not None:
@@ -5,4 +5,4 @@
5
5
  # SPDX-License-Identifier: Apache-2.0
6
6
 
7
7
  # This file contains the version information for the ESP RainMaker CLI
8
- VERSION = "1.12.0"
8
+ VERSION = "1.13.1"
@@ -0,0 +1,217 @@
1
+ # SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from rmaker_lib.profile_utils import get_session_with_profile
6
+ import json
7
+
8
+
9
+ def _handle_error(e, operation):
10
+ """Helper function to handle errors and extract descriptions from API responses"""
11
+ error_msg = str(e)
12
+ try:
13
+ if hasattr(e, 'response') and e.response is not None:
14
+ resp = e.response.json()
15
+ if 'description' in resp:
16
+ error_msg = resp['description']
17
+ except Exception:
18
+ pass
19
+ print(f"Failed to {operation}: {error_msg}")
20
+
21
+
22
+ def _parse_json(value, field_name):
23
+ """Parse a JSON string, returning the parsed object or None on error."""
24
+ try:
25
+ return json.loads(value)
26
+ except Exception as e:
27
+ print(f"Invalid JSON for {field_name}: {e}")
28
+ return None
29
+
30
+
31
+ def _parse_location(location_str):
32
+ """Parse 'lat,long' string into location dict."""
33
+ parts = location_str.split(',')
34
+ if len(parts) != 2:
35
+ print("Invalid location format. Use: lat,long (e.g., 18.521428,73.8544541)")
36
+ return None
37
+ return {"latitude": parts[0].strip(), "longitude": parts[1].strip()}
38
+
39
+
40
+ def automation_add(vars=None):
41
+ try:
42
+ s = get_session_with_profile(vars or {})
43
+ name = vars['name']
44
+ event_type = vars['event_type']
45
+ event_jsons = vars['event']
46
+ action_json = vars['action']
47
+ node_id = vars.get('node_id')
48
+ location = vars.get('location')
49
+ event_operator = vars.get('event_operator')
50
+ retrigger = vars.get('retrigger', False)
51
+ metadata_arg = vars.get('metadata')
52
+
53
+ # Map 'params' to 'node_params' for the API
54
+ api_event_type = 'node_params' if event_type == 'params' else event_type
55
+
56
+ # Validate event_type-specific requirements
57
+ if event_type == 'params' and not node_id:
58
+ print("Error: --node-id is required when --event-type is 'params'")
59
+ return
60
+ if event_type in ('weather', 'daylight') and not location:
61
+ print(f"Error: --location is required when --event-type is '{event_type}'")
62
+ return
63
+
64
+ # Parse events
65
+ events = []
66
+ for ev in event_jsons:
67
+ parsed = _parse_json(ev, 'event')
68
+ if parsed is None:
69
+ return
70
+ events.append(parsed)
71
+
72
+ # Validate event_operator if multiple events
73
+ if len(events) > 1 and not event_operator:
74
+ print("Error: --event-operator (AND/OR) is required when multiple events are specified")
75
+ return
76
+
77
+ # Parse action
78
+ action = _parse_json(action_json, 'action')
79
+ if action is None:
80
+ return
81
+ if isinstance(action, dict):
82
+ actions = [action]
83
+ elif isinstance(action, list):
84
+ actions = action
85
+ else:
86
+ print("Error: --action must be a JSON object or array of objects")
87
+ return
88
+
89
+ # Build payload
90
+ payload = {
91
+ 'name': name,
92
+ 'event_type': api_event_type,
93
+ 'events': events,
94
+ 'actions': actions,
95
+ 'retrigger': retrigger,
96
+ }
97
+ if node_id:
98
+ payload['node_id'] = node_id
99
+ if location:
100
+ loc = _parse_location(location)
101
+ if loc is None:
102
+ return
103
+ payload['location'] = loc
104
+ if event_operator:
105
+ payload['event_operator'] = event_operator.lower()
106
+ if metadata_arg:
107
+ metadata = _parse_json(metadata_arg, 'metadata')
108
+ if metadata is None:
109
+ return
110
+ payload['metadata'] = metadata
111
+
112
+ resp = s.create_automation(payload)
113
+ print("Automation created successfully:")
114
+ print(json.dumps(resp, indent=2))
115
+ except Exception as e:
116
+ _handle_error(e, "create automation")
117
+
118
+
119
+ def automation_edit(vars=None):
120
+ try:
121
+ s = get_session_with_profile(vars or {})
122
+ automation_id = vars['id']
123
+
124
+ payload = {}
125
+ if vars.get('name'):
126
+ payload['name'] = vars['name']
127
+ if vars.get('node_id'):
128
+ payload['node_id'] = vars['node_id']
129
+ if vars.get('event'):
130
+ events = []
131
+ for ev in vars['event']:
132
+ parsed = _parse_json(ev, 'event')
133
+ if parsed is None:
134
+ return
135
+ events.append(parsed)
136
+ payload['events'] = events
137
+ if vars.get('action'):
138
+ action = _parse_json(vars['action'], 'action')
139
+ if action is None:
140
+ return
141
+ if isinstance(action, dict):
142
+ payload['actions'] = [action]
143
+ elif isinstance(action, list):
144
+ payload['actions'] = action
145
+ else:
146
+ print("Error: --action must be a JSON object or array of objects")
147
+ return
148
+ if vars.get('event_operator'):
149
+ payload['event_operator'] = vars['event_operator'].lower()
150
+ if vars.get('location'):
151
+ loc = _parse_location(vars['location'])
152
+ if loc is None:
153
+ return
154
+ payload['location'] = loc
155
+ if vars.get('metadata'):
156
+ metadata = _parse_json(vars['metadata'], 'metadata')
157
+ if metadata is None:
158
+ return
159
+ payload['metadata'] = metadata
160
+
161
+ # Handle retrigger/no-retrigger
162
+ if vars.get('retrigger'):
163
+ payload['retrigger'] = True
164
+ elif vars.get('no_retrigger'):
165
+ payload['retrigger'] = False
166
+
167
+ # Handle enabled/disabled
168
+ if vars.get('enabled'):
169
+ payload['enabled'] = True
170
+ elif vars.get('disabled'):
171
+ payload['enabled'] = False
172
+
173
+ if not payload:
174
+ print("No fields to update. Provide at least one option to edit.")
175
+ return
176
+
177
+ resp = s.update_automation(automation_id, payload)
178
+ print("Automation updated successfully:")
179
+ print(json.dumps(resp, indent=2))
180
+ except Exception as e:
181
+ _handle_error(e, "update automation")
182
+
183
+
184
+ def automation_remove(vars=None):
185
+ try:
186
+ s = get_session_with_profile(vars or {})
187
+ automation_id = vars['id']
188
+ resp = s.remove_automation(automation_id)
189
+ print("Automation removed successfully:")
190
+ print(json.dumps(resp, indent=2))
191
+ except Exception as e:
192
+ _handle_error(e, "remove automation")
193
+
194
+
195
+ def automation_get(vars=None):
196
+ try:
197
+ s = get_session_with_profile(vars or {})
198
+ automation_id = vars.get('id')
199
+ node_id = vars.get('node_id')
200
+ resp = s.get_automations(automation_id=automation_id, node_id=node_id)
201
+
202
+ if not resp:
203
+ print("No automations found.")
204
+ return
205
+
206
+ automations = resp.get('automation_trigger_actions', [])
207
+ if not automations:
208
+ print("No automations found.")
209
+ return
210
+
211
+ if automation_id:
212
+ print("Automation details:")
213
+ else:
214
+ print(f"Automations ({len(automations)}):")
215
+ print(json.dumps(automations, indent=2))
216
+ except Exception as e:
217
+ _handle_error(e, "get automations")
@@ -897,7 +897,7 @@ def list_sharing_details(node_id=None, primary_user=False, request_id=None, list
897
897
 
898
898
  return node_json_resp
899
899
 
900
- def add_user_to_share_nodes(nodes=None, user=None, profile_override=None):
900
+ def add_user_to_share_nodes(nodes=None, user=None, sub_role=None, primary=False, transfer=False, new_role=None, profile_override=None):
901
901
  """
902
902
  Add user to share nodes
903
903
 
@@ -932,6 +932,14 @@ def add_user_to_share_nodes(nodes=None, user=None, profile_override=None):
932
932
  api_input = {}
933
933
  api_input['nodes'] = node_id_list
934
934
  api_input['user_name'] = user
935
+ if sub_role is not None:
936
+ api_input['sub_role'] = sub_role
937
+ if primary:
938
+ api_input['primary'] = True
939
+ if transfer:
940
+ api_input['transfer'] = True
941
+ if new_role is not None:
942
+ api_input['new_role'] = new_role
935
943
  log.debug("API data set: {}".format(api_input))
936
944
 
937
945
  # API with profile-aware session
@@ -1116,7 +1124,7 @@ def node_sharing_ops(vars=None):
1116
1124
  if action == 'add_user':
1117
1125
  # Share nodes with user
1118
1126
  print("Adding user to share node(s)")
1119
- node_json_resp = add_user_to_share_nodes(nodes=vars['nodes'], user=vars['user'], profile_override=profile_override)
1127
+ node_json_resp = add_user_to_share_nodes(nodes=vars['nodes'], user=vars['user'], sub_role=vars.get('sub_role'), primary=vars.get('primary', False), transfer=vars.get('transfer', False), new_role=vars.get('new_role'), profile_override=profile_override)
1120
1128
 
1121
1129
  # Print success response
1122
1130
  if 'status' in node_json_resp and node_json_resp['status'].lower() == 'success':