pyg90alarm 2.5.3__tar.gz → 2.7.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 (97) hide show
  1. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/.github/workflows/main.yml +28 -9
  2. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/MANIFEST.in +1 -0
  3. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/PKG-INFO +28 -26
  4. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/README.rst +27 -25
  5. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/docs/local-protocol.rst +59 -0
  6. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/__init__.py +3 -2
  7. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/alarm.py +114 -11
  8. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/cloud/messages.py +4 -8
  9. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/cloud/notifications.py +5 -3
  10. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/const.py +31 -4
  11. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/local/base_cmd.py +170 -100
  12. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/local/host_info.py +34 -6
  13. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/local/paginated_cmd.py +5 -10
  14. pyg90alarm-2.7.0/src/pyg90alarm/local/system_cmd.py +276 -0
  15. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm.egg-info/PKG-INFO +28 -26
  16. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm.egg-info/SOURCES.txt +2 -0
  17. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/tests/conftest.py +1 -1
  18. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/tests/device_mock.py +11 -8
  19. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/tests/test_alarm.py +7 -0
  20. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/tests/test_cloud_notifications.py +31 -3
  21. pyg90alarm-2.7.0/tests/test_system_commands.py +128 -0
  22. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/.github/CODEOWNERS +0 -0
  23. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/.github/dependabot.yml +0 -0
  24. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/.gitignore +0 -0
  25. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/.pylintrc +0 -0
  26. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/.readthedocs.yaml +0 -0
  27. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/LICENSE +0 -0
  28. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/docs/.DS_Store +0 -0
  29. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/docs/.gitignore +0 -0
  30. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/docs/api-docs.rst +0 -0
  31. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/docs/cloud-protocol.rst +0 -0
  32. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/docs/conf.py +0 -0
  33. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/docs/index.rst +0 -0
  34. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/docs/requirements.txt +0 -0
  35. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/pyproject.toml +0 -0
  36. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/setup.cfg +0 -0
  37. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/setup.py +0 -0
  38. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/sonar-project.properties +0 -0
  39. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/callback.py +0 -0
  40. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/cloud/__init__.py +0 -0
  41. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/cloud/const.py +0 -0
  42. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/cloud/protocol.py +0 -0
  43. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/dataclass/__init__.py +0 -0
  44. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/dataclass/load_save.py +0 -0
  45. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/dataclass/validation.py +0 -0
  46. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/definitions/__init__.py +0 -0
  47. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/definitions/base.py +0 -0
  48. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/definitions/devices.py +0 -0
  49. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/definitions/sensors.py +0 -0
  50. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/entities/__init__.py +0 -0
  51. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/entities/base_entity.py +0 -0
  52. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/entities/base_list.py +0 -0
  53. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/entities/device.py +0 -0
  54. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/entities/device_list.py +0 -0
  55. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/entities/sensor.py +0 -0
  56. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/entities/sensor_list.py +0 -0
  57. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/event_mapping.py +0 -0
  58. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/exceptions.py +0 -0
  59. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/local/__init__.py +0 -0
  60. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/local/alarm_phones.py +0 -0
  61. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/local/alert_config.py +0 -0
  62. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/local/config.py +0 -0
  63. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/local/discovery.py +0 -0
  64. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/local/history.py +0 -0
  65. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/local/host_config.py +0 -0
  66. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/local/host_status.py +0 -0
  67. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/local/net_config.py +0 -0
  68. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/local/notifications.py +0 -0
  69. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/local/paginated_result.py +0 -0
  70. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/local/targeted_discovery.py +0 -0
  71. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/local/user_data_crc.py +0 -0
  72. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/notifications/__init__.py +0 -0
  73. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/notifications/base.py +0 -0
  74. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/notifications/protocol.py +0 -0
  75. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm/py.typed +0 -0
  76. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm.egg-info/dependency_links.txt +0 -0
  77. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm.egg-info/requires.txt +0 -0
  78. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/src/pyg90alarm.egg-info/top_level.txt +0 -0
  79. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/tests/__init__.py +0 -0
  80. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/tests/test_alarm_phones.py +0 -0
  81. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/tests/test_base_commands.py +0 -0
  82. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/tests/test_config.py +0 -0
  83. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/tests/test_devices.py +0 -0
  84. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/tests/test_discovery.py +0 -0
  85. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/tests/test_history.py +0 -0
  86. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/tests/test_host_config.py +0 -0
  87. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/tests/test_local_notifications.py +0 -0
  88. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/tests/test_net_config.py +0 -0
  89. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/tests/test_paginated_commands.py +0 -0
  90. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/tests/test_sensor.py +0 -0
  91. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/tests/unit/dataclass/test_dataclass_load_save.py +0 -0
  92. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/tests/unit/dataclass/test_dataclass_load_save_descriptor.py +0 -0
  93. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/tests/unit/dataclass/test_dataclass_load_save_serialize.py +0 -0
  94. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/tests/unit/dataclass/test_validation.py +0 -0
  95. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/tests/unit/entities/test_base_list.py +0 -0
  96. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/tests/unit/test_exceptions.py +0 -0
  97. {pyg90alarm-2.5.3 → pyg90alarm-2.7.0}/tox.ini +0 -0
@@ -3,8 +3,6 @@ name: main
3
3
 
4
4
  on:
5
5
  pull_request:
6
- release:
7
- types: [published]
8
6
  push:
9
7
  branches:
10
8
  - main
@@ -68,10 +66,35 @@ jobs:
68
66
  -Dsonar.projectVersion=${{ steps.package-version.outputs.VALUE }}
69
67
  # yamllint enable rule:line-length
70
68
 
69
+ release:
70
+ name: Release with semantic-release
71
+ runs-on: ubuntu-latest
72
+ needs: [tests]
73
+ permissions:
74
+ contents: write
75
+ issues: write
76
+ pull-requests: write
77
+ outputs:
78
+ new_release_published: ${{ steps.semantic.outputs.new_release_published }}
79
+ steps:
80
+ - name: Checkout code (SSH)
81
+ uses: actions/checkout@v6
82
+ with:
83
+ fetch-depth: 0
84
+
85
+ - name: Run semantic release
86
+ id: semantic
87
+ uses: cycjimmy/semantic-release-action@v6
88
+ with:
89
+ extra_plugins: |
90
+ conventional-changelog-conventionalcommits@9.1.0
91
+ env:
92
+ GITHUB_TOKEN: ${{ github.token }}
93
+
71
94
  pypi-publish:
72
95
  name: Publish to PyPi
73
96
  runs-on: ubuntu-latest
74
- needs: [tests]
97
+ needs: [tests, release]
75
98
  permissions:
76
99
  id-token: write # Required for trusted publishing
77
100
  steps:
@@ -90,7 +113,7 @@ jobs:
90
113
  - name: Publish the package to Test PyPi
91
114
  # Skip publishing to test PyPI if we're performing release, there might
92
115
  # be already the version of the package from the merge to master branch
93
- if: github.event_name != 'release'
116
+ if: needs.release.outputs.new_release_published != 'true'
94
117
  uses: pypa/gh-action-pypi-publish@release/v1
95
118
  with:
96
119
  repository-url: https://test.pypi.org/legacy/
@@ -98,11 +121,7 @@ jobs:
98
121
  - name: Publish the release to PyPi
99
122
  # Publish to production PyPi only happens when a release published out
100
123
  # of the main branch
101
- if: >-
102
- github.event_name == 'release'
103
- && github.event.action == 'published'
104
- && (github.event.release.target_commitish == 'main'
105
- || github.event.release.target_commitish == 'master')
124
+ if: needs.release.outputs.new_release_published == 'true'
106
125
  uses: pypa/gh-action-pypi-publish@release/v1
107
126
  with:
108
127
  attestations: true
@@ -12,3 +12,4 @@ include setup.py
12
12
  recursive-include src *.py py.typed
13
13
 
14
14
  exclude requirements_dev.txt
15
+ exclude .releaserc.yaml
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyg90alarm
3
- Version: 2.5.3
3
+ Version: 2.7.0
4
4
  Summary: G90 Alarm system protocol
5
5
  Home-page: https://github.com/hostcc/pyg90alarm
6
6
  Author: Ilia Sotnikov
@@ -189,7 +189,7 @@ Cloud notifications
189
189
  The cloud protocol is native to the panel and is used to interact with mobile application. The package can mimic the cloud server and interpret the messages the panel sends to the cloud, allowing to receive the notifications and alerts.
190
190
  While the protocol also allows to send commands to the panel, it is not implemented and local protocol is used for that - i.e. when cloud notifications are in use the local protocol still utilized for sending commands to the panel.
191
191
 
192
- The cloud protocol is TCP based and typically interacts with cloud service at known IP address and port (not customizable at panel side). To process the cloud notifications all the traffic from panel towards the cloud (IP address ``47.88.7.61`` and TCP port ``5678`` as of writing) needs to be diverted to the node where the package is running - depending on your network equipment it could be port forwarding, DNAT or other means. It is unclear whether the panel utilizes DNS to resolve the cloud service IP address, hence the documentation only mentions IP-based traffic redirection.
192
+ The cloud protocol is TCP based and typically interacts with cloud service at known IP address and port, which could be customized. To process the cloud notifications all the traffic from panel towards the configured IP address service needs to be received by the node where the package is running.
193
193
 
194
194
  Please see
195
195
  `the section <docs/cloud-protocol.rst>`_ for further details on the protocol.
@@ -203,25 +203,6 @@ The package could act as:
203
203
  - Chained cloud server, where in addition to interpreting the notifications it
204
204
  will also forward all packets received from the panel to the cloud server, and pass its responses back to the panel. This allows to have notifications processed by the package and the mobile application working as well.
205
205
 
206
- .. note:: Sending packets upstream to the known IP address and port of the cloud server might result in those looped back (since traffic from panel to cloud service has to be redirected to the host where package runs), if your network equipment can't account for source address in redirection rules (i.e. limiting the port redirection to the panel's IP address). In that case you'll need another redirection, from the host where the package runs to the cloud service using an IP from your network. That way those two redirection rules will coexist correctly. To illustrate:
207
-
208
- Port forwarding rule 1:
209
-
210
- - Source: panel IP address
211
- - Destination: 47.88.7.61
212
- - Port: 5678
213
- - Redirect to host: host where package runs
214
- - Redirect to port: 5678 (or other port if you want to use it)
215
-
216
-
217
- Port forwarding rule 2 (optional):
218
-
219
- - Source: host where package runs
220
- - Destination: an IP address from your network
221
- - Port: 5678 (or other port if you want to use it)
222
- - Redirect to : 47.88.7.61
223
- - Redirect to port: 5678
224
-
225
206
  The code fragments below demonstrate how to utilize both modes - please note those are incomplete, since no callbacks are set to process the notifications.
226
207
 
227
208
  **Standalone mode**
@@ -232,10 +213,20 @@ The code fragments below demonstrate how to utilize both modes - please note tho
232
213
 
233
214
  # Create an instance of the alarm panel
234
215
  alarm = G90Alarm(host='<panel IP address>')
216
+
217
+ # Configure cloud server address the panel should use - the host running the
218
+ # package.
219
+ await alarm.set_cloud_server_address(
220
+ cloud_ip='<host IP address running the package>', cloud_port=5678
221
+ )
222
+
235
223
  # Enable cloud notifications
236
224
  await alarm.use_cloud_notifications(
237
- # Optional, see note above redirecting cloud traffic from panel
238
- local_port=5678,
225
+ # The host/port the package will listen on for the cloud notifications,
226
+ # should match ones above.
227
+ cloud_host='<host IP address running the package>',
228
+ cloud_port=5678,
229
+ cloud_local_port=5678,
239
230
  upstream_host=None
240
231
  )
241
232
  # Start listening for notifications
@@ -250,11 +241,22 @@ The code fragments below demonstrate how to utilize both modes - please note tho
250
241
 
251
242
  # Create an instance of the alarm panel
252
243
  alarm = G90Alarm(host='<panel IP address>')
244
+
245
+ # Configure cloud server address the panel should use - the host running the
246
+ # package.
247
+ await alarm.set_cloud_server_address(
248
+ cloud_ip='<host IP address running the package>', cloud_port=5678
249
+ )
250
+
253
251
  # Enable cloud notifications
254
252
  await alarm.use_cloud_notifications(
255
- # Optional, see note above redirecting cloud traffic from panel
256
- local_port=5678,
257
- # See note above re: cloud service and sending packets to it
253
+ # The host/port the package will listen on for the cloud notifications,
254
+ # should match ones above.
255
+ cloud_host='<host IP address running the package>',
256
+ cloud_port=5678,
257
+ cloud_local_port=5678,
258
+ # Upstream cloud server address the package should forward the
259
+ # notifications to.
258
260
  upstream_host='47.88.7.61',
259
261
  upstream_port=5678
260
262
  )
@@ -142,7 +142,7 @@ Cloud notifications
142
142
  The cloud protocol is native to the panel and is used to interact with mobile application. The package can mimic the cloud server and interpret the messages the panel sends to the cloud, allowing to receive the notifications and alerts.
143
143
  While the protocol also allows to send commands to the panel, it is not implemented and local protocol is used for that - i.e. when cloud notifications are in use the local protocol still utilized for sending commands to the panel.
144
144
 
145
- The cloud protocol is TCP based and typically interacts with cloud service at known IP address and port (not customizable at panel side). To process the cloud notifications all the traffic from panel towards the cloud (IP address ``47.88.7.61`` and TCP port ``5678`` as of writing) needs to be diverted to the node where the package is running - depending on your network equipment it could be port forwarding, DNAT or other means. It is unclear whether the panel utilizes DNS to resolve the cloud service IP address, hence the documentation only mentions IP-based traffic redirection.
145
+ The cloud protocol is TCP based and typically interacts with cloud service at known IP address and port, which could be customized. To process the cloud notifications all the traffic from panel towards the configured IP address service needs to be received by the node where the package is running.
146
146
 
147
147
  Please see
148
148
  `the section <docs/cloud-protocol.rst>`_ for further details on the protocol.
@@ -156,25 +156,6 @@ The package could act as:
156
156
  - Chained cloud server, where in addition to interpreting the notifications it
157
157
  will also forward all packets received from the panel to the cloud server, and pass its responses back to the panel. This allows to have notifications processed by the package and the mobile application working as well.
158
158
 
159
- .. note:: Sending packets upstream to the known IP address and port of the cloud server might result in those looped back (since traffic from panel to cloud service has to be redirected to the host where package runs), if your network equipment can't account for source address in redirection rules (i.e. limiting the port redirection to the panel's IP address). In that case you'll need another redirection, from the host where the package runs to the cloud service using an IP from your network. That way those two redirection rules will coexist correctly. To illustrate:
160
-
161
- Port forwarding rule 1:
162
-
163
- - Source: panel IP address
164
- - Destination: 47.88.7.61
165
- - Port: 5678
166
- - Redirect to host: host where package runs
167
- - Redirect to port: 5678 (or other port if you want to use it)
168
-
169
-
170
- Port forwarding rule 2 (optional):
171
-
172
- - Source: host where package runs
173
- - Destination: an IP address from your network
174
- - Port: 5678 (or other port if you want to use it)
175
- - Redirect to : 47.88.7.61
176
- - Redirect to port: 5678
177
-
178
159
  The code fragments below demonstrate how to utilize both modes - please note those are incomplete, since no callbacks are set to process the notifications.
179
160
 
180
161
  **Standalone mode**
@@ -185,10 +166,20 @@ The code fragments below demonstrate how to utilize both modes - please note tho
185
166
 
186
167
  # Create an instance of the alarm panel
187
168
  alarm = G90Alarm(host='<panel IP address>')
169
+
170
+ # Configure cloud server address the panel should use - the host running the
171
+ # package.
172
+ await alarm.set_cloud_server_address(
173
+ cloud_ip='<host IP address running the package>', cloud_port=5678
174
+ )
175
+
188
176
  # Enable cloud notifications
189
177
  await alarm.use_cloud_notifications(
190
- # Optional, see note above redirecting cloud traffic from panel
191
- local_port=5678,
178
+ # The host/port the package will listen on for the cloud notifications,
179
+ # should match ones above.
180
+ cloud_host='<host IP address running the package>',
181
+ cloud_port=5678,
182
+ cloud_local_port=5678,
192
183
  upstream_host=None
193
184
  )
194
185
  # Start listening for notifications
@@ -203,11 +194,22 @@ The code fragments below demonstrate how to utilize both modes - please note tho
203
194
 
204
195
  # Create an instance of the alarm panel
205
196
  alarm = G90Alarm(host='<panel IP address>')
197
+
198
+ # Configure cloud server address the panel should use - the host running the
199
+ # package.
200
+ await alarm.set_cloud_server_address(
201
+ cloud_ip='<host IP address running the package>', cloud_port=5678
202
+ )
203
+
206
204
  # Enable cloud notifications
207
205
  await alarm.use_cloud_notifications(
208
- # Optional, see note above redirecting cloud traffic from panel
209
- local_port=5678,
210
- # See note above re: cloud service and sending packets to it
206
+ # The host/port the package will listen on for the cloud notifications,
207
+ # should match ones above.
208
+ cloud_host='<host IP address running the package>',
209
+ cloud_port=5678,
210
+ cloud_local_port=5678,
211
+ # Upstream cloud server address the package should forward the
212
+ # notifications to.
211
213
  upstream_host='47.88.7.61',
212
214
  upstream_port=5678
213
215
  )
@@ -158,3 +158,62 @@ uses ``utf-8`` encoding.
158
158
 
159
159
  Data varies across different notification and alert types, see
160
160
  `src/pyg90alarm/local/notifications.py <../../src/pyg90alarm/local/notifications.py>`_.
161
+
162
+ System commands
163
+ ---------------
164
+
165
+ In addition to regular commands, the local protocol also supports system commands that perform device maintenance operations. Unlike regular commands, system commands do not expect a response from the device and are invoked using a special wire format based on AT commands.
166
+
167
+ .. note:: System commands are not exposed through regular command interface but have their own dedicated methods in the ``G90Alarm`` class.
168
+
169
+ Wire Format
170
+ ^^^^^^^^^^^
171
+
172
+ System commands use a different wire format compared to regular commands:
173
+
174
+ :samp:`ISTART[0,100,"AT^IWT={command code}{command data},IWT"]IEND\\0`
175
+
176
+ The command is wrapped in an AT command format (``AT^IWT=...``) within a regular command structure with fixed codes ``0`` and ``100``.
177
+
178
+ Available System Commands
179
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^
180
+
181
+ The following system commands are available:
182
+
183
+ - MCU Reboot (code ``1123``)
184
+ Reboots the Main Control Unit (MCU) of the alarm panel.
185
+
186
+ **Wire format example:**
187
+
188
+ :samp:`ISTART[0,100,"AT^IWT=1123,IWT"]IEND\\0`
189
+
190
+ - GSM Reboot (code ``1129``)
191
+ Reboots the GSM module of the alarm panel.
192
+
193
+ **Wire format example:**
194
+
195
+ :samp:`ISTART[0,100,"AT^IWT=1129,IWT"]IEND\\0`
196
+
197
+ - WiFi Reboot (code ``1006``)
198
+ Reboots the WiFi module of the alarm panel.
199
+
200
+ **Wire format example:**
201
+
202
+ :samp:`ISTART[0,100,"AT^IWT=1006,IWT"]IEND\\0`
203
+
204
+ - Set Server Address (configuration command ``1`` with sub-command ``78``)
205
+ Configures the cloud server address that the alarm panel connects to.
206
+ The command requires three parameters separated by ``&``: cloud primary host,
207
+ cloud secondary host, and cloud port number.
208
+
209
+ There is no indication the panel will use DNS resolution, so both addresses should be IP ones. Also, it is unclear what 2nd address is used for - experiments revealed the panel always connects to the 1st one.
210
+
211
+ **Wire format example:**
212
+
213
+ :samp:`ISTART[0,100,"AT^IWT=1,78=192.168.1.100&192.168.1.100&5678,IWT"]IEND\\0`
214
+
215
+ Where:
216
+
217
+ - ``192.168.1.100`` is the primary IP address of the cloud server
218
+ - ``192.168.1.100`` is the secondary IP address of the cloud server (see above note)
219
+ - ``5678`` is the port number the cloud server listens on
@@ -33,7 +33,8 @@ from .entities.sensor import (
33
33
  )
34
34
  from .entities.device import G90Device
35
35
  from .local.host_info import (
36
- G90HostInfo, G90HostInfoWifiStatus, G90HostInfoGsmStatus
36
+ G90HostInfo, G90HostInfoWifiStatus, G90HostInfoGsmStatus,
37
+ G90HostInfoWifiSetupProgress,
37
38
  )
38
39
  from .definitions.sensors import (
39
40
  G90SensorDefinitions
@@ -81,7 +82,7 @@ __all__ = [
81
82
  'G90Device',
82
83
  # Panel information and status
83
84
  'G90HostInfo', 'G90HostInfoWifiStatus', 'G90HostInfoGsmStatus',
84
- 'G90HostStatus',
85
+ 'G90HostStatus', 'G90HostInfoWifiSetupProgress',
85
86
  # Types for alerts and notifications
86
87
  'G90MessageTypes', 'G90NotificationTypes', 'G90ArmDisarmTypes',
87
88
  'G90AlertTypes', 'G90AlertSources', 'G90AlertStates',
@@ -60,14 +60,14 @@ from typing import (
60
60
  Callable, Coroutine, Union
61
61
  )
62
62
  from .const import (
63
- G90Commands, REMOTE_PORT,
63
+ G90Commands, G90SystemCommands,
64
+ REMOTE_PORT,
64
65
  REMOTE_TARGETED_DISCOVERY_PORT,
65
66
  LOCAL_TARGETED_DISCOVERY_PORT,
66
67
  LOCAL_NOTIFICATIONS_HOST,
67
68
  LOCAL_NOTIFICATIONS_PORT,
68
- CLOUD_NOTIFICATIONS_HOST,
69
- CLOUD_NOTIFICATIONS_PORT,
70
- REMOTE_CLOUD_HOST,
69
+ LOCAL_CLOUD_NOTIFICATIONS_HOST,
70
+ LOCAL_CLOUD_NOTIFICATIONS_PORT,
71
71
  REMOTE_CLOUD_PORT,
72
72
  DEVICE_REGISTRATION_TIMEOUT,
73
73
  ROOM_ID,
@@ -75,7 +75,8 @@ from .const import (
75
75
  G90RemoteButtonStates,
76
76
  G90RFIDKeypadStates,
77
77
  )
78
- from .local.base_cmd import (G90BaseCommand, G90BaseCommandData)
78
+ from .local.base_cmd import G90BaseCommand, BaseCommandsDataT
79
+ from .local.system_cmd import G90SystemCommand, G90SetServerAddressCommand
79
80
  from .local.paginated_result import G90PaginatedResult, G90PaginatedResponse
80
81
  from .entities.base_list import ListChangeCallback
81
82
  from .entities.sensor import G90Sensor
@@ -263,8 +264,8 @@ class G90Alarm(G90NotificationProtocol):
263
264
  return self._port
264
265
 
265
266
  async def command(
266
- self, code: G90Commands, data: Optional[G90BaseCommandData] = None
267
- ) -> G90BaseCommandData:
267
+ self, code: G90Commands, data: Optional[BaseCommandsDataT] = None
268
+ ) -> BaseCommandsDataT:
268
269
  """
269
270
  Invokes a command against the alarm panel.
270
271
 
@@ -272,7 +273,7 @@ class G90Alarm(G90NotificationProtocol):
272
273
  :param data: Command data
273
274
  :return: The result of command invocation
274
275
  """
275
- cmd: G90BaseCommand = await G90BaseCommand(
276
+ cmd = await G90BaseCommand(
276
277
  self._host, self._port, code, data).process()
277
278
  return cmd.result
278
279
 
@@ -292,6 +293,81 @@ class G90Alarm(G90NotificationProtocol):
292
293
  self._host, self._port, code, start, end
293
294
  ).process()
294
295
 
296
+ async def mcu_reboot(self) -> None:
297
+ """
298
+ Reboots the MCU of the alarm panel.
299
+
300
+ Note that underlying command doesn't return any result, so there is no
301
+ feedback from the panel upon execution.
302
+ """
303
+ await G90SystemCommand(
304
+ host=self._host, port=self._port,
305
+ code=G90SystemCommands.MCU_REBOOT
306
+ ).process()
307
+
308
+ async def gsm_reboot(self) -> None:
309
+ """
310
+ Reboots the GSM module of the alarm panel.
311
+
312
+ Note that underlying command doesn't return any result, so there is no
313
+ feedback from the panel upon execution.
314
+ """
315
+ await G90SystemCommand(
316
+ host=self._host, port=self._port,
317
+ code=G90SystemCommands.GSM_REBOOT
318
+ ).process()
319
+
320
+ async def wifi_reboot(self) -> None:
321
+ """
322
+ Reboots the WiFi module of the alarm panel.
323
+
324
+ Note that underlying command doesn't return any result, so there is no
325
+ feedback from the panel upon execution.
326
+ """
327
+ await G90SystemCommand(
328
+ host=self._host, port=self._port,
329
+ code=G90SystemCommands.WIFI_REBOOT
330
+ ).process()
331
+
332
+ async def reboot(self) -> None:
333
+ """
334
+ Reboots the entire alarm panel.
335
+
336
+ The system commands performing reboot of a panel's module don't return
337
+ anything so there is no feedback from the panel upon execution, hence
338
+ the commands are spaced with delays to allow the panel to process
339
+ them.
340
+
341
+ Please be aware that the delays are determined experimentally and might
342
+ be too long or too short.
343
+ """
344
+ await self.gsm_reboot()
345
+ await asyncio.sleep(1)
346
+ await self.mcu_reboot()
347
+ await asyncio.sleep(1)
348
+ await self.wifi_reboot()
349
+ # The MCU likely needs more than 1 second to reboot, but checking it
350
+ # completed the reboot should be done separately
351
+ await asyncio.sleep(1)
352
+
353
+ async def set_cloud_server_address(
354
+ self, cloud_ip: str, cloud_port: int
355
+ ) -> None:
356
+ """
357
+ Sets the cloud server address the alarm panel connects to.
358
+
359
+ :param cloud_ip: IP address of the server to receive cloud protocol
360
+ notifications, should be reachable from the panel. Typically it is the
361
+ IP address of the host running the package
362
+ :param cloud_port: Port number of the server to receive cloud protocol
363
+ notifications, should be reachable from the panel
364
+ """
365
+ await G90SetServerAddressCommand(
366
+ host=self._host, port=self._port,
367
+ cloud_ip=cloud_ip,
368
+ cloud_port=cloud_port
369
+ ).process()
370
+
295
371
  @classmethod
296
372
  async def discover(cls) -> List[G90DiscoveredDevice]:
297
373
  """
@@ -1321,14 +1397,39 @@ class G90Alarm(G90NotificationProtocol):
1321
1397
 
1322
1398
  # pylint: disable=too-many-positional-arguments
1323
1399
  async def use_cloud_notifications(
1324
- self, cloud_local_host: str = CLOUD_NOTIFICATIONS_HOST,
1325
- cloud_local_port: int = CLOUD_NOTIFICATIONS_PORT,
1326
- upstream_host: Optional[str] = REMOTE_CLOUD_HOST,
1400
+ self,
1401
+ cloud_host: str,
1402
+ cloud_port: int,
1403
+ cloud_local_host: str = LOCAL_CLOUD_NOTIFICATIONS_HOST,
1404
+ cloud_local_port: int = LOCAL_CLOUD_NOTIFICATIONS_PORT,
1405
+ upstream_host: Optional[str] = None,
1327
1406
  upstream_port: Optional[int] = REMOTE_CLOUD_PORT,
1328
1407
  keep_single_connection: bool = True
1329
1408
  ) -> None:
1330
1409
  """
1331
1410
  Switches to use cloud notifications for device alerts.
1411
+
1412
+ Please note the method does not configure the panel for the host to
1413
+ receive the notifications - please invoke
1414
+ :meth:`G90Alarm.set_cloud_server_address` method to do that. The reason
1415
+ of that is configuring cloud server address on the panel is one-time
1416
+ operation, while the method will be called multiple times.
1417
+
1418
+ :param cloud_host: The cloud server host to connect to, should be
1419
+ reachable from the panel
1420
+ :param cloud_port: The cloud server port to connect to, should be
1421
+ reachable from the panel
1422
+ :param cloud_local_host: Local host to bind cloud notifications
1423
+ listener to
1424
+ :param cloud_local_port: Local port to bind cloud notifications
1425
+ listener to, should match `cloud_port` above unless network setup
1426
+ dictates otherwise
1427
+ :param upstream_host: Optional upstream host to connect to cloud
1428
+ server through
1429
+ :param upstream_port: Optional upstream port to connect to cloud
1430
+ server through
1431
+ :param keep_single_connection: If enabled, keeps a single connection
1432
+ to the upstream cloud server for both sending and receiving data
1332
1433
  """
1333
1434
  await self.close_notifications()
1334
1435
 
@@ -1338,6 +1439,8 @@ class G90Alarm(G90NotificationProtocol):
1338
1439
  upstream_port=upstream_port,
1339
1440
  local_host=cloud_local_host,
1340
1441
  local_port=cloud_local_port,
1442
+ cloud_host=cloud_host,
1443
+ cloud_port=cloud_port,
1341
1444
  keep_single_connection=keep_single_connection
1342
1445
  )
1343
1446
 
@@ -37,7 +37,7 @@ from .protocol import (
37
37
  )
38
38
  from .const import G90CloudDirection, G90CloudCommand
39
39
  from ..const import (
40
- G90AlertStateChangeTypes, REMOTE_CLOUD_HOST, REMOTE_CLOUD_PORT,
40
+ G90AlertStateChangeTypes,
41
41
  G90AlertTypes, G90AlertSources, G90AlertStates,
42
42
  )
43
43
  from ..definitions.base import G90PeripheralTypes
@@ -212,15 +212,11 @@ class G90CloudHelloDiscoveryRespMessage(G90CloudMessage):
212
212
  _source = G90CloudDirection.CLOUD_DISCOVERY
213
213
  _destination = G90CloudDirection.DEVICE
214
214
 
215
- # Simulated cloud response always contains known IP address of the vendor's
216
- # cloud service - that is, all interactions between alarm panel and
217
- # simulated cloud service will use same IP address for unification (i.e.
218
- # traffic redicrection will always be used to divert panel's cloud traffic
219
- # to the simulated cloud service)
220
- ip_addr: bytes = REMOTE_CLOUD_HOST.encode()
215
+ # The default values are set in `__post_init__()` method below
216
+ ip_addr: bytes = b''
221
217
  flag2: int = 0
222
218
  flag3: int = 0
223
- port: int = REMOTE_CLOUD_PORT
219
+ port: int = 0
224
220
  _timestamp: int = 0 # unix timestamp
225
221
 
226
222
  def __post_init__(self, context: G90CloudMessageContext) -> None:
@@ -45,7 +45,6 @@ from .messages import (
45
45
  )
46
46
  from ..notifications.base import G90NotificationsBase
47
47
  from ..notifications.protocol import G90NotificationProtocol
48
- from ..const import (REMOTE_CLOUD_HOST, REMOTE_CLOUD_PORT)
49
48
 
50
49
 
51
50
  _LOGGER = logging.getLogger(__name__)
@@ -75,6 +74,7 @@ class G90CloudNotifications(G90NotificationsBase, asyncio.Protocol):
75
74
  self,
76
75
  protocol_factory: Callable[[], G90NotificationProtocol],
77
76
  local_host: str, local_port: int,
77
+ cloud_host: str, cloud_port: int,
78
78
  upstream_host: Optional[str] = None,
79
79
  upstream_port: Optional[int] = None,
80
80
  keep_single_connection: bool = True,
@@ -84,6 +84,8 @@ class G90CloudNotifications(G90NotificationsBase, asyncio.Protocol):
84
84
  self._server: Optional[asyncio.Server] = None
85
85
  self._local_host = local_host
86
86
  self._local_port = local_port
87
+ self._cloud_host = cloud_host
88
+ self._cloud_port = cloud_port
87
89
  self._upstream_host = upstream_host
88
90
  self._upstream_port = upstream_port
89
91
  self._keep_single_connection = keep_single_connection
@@ -164,8 +166,8 @@ class G90CloudNotifications(G90NotificationsBase, asyncio.Protocol):
164
166
  device_id=self.device_id,
165
167
  local_host=self._local_host,
166
168
  local_port=self._local_port,
167
- cloud_host=REMOTE_CLOUD_HOST,
168
- cloud_port=REMOTE_CLOUD_PORT,
169
+ cloud_host=self._cloud_host,
170
+ cloud_port=self._cloud_port,
169
171
  upstream_host=self._upstream_host,
170
172
  upstream_port=self._upstream_port,
171
173
  remote_host=host,
@@ -30,9 +30,8 @@ REMOTE_TARGETED_DISCOVERY_PORT = 12900
30
30
  LOCAL_TARGETED_DISCOVERY_PORT = 12901
31
31
  LOCAL_NOTIFICATIONS_HOST = '0.0.0.0'
32
32
  LOCAL_NOTIFICATIONS_PORT = 12901
33
- CLOUD_NOTIFICATIONS_HOST = '0.0.0.0'
34
- CLOUD_NOTIFICATIONS_PORT = 5678
35
- REMOTE_CLOUD_HOST = '47.88.7.61'
33
+ LOCAL_CLOUD_NOTIFICATIONS_HOST = '0.0.0.0'
34
+ LOCAL_CLOUD_NOTIFICATIONS_PORT = 5678
36
35
  REMOTE_CLOUD_PORT = 5678
37
36
  DEVICE_REGISTRATION_TIMEOUT = 30
38
37
  ROOM_ID = 0
@@ -42,7 +41,14 @@ CMD_PAGE_SIZE = 10
42
41
  BUG_REPORT_URL = 'https://github.com/hostcc/pyg90alarm/issues'
43
42
 
44
43
 
45
- class G90Commands(IntEnum):
44
+ class G90CommandsBase(IntEnum):
45
+ """
46
+ Base class for G90Commands and G90SystemCommands to allow proper typing
47
+ with subclasses of `G90BaseCommand`.
48
+ """
49
+
50
+
51
+ class G90Commands(G90CommandsBase):
46
52
  """
47
53
  Defines the alarm panel commands and their codes.
48
54
 
@@ -163,6 +169,27 @@ class G90Commands(IntEnum):
163
169
  PING = 219
164
170
 
165
171
 
172
+ class G90SystemCommands(G90CommandsBase):
173
+ """
174
+ Defines system commands for the G90 alarm panel.
175
+ """
176
+ GET_CONFIGURATION = 0
177
+ SET_CONFIGURATION = 1
178
+ GSM_REBOOT = 1129
179
+ MCU_REBOOT = 1123
180
+ WIFI_REBOOT = 1006
181
+
182
+
183
+ class G90SystemConfigurationCommands(IntEnum):
184
+ """
185
+ Defines sub-commands for getting/setting system configuration.
186
+
187
+ Applicable only to `G90SystemCommands.GET_CONFIGURATION` and
188
+ `G90SystemCommands.SET_CONFIGURATION` commands above.
189
+ """
190
+ SERVER_ADDRESS = 78
191
+
192
+
166
193
  class G90MessageTypes(IntEnum):
167
194
  """
168
195
  Defines message types (codes) from messages coming from the alarm panel.