Compare commits
450 commits
Author | SHA1 | Date | |
---|---|---|---|
db8ea33b68 | |||
03631bcebc | |||
ade3a7c2cf | |||
1f6c7aae60 | |||
a6c7d64511 | |||
af7977bcf7 | |||
0762004838 | |||
|
c8bfbae4f0 | ||
|
d35a9729a4 | ||
a39452124d | |||
fc1d9ad3e6 | |||
7ee869ce42 | |||
df36a9f811 | |||
abf793e703 | |||
132c659d3c | |||
acc4f1c0e3 | |||
d7d5bcdd0c | |||
def8c0dd76 | |||
6cc28a3c3b | |||
93c3327bcd | |||
85d975edc6 | |||
d767cafafe | |||
cee8f9f8e0 | |||
b2e2ae9538 | |||
f296f4b161 | |||
9eab526e47 | |||
8f6404d0b1 | |||
eac26b9b22 | |||
b42c491390 | |||
c7fb97cdc7 | |||
18e99c6f12 | |||
664ce4050d | |||
69583d2e15 | |||
2f840200be | |||
46aef7c8b5 | |||
5ca15937e3 | |||
ce882381c0 | |||
99b35b292f | |||
|
174439a8ed | ||
3a18e9faf4 | |||
f8d76fe4eb | |||
9fa3385766 | |||
|
4fe5322600 | ||
2224681e3c | |||
68c44c0c3c | |||
02a22d4a88 | |||
b9bc4b5fe0 | |||
c006c4b368 | |||
75e1f35523 | |||
9e08b731a5 | |||
|
edfa5ed16f | ||
|
f2628f4f2c | ||
f1faa1141e | |||
2a78f81a7b | |||
86761e7088 | |||
89beab4767 | |||
8db8f3e6c4 | |||
ee0685363e | |||
33276bf697 | |||
99831bf0c7 | |||
641d9c0d41 | |||
95625a401d | |||
a147a4d37a | |||
177c697f83 | |||
eb486df1ee | |||
a4c70f1e4d | |||
c16f8aa39e | |||
570f1d0cf6 | |||
|
4313b6e883 | ||
00fabf3853 | |||
cad184fc1f | |||
928bb3667a | |||
782be7794b | |||
40dc739d09 | |||
4821fe086b | |||
8d621b2688 | |||
1355f7a3f6 | |||
3ce98305f0 | |||
0a4cadba3e | |||
9e46ab0b60 | |||
c74d2fb124 | |||
ca573cb980 | |||
75deb0393d | |||
14f1c44378 | |||
fdd46edb6a | |||
b9738d88df | |||
f92d19a24e | |||
ce0ca2e9ee | |||
7f157d0234 | |||
8a3df30001 | |||
82274d3d12 | |||
ab6c85c2a6 | |||
f8564c19cd | |||
bf519babb0 | |||
632a7ab792 | |||
1d3d741212 | |||
b171cb1012 | |||
352d421e61 | |||
6f224cbda9 | |||
adb472da7f | |||
|
347a4d2555 | ||
cef310ffd7 | |||
96588df83b | |||
02f6845e72 | |||
40834f7ce5 | |||
3d6af00ee6 | |||
b06867dc5d | |||
22dad79dd5 | |||
211025cedb | |||
|
da27ed7546 | ||
775478fff0 | |||
093bac3a60 | |||
b3606a8ac3 | |||
4902475caf | |||
7687e52058 | |||
eae4b4f62a | |||
296458ece3 | |||
71af6e87e0 | |||
c659ec507f | |||
fb1953ce34 | |||
34a108bbcb | |||
5be6ca50f0 | |||
090e7d6de8 | |||
6f85318868 | |||
8f256e4077 | |||
4ed80a0945 | |||
ca90060ba1 | |||
49ad3261f1 | |||
73e16fa6b1 | |||
d860d8aef1 | |||
|
36aee6f787 | ||
|
04ff008800 | ||
|
47ba13d985 | ||
4ada1c663d | |||
210cefc1a4 | |||
e43147e6a3 | |||
66c1e59c61 | |||
0e3845ef88 | |||
833f810d4b | |||
d190560536 | |||
e0e3081eb1 | |||
708ce210ba | |||
d0707b6479 | |||
66445cb4e4 | |||
f93df2fd49 | |||
0d806eeb6e | |||
36fdcf6963 | |||
3932fb56c4 | |||
cde5e4e4f9 | |||
b4f9472fc5 | |||
9e00428568 | |||
b4258faa29 | |||
9e4daacd74 | |||
61c5bae527 | |||
82fcf86900 | |||
a5f02c6a30 | |||
076cc6a63e | |||
d0a579cf4b | |||
33af368940 | |||
531be19a66 | |||
ef36c76f10 | |||
571a8ca9d1 | |||
2800bac3fb | |||
d1b7b1768c | |||
1a7d0a3b07 | |||
|
b27c9ee630 | ||
04a23d555d | |||
dae8cf0111 | |||
67702d3cc8 | |||
6df336465f | |||
ffb7a3e5a3 | |||
151d0008ee | |||
10eb0c12aa | |||
aa5cbbce28 | |||
53fb254c57 | |||
727094467d | |||
|
6f9428487f | ||
3e777bd19f | |||
7ab4da6156 | |||
7922ae4801 | |||
94c35e210e | |||
34892e227a | |||
856eb720b0 | |||
94ad14f23f | |||
c8fa61cc4f | |||
8ab72e8f94 | |||
6b5b50d186 | |||
56f8d85feb | |||
51de11da25 | |||
ca2fd60950 | |||
e508d453ba | |||
12e1c60f6c | |||
8c3ba9f367 | |||
41acb02eb0 | |||
748609c6f4 | |||
ee7407a7cc | |||
201bb5986f | |||
51e6d95205 | |||
c78420749b | |||
adbde5a681 | |||
80aa9b968d | |||
b922f29bb8 | |||
4dd5ea71d6 | |||
297c18e176 | |||
a1e2bf9b3a | |||
c2784c400f | |||
b49865181b | |||
77c6f699a0 | |||
add1bd05cb | |||
4c69a1e579 | |||
5832bc68d5 | |||
8168cd3ab3 | |||
be497548c1 | |||
b3c28f6773 | |||
9b9334682f | |||
06ca5be54b | |||
30d5cdcb00 | |||
2427cceb5e | |||
73cc742dfb | |||
ade04a6ea1 | |||
3cf91a3f27 | |||
f9598977db | |||
7325c87068 | |||
1a70c6ea0b | |||
021dd32190 | |||
db80240209 | |||
1eedcaf2be | |||
86e6ffd18d | |||
15d2e1116b | |||
452533db17 | |||
b8979040da | |||
816492d3b2 | |||
0bddbb0bca | |||
314c01ef97 | |||
4c5a52417e | |||
23a5cd519a | |||
b57a241f52 | |||
2abfb2964c | |||
4858dbc060 | |||
|
2834ed2a7c | ||
|
165f85f8dc | ||
|
7f24b82281 | ||
7e1d232942 | |||
31a7ecee03 | |||
346bd9602d | |||
673351db51 | |||
86ebc4fae9 | |||
118540db8c | |||
a1d6c4fbe4 | |||
2bb07ae191 | |||
1920bd80a3 | |||
269000ab85 | |||
0ad4597daf | |||
e08d4c21b8 | |||
254045283b | |||
41b8b738d6 | |||
0411145ebf | |||
cca4444af2 | |||
3332c5c573 | |||
dca81de5a3 | |||
1c84891df6 | |||
fca0c2265c | |||
fda8872a15 | |||
ef63c3769e | |||
fddf2006e4 | |||
a0bf227573 | |||
32ec76611a | |||
13f4edbc92 | |||
8a1a8bc9a0 | |||
7d4d9eb438 | |||
e2415928a7 | |||
80112652bf | |||
92c98f26e5 | |||
8e2d590e62 | |||
e2e7011e53 | |||
|
31b110a06c | ||
|
717ad5d88c | ||
|
cc839620cf | ||
|
cebd79079b | ||
|
7a7e00bea2 | ||
|
7ff08a9587 | ||
|
370a7d4c15 | ||
|
85f56cf98c | ||
|
6ae76f1f38 | ||
|
67d3b40772 | ||
|
d2887b7454 | ||
d10649e1f1 | |||
|
e127f2597c | ||
|
1777ebb051 | ||
|
3eb7f01d38 | ||
|
57304e8d7e | ||
|
b4fc734a15 | ||
|
bc3e0b8634 | ||
|
1726cbd96a | ||
|
e5c8adfc1b | ||
|
049a48e156 | ||
|
5d4f4b0378 | ||
|
0db997c6a0 | ||
|
5e7c6c26c9 | ||
|
2de1e3ebe6 | ||
|
79179746a7 | ||
|
fc718c907a | ||
|
0cd120f492 | ||
|
c3f01c198f | ||
|
229b2de566 | ||
|
dea547a491 | ||
|
1036358b28 | ||
|
8f477fa335 | ||
|
3c6f3c5a21 | ||
|
0902099855 | ||
|
3c9a633907 | ||
|
1e193f8346 | ||
|
9af02ba886 | ||
|
221bcc058b | ||
|
04cb2324aa | ||
|
887a0e5e88 | ||
|
cc3e52c69d | ||
|
243e56b194 | ||
|
ecf6a844dd | ||
|
62b651789a | ||
|
ba8e5ef6a0 | ||
|
9dacd2d3c9 | ||
|
39abdfe40a | ||
|
0c0e7411f7 | ||
|
9179f35a82 | ||
|
470bd62af7 | ||
|
c7711d75a1 | ||
|
711ea543bb | ||
|
43f71ed47b | ||
|
a07cb6ec3d | ||
|
45d998130b | ||
|
cc36325ca6 | ||
|
c5dc9333f0 | ||
|
8a7f783032 | ||
|
77530b4a06 | ||
|
6d7f1502ce | ||
|
8279f22940 | ||
|
1c84659e34 | ||
|
37e006d86e | ||
|
8d7e790eda | ||
|
daaa0050d1 | ||
|
287b6303ae | ||
|
9b23ab7015 | ||
|
7947c1031d | ||
|
9e6c40d393 | ||
|
f3a9dc4ef5 | ||
|
05b0a7f14d | ||
|
6ad5397a25 | ||
|
0a9c4fc3a7 | ||
|
b30145dfc9 | ||
|
e9d9ef252f | ||
|
f2a654bdec | ||
|
d92e630314 | ||
|
07336d3272 | ||
|
00012aacae | ||
|
959cc8b75b | ||
|
df1e03f0af | ||
|
b21193dc74 | ||
|
9ad6188b5d | ||
|
31f2c5152c | ||
|
c269c62fe6 | ||
|
6e6092e4b2 | ||
|
ac42f7eba4 | ||
|
258a9b57ce | ||
|
28409b8688 | ||
|
c12e7bab90 | ||
|
09f9e974b1 | ||
|
c0f7cc0782 | ||
|
0af326fa11 | ||
|
beeb7dca7c | ||
|
1e972ded99 | ||
|
a650840429 | ||
|
4d0d467292 | ||
|
83122becdb | ||
|
9623752e19 | ||
|
53ddbad7ce | ||
|
ac02becba8 | ||
|
13642cc42e | ||
|
d6f653d834 | ||
|
dc254d6474 | ||
|
8a1f49a906 | ||
|
6dce4c59f6 | ||
|
ce42f5aada | ||
|
e9d4ed3911 | ||
|
2ceb3511b3 | ||
|
d27b23ec5a | ||
|
9402e4f65f | ||
|
a4c6028dc4 | ||
|
aeed86e377 | ||
|
907bc0f75b | ||
|
d7806757c5 | ||
|
ca168828de | ||
|
6c24783df7 | ||
|
a65a6e9d02 | ||
|
681e9f1703 | ||
|
737c135996 | ||
|
edd2235cbc | ||
|
dc1b54961f | ||
|
3c3ee09d90 | ||
|
e533484505 | ||
|
1681f80728 | ||
|
f1ab923bfe | ||
|
d7c3ad64f5 | ||
|
ca0c4e3089 | ||
|
beceb39b0c | ||
|
9f1128e2c0 | ||
|
e77e5bb3d8 | ||
|
0d182820e8 | ||
|
f7df1d2f6e | ||
|
cd8732dc8f | ||
|
b8917de52f | ||
|
aa631deb88 | ||
|
2e6388f6f4 | ||
|
5d2e74eb97 | ||
|
40269ddaad | ||
|
af614480b8 | ||
|
7a7c065754 | ||
|
6bed284e8b | ||
|
289eebd6a0 | ||
|
f54a5cdf87 | ||
|
43ef4bccdf | ||
|
1bb22d818a | ||
|
fa0a5805be | ||
|
bf92d66be1 | ||
|
a9509fc959 | ||
|
2dc8fe9437 | ||
|
a0d97c0f18 | ||
|
d8f7b15111 | ||
|
ddedcd647c | ||
|
63ad4bfdce | ||
|
1abcb1de4b | ||
|
8eb457017a | ||
|
019bcbf90a | ||
|
403076e6ab | ||
|
988601b10e | ||
|
cbae8132ed | ||
|
5302d3143e | ||
|
4b86b8ef54 | ||
|
62e5d03931 | ||
|
d5292e068d | ||
|
40c2c157ec | ||
|
ead5b94942 | ||
|
9b2c6ef62d | ||
|
ccc01e1823 | ||
|
9c52d96c08 | ||
|
06258b8304 | ||
|
0659996c48 | ||
|
af2dbf899d | ||
|
20b095232d | ||
|
72bb159263 |
2130 changed files with 149524 additions and 22092 deletions
12
.github/FUNDING.yml
vendored
12
.github/FUNDING.yml
vendored
|
@ -1,12 +0,0 @@
|
||||||
# These are supported funding model platforms
|
|
||||||
|
|
||||||
github: [BlackLight]
|
|
||||||
patreon: # Replace with a single Patreon username
|
|
||||||
open_collective: # Replace with a single Open Collective username
|
|
||||||
ko_fi: # Replace with a single Ko-fi username
|
|
||||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
|
||||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
|
||||||
liberapay: # Replace with a single Liberapay username
|
|
||||||
issuehunt: # Replace with a single IssueHunt username
|
|
||||||
otechie: # Replace with a single Otechie username
|
|
||||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
|
31
.github/workflows/python-publish.yml
vendored
31
.github/workflows/python-publish.yml
vendored
|
@ -1,31 +0,0 @@
|
||||||
# This workflows will upload a Python Package using Twine when a release is created
|
|
||||||
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
|
|
||||||
|
|
||||||
name: Upload Python Package
|
|
||||||
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [created]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: '3.x'
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
pip install setuptools wheel twine
|
|
||||||
- name: Build and publish
|
|
||||||
env:
|
|
||||||
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
|
|
||||||
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
|
||||||
run: |
|
|
||||||
python setup.py sdist bdist_wheel
|
|
||||||
twine upload dist/*
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -3,7 +3,7 @@
|
||||||
*.pyc
|
*.pyc
|
||||||
__pycache__
|
__pycache__
|
||||||
build/
|
build/
|
||||||
dist/
|
/dist/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
package.sh
|
package.sh
|
||||||
.pypirc
|
.pypirc
|
||||||
|
@ -17,3 +17,5 @@ platypush/backend/http/static/js/lib/vue.js
|
||||||
platypush/notebooks
|
platypush/notebooks
|
||||||
platypush/requests
|
platypush/requests
|
||||||
/http-client.env.json
|
/http-client.env.json
|
||||||
|
/platypush/backend/http/static/css/dist
|
||||||
|
/tests/etc/dashboards
|
||||||
|
|
6
.gitmodules
vendored
6
.gitmodules
vendored
|
@ -1,9 +1,3 @@
|
||||||
[submodule "docs/wiki"]
|
|
||||||
path = docs/wiki
|
|
||||||
url = https://github.com/BlackLight/platypush.wiki.git
|
|
||||||
[submodule "platypush/plugins/gpio/sensor/ir/mlx90640/lib"]
|
[submodule "platypush/plugins/gpio/sensor/ir/mlx90640/lib"]
|
||||||
path = platypush/plugins/camera/ir/mlx90640/lib
|
path = platypush/plugins/camera/ir/mlx90640/lib
|
||||||
url = https://github.com/pimoroni/mlx90640-library
|
url = https://github.com/pimoroni/mlx90640-library
|
||||||
[submodule "webext"]
|
|
||||||
path = webext
|
|
||||||
url = https://github.com/BlackLight/platypush-webext.git
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
build:
|
|
||||||
image: latest
|
|
||||||
|
|
||||||
python:
|
|
||||||
version: 3.6
|
|
||||||
setup_py_install: true
|
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
pyyaml
|
|
||||||
requests
|
|
||||||
flask
|
|
||||||
redis
|
|
||||||
python-dateutil
|
|
||||||
websockets
|
|
||||||
bcrypt
|
|
||||||
sqlalchemy
|
|
||||||
croniter
|
|
22
.travis.yml
22
.travis.yml
|
@ -1,22 +0,0 @@
|
||||||
language: python
|
|
||||||
dist: xenial
|
|
||||||
python:
|
|
||||||
- "3.7"
|
|
||||||
|
|
||||||
install: "pip install -r .travis.requirements"
|
|
||||||
|
|
||||||
script: ./run_tests.sh
|
|
||||||
|
|
||||||
notifications:
|
|
||||||
email:
|
|
||||||
recipients:
|
|
||||||
- blacklight86@gmail.com
|
|
||||||
on_success: change
|
|
||||||
on_failure: change
|
|
||||||
|
|
||||||
services:
|
|
||||||
- redis-server
|
|
||||||
|
|
||||||
git:
|
|
||||||
submodules: false
|
|
||||||
|
|
177
CHANGELOG.md
Normal file
177
CHANGELOG.md
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
Given the high speed of development in the first phase, changes are being reported only starting from v0.20.2.
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
- Added `music.spotify.connect` backend to emulate a Spotify Connect receiver through Platypush.
|
||||||
|
|
||||||
|
## [0.21.1] - 2021-06-22
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added `switchbot` plugin to interact with Switchbot devices over the cloud API instead of
|
||||||
|
directly accessing the device's Bluetooth interface.
|
||||||
|
|
||||||
|
- Added `marshmallow` dependency - it will be used from now own to dump and document schemas
|
||||||
|
and responses instead of the currently mixed approach with `Response` objects and plain
|
||||||
|
dictionaries and lists.
|
||||||
|
|
||||||
|
- Support for custom MQTT timeout on all the `zwavejs2mqtt` calls.
|
||||||
|
|
||||||
|
- Added generic joystick backend `backend.joystick.jstest` which uses `jstest` from the
|
||||||
|
standard `joystick` system package to read the state of joysticks not compatible with
|
||||||
|
`python-inputs`.
|
||||||
|
|
||||||
|
- Added PWM PCA9685 plugin.
|
||||||
|
|
||||||
|
- Added Linux native joystick plugin, ``backend.joystick.linux``, for the cases where
|
||||||
|
``python-inputs`` doesn't work and ``jstest`` is too slow.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `switch.switchbot` plugin renamed to `switchbot.bluetooth` plugin, while the new plugin
|
||||||
|
that uses the Switchbot API is simply named `switchbot`.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- More robust reconnection logic on the Pushbullet backend in case of websocket errors.
|
||||||
|
|
||||||
|
## [0.21.0] - 2021-05-06
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Support for custom PopcornTime API mirror/base URL.
|
||||||
|
|
||||||
|
- Full support for TV series search.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed torrent search (now using a different PopcornTime API mirror).
|
||||||
|
|
||||||
|
- Migrated SASS engine from `node-sass` (currently deprecated and broken on Node 16) to `sass`.
|
||||||
|
|
||||||
|
- Fixed alignment of Z-Wave UI header on Chrome/Webkit.
|
||||||
|
|
||||||
|
## [0.20.10] - 2021-04-28
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed zwave/zwavejs2mqtt interoperability.
|
||||||
|
|
||||||
|
## [0.20.9] - 2021-04-12
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added zwavejs2mqtt integration (see [#186](https://git.platypush.tech/platypush/platypush/-/issues/186).
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Major LINT fixes.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Removed unmaintained integrations: TorrentCast and Booking.com
|
||||||
|
|
||||||
|
## [0.20.8] - 2021-04-04
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added `<Camera>` dashboard widget.
|
||||||
|
|
||||||
|
- Added support for custom dashboard widgets with customized (see https://git.platypush.tech/platypush/platypush/-/wikis/Backends#creating-custom-widgets).
|
||||||
|
|
||||||
|
- Added support for controls on `music.mpd` dashboard widget.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed zigbee2mqtt backend error in case of messages with empty payload (see [#184](https://git.platypush.tech/platypush/platypush/-/issues/184)).
|
||||||
|
|
||||||
|
- Fixed compatibility with all versions of websocket-client - versions >= 0.58.0 pass a `WebSocketApp` object as a first
|
||||||
|
argument to the callbacks, as well as versions < 0.54.0 do, but the versions in between don't pass this argument.
|
||||||
|
|
||||||
|
## [0.20.7] - 2021-03-26
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed race condition on `media.vlc.stop` when clearing the VLC instance.
|
||||||
|
|
||||||
|
- Fixed dashboard widgets custom classes being propagated both to the container and to the widget content [see #179]
|
||||||
|
|
||||||
|
- Fixed compatibility with SQLAlchemy >= 1.4.
|
||||||
|
|
||||||
|
## [0.20.6] - 2021-03-16
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added `log.http` backend to monitor changes to HTTP log files
|
||||||
|
(see [#167](https://git.platypush.tech/platypush/platypush/-/issues/167)).
|
||||||
|
|
||||||
|
- Added `file.monitor` backend, which replaces the `inotify` backend
|
||||||
|
(see [#172](https://git.platypush.tech/platypush/platypush/-/issues/172)).
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Removed legacy `pusher` script and `local` backend.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed support for Z-Wave switches.
|
||||||
|
|
||||||
|
- Fixed possible race condition on VLC stop.
|
||||||
|
|
||||||
|
## [0.20.5] - 2021-03-12
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for a static list of devices to actively scan to the `bluetooth.scanner` backend
|
||||||
|
(see [#174](https://git.platypush.tech/platypush/platypush/-/issues/174)).
|
||||||
|
|
||||||
|
- Added `weather.openweathermap` plugin and backend, which replaces `weather.darksky`, since the
|
||||||
|
Darksky API will be completely shut down by the end of 2021.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Cron expressions should adhere to the UNIX cronjob standard and use the machine local time,
|
||||||
|
not UTC, as a reference (closes [#173](https://git.platypush.tech/platypush/platypush/-/issues/173)).
|
||||||
|
|
||||||
|
- Better management of Z-Wave values types from the UI.
|
||||||
|
|
||||||
|
- Disable logging for `ZwaveValueEvent` events - they tend to be very verbose and
|
||||||
|
can impact the performance on slower devices. They will still be published to the
|
||||||
|
websocket clients though, so you can still debug Z-Wave values issues from the browser
|
||||||
|
developer console (enable debug traces).
|
||||||
|
|
||||||
|
- Added suffix to the `zigbee.mqtt` backend default `client_id` to prevent clashes with
|
||||||
|
the default `mqtt` backend `client_id`.
|
||||||
|
|
||||||
|
## [0.20.4] - 2021-03-08
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added SmartThings integration.
|
||||||
|
- Support for custom Redis message queue name over the `--redis-queue` argument.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Refactored tests to use `pytest` instead of `unittest`.
|
||||||
|
- Some major bug fixes on procedures and hooks context evaluation.
|
||||||
|
|
||||||
|
## [0.20.3] - 2021-02-28
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Several bug fixes on the VLC plugin, including proper management of stop/end-of-stream, volume set and missing integration requirements in `requirements.txt` and `setup.py`.
|
||||||
|
|
||||||
|
## [0.20.2] - 2021-02-27
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- More stable ZeroConf backends registration logic in case of partial or missing results.
|
||||||
|
- Improved and refactored integration tests.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Support for passing context variables (${}) from YAML procedures/hooks/crons to Python procedure/hooks/crons.
|
||||||
|
- New integration test for testing procedures.
|
37
CONTRIBUTING.md
Normal file
37
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
Thanks for considering contributing your work to make Platypush a better product!
|
||||||
|
|
||||||
|
Contributions are more than welcome, and the follow the standard Gitlab procedure:
|
||||||
|
|
||||||
|
- [Fork the repo](https://git.platypush.tech/platypush/platypush).
|
||||||
|
- Prepare your changes.
|
||||||
|
- [Submit a merge request](https://git.platypush.tech/platypush/platypush/-/merge_requests).
|
||||||
|
|
||||||
|
Guidelines:
|
||||||
|
|
||||||
|
- The code should ideally have no LINT warnings/issues.
|
||||||
|
|
||||||
|
- Project conventions:
|
||||||
|
- 4 spaces to indent.
|
||||||
|
- RST format for classes and methods documentation
|
||||||
|
- Run `python generate_missing_docs.py` if you are adding new plugins/backends to automatically
|
||||||
|
generate the doc templates. Make sure that you don't accidentally remove lines elements from
|
||||||
|
the docs because of missing dependencies on the machine you use to generate the docs.
|
||||||
|
- Naming conventions: plugin classes are named `<Module>Plugin` and backend classes are
|
||||||
|
named `<Module>Backend`, with `<Module>` being the (camel-case) representation of the
|
||||||
|
Python module of the plugin without the prefix - for example, the plugin under
|
||||||
|
`platypush.plugins.light.hue` must be named `LightHuePlugin`.
|
||||||
|
|
||||||
|
- If possible, [add a test](https://git.platypush.tech/platypush/platypush/-/tree/master/tests)
|
||||||
|
for the new functionality. If you have built a new functionality that works with some specific
|
||||||
|
device or service then it's not required to write a test that mocks the whole service, but if
|
||||||
|
you are changing some of the core entities (e.g. requests, events, procedures, hooks, crons
|
||||||
|
or the bus) then make sure to add tests and not to break the existing tests.
|
||||||
|
|
||||||
|
- If the feature requires an optional dependency then make sure to document it:
|
||||||
|
|
||||||
|
- In the class docstring (see other plugins and backends for examples)
|
||||||
|
- In [`setup.py`](https://git.platypush.tech/platypush/platypush/-/blob/master/setup.py#L72) as
|
||||||
|
an `extras_require` entry
|
||||||
|
- In [`requirements.txt`](https://git.platypush.tech/platypush/platypush/-/blob/master/requirements.txt) -
|
||||||
|
if the feature is optional then leave it commented and add a one-line comment to explain which
|
||||||
|
plugin or backend requires it.
|
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2017, 2018 Fabio Manganiello
|
Copyright (c) 2017, 2020 Fabio Manganiello
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
recursive-include platypush/backend/http/static *
|
recursive-include platypush/backend/http/webapp/dist *
|
||||||
recursive-include platypush/backend/http/templates *
|
|
||||||
include platypush/plugins/http/webpage/mercury-parser.js
|
include platypush/plugins/http/webpage/mercury-parser.js
|
||||||
|
|
539
README.md
539
README.md
|
@ -1,68 +1,509 @@
|
||||||
Platypush
|
Platypush
|
||||||
=========
|
=========
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/BlackLight/platypush.svg?branch=master)](https://travis-ci.org/BlackLight/platypush)
|
[![Build Status](https://ci.platypush.tech/status.svg)](https://ci.platypush.tech/latest.log)
|
||||||
[![Documentation Status](https://readthedocs.org/projects/platypush/badge/?version=latest)](https://platypush.readthedocs.io/en/latest/?badge=latest)
|
[![Documentation Status](https://ci.platypush.tech/docs/status.svg)](https://ci.platypush.tech/docs/latest.log)
|
||||||
[![pip version](https://img.shields.io/pypi/v/platypush.svg?style=flat)](https://pypi.python.org/pypi/platypush/)
|
[![pip version](https://img.shields.io/pypi/v/platypush.svg?style=flat)](https://pypi.python.org/pypi/platypush/)
|
||||||
|
[![License](https://img.shields.io/github/license/BlackLight/platypush.svg)](https://git.platypush.tech/platypush/platypush/-/blob/master/LICENSE.txt)
|
||||||
|
[![Last Commit](https://img.shields.io/github/last-commit/BlackLight/platypush.svg)](https://git.platypush.tech/platypush/platypush/-/commits/master/)
|
||||||
|
[![Contributions](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://git.platypush.tech/platypush/platypush/-/blob/master/CONTRIBUTING.md)
|
||||||
|
[![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/BlackLight/platypush.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/BlackLight/platypush/context:python)
|
||||||
|
[![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/BlackLight/platypush.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/BlackLight/platypush/context:javascript)
|
||||||
|
|
||||||
Advised read: [**Getting started with Platypush**](https://medium.com/@automationguru/automate-your-house-your-life-and-everything-else-around-with-platypush-dba1cd13e3f6) (Medium article).
|
- Recommended read: [**Getting started with Platypush**](https://blog.platypush.tech/article/Ultimate-self-hosted-automation-with-Platypush).
|
||||||
[Reddit channel](https://www.reddit.com/r/platypush)
|
|
||||||
|
|
||||||
Imagine Platypush as some kind of [IFTTT](https://ifttt.com) on steroids - or [Tasker](https://tasker.joaoapps.com/), or [Microsoft Flow](https://flow.microsoft.com), or [PushBullet](https://pushbullet.com) on steroids.
|
- The [blog](https://blog.platypush.tech) is in general a good place to get more insights on what you can build with it and inspiration about possible usages.
|
||||||
Platypush aims to turn any device in a smart hub that can control things, interact with cloud services and send messages to other devices. It's a general-purpose lightweight platform to process any request and run any logic triggered by custom events.
|
|
||||||
|
|
||||||
Imagine the ability of running any task you like, or automate any routine you like, on any of your devices. And the flexibility of executing actions through a cloud service, with the power of running them from your laptop, Raspberry Pi, smart home device or smartphone.
|
- The [wiki](https://git.platypush.tech/platypush/platypush/-/wikis/home) also contains many resources on getting started.
|
||||||
|
|
||||||
|
- Extensive documentation for all the available integrations and messages [is available](https://docs.platypush.tech/).
|
||||||
|
|
||||||
|
- If you have issues/feature requests/enhancement ideas please [create an issue](https://git.platypush.tech/platypush/platypush/-/issues).
|
||||||
|
|
||||||
|
- A [Reddit channel](https://www.reddit.com/r/platypush) is also available for more general questions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Platypush is a general-purpose extensible platform for automation and integration across multiple services and devices.
|
||||||
|
|
||||||
|
It enables users to create their own self-hosted pieces of automation based on events (*if this happens then do that*)
|
||||||
|
and it provides a comprehensive and customizable user interface that collects everything you need to visualize and
|
||||||
|
control under one roof.
|
||||||
|
|
||||||
|
It takes some concepts from [IFTTT](https://ifttt.com), [Tasker](https://tasker.joaoapps.com/),
|
||||||
|
[Microsoft Flow](https://flow.microsoft.com), [PushBullet](https://pushbullet.com) and
|
||||||
|
[Home Assistant](https://www.home-assistant.io/) to provide an environment where the user can easily connect things
|
||||||
|
together.
|
||||||
|
|
||||||
|
Its ideal home is a single-board computer like a RaspberryPi that you can configure to orchestrate any home automation
|
||||||
|
and cloud automation in your own living room or garage, but it can easily run on any device that can run a Python
|
||||||
|
interpreter, and the bar for the hardware requirements is very low as well - I use it to run pieces of automation on
|
||||||
|
devices as powerful as a RaspberryPi Zero or an old Nokia N900 with Linux.
|
||||||
|
|
||||||
You can use Platypush to do things like:
|
You can use Platypush to do things like:
|
||||||
|
|
||||||
- Control your smart home lights
|
- [Control your smart home lights](https://blog.platypush.tech/article/Ultimate-self-hosted-automation-with-Platypush)
|
||||||
- Control your favourite music player
|
- [Control your music and synchronize it to multiple devices](https://blog.platypush.tech/article/Build-your-open-source-multi-room-and-multi-provider-sound-server-with-Platypush-Mopidy-and-Snapcast)
|
||||||
- Interact with your voice assistant
|
- [Create custom and privacy-secure voice assistants that run custom hooks on your phrases](https://blog.platypush.tech/article/Build-custom-voice-assistants)
|
||||||
- Get events from your Google or Facebook calendars
|
- Build integrations between [sensors](https://docs.platypush.tech/en/latest/platypush/backend/sensor.html),
|
||||||
- Read data from your sensors and trigger custom events whenever they go above or below some custom thresholds
|
[cameras](https://docs.platypush.tech/en/latest/platypush/plugins/camera.pi.html),
|
||||||
- Control the motors of your robot
|
[microphones](https://docs.platypush.tech/en/latest/platypush/plugins/sound.html) and
|
||||||
- Send automated emails
|
[machine learning models](https://docs.platypush.tech/en/latest/platypush/plugins/tensorflow.html) to create smart
|
||||||
- Synchronize the clipboards on your devices
|
pieces of automation for e.g.
|
||||||
- Control your smart switches
|
[people detection](https://blog.platypush.tech/article/Detect-people-with-a-RaspberryPi-a-thermal-camera-Platypush-and-a-pinch-of-machine-learning)
|
||||||
- Implement custom text-to-speech commands
|
or [sound detection](https://blog.platypush.tech/article/Create-your-smart-baby-monitor-with-Platypush-and-Tensorflow)
|
||||||
- Build any kind of interaction with your Android device using Tasker
|
- [Get events from your Google or Facebook calendars](https://docs.platypush.tech/en/latest/platypush/plugins/calendar.html)
|
||||||
- Play local videos, YouTube videos and torrent links
|
- [Read data from your sensors and trigger custom events whenever they go above or below some custom thresholds](https://blog.platypush.tech/article/How-to-build-your-personal-infrastructure-for-data-collection-and-visualization)
|
||||||
- Get weather forecast for your location
|
- [Control and automate a self-built robot](https://docs.platypush.tech/en/latest/platypush/plugins/gpio.zeroborg.html)
|
||||||
|
- [Deliver automated newsletters from custom RSS digests](https://blog.platypush.tech/article/Deliver-customized-newsletters-from-RSS-feeds-with-Platypush)
|
||||||
|
- [Synchronize the clipboards on your devices](https://docs.platypush.tech/en/latest/platypush/plugins/clipboard.html)
|
||||||
|
- [Control your smart switches](https://docs.platypush.tech/en/latest/platypush/plugins/switch.html)
|
||||||
|
- [Implement automated custom text-to-speech routines](https://docs.platypush.tech/en/latest/platypush/plugins/tts.html)
|
||||||
|
- [Build any kind of interactions and automation routines with your Android device using Tasker](https://blog.platypush.tech/article/How-to-build-your-personal-infrastructure-for-data-collection-and-visualization)
|
||||||
|
- Play [local videos](https://docs.platypush.tech/en/latest/platypush/plugins/media.mpv.html), YouTube videos and torrent media from any device and service, to any device
|
||||||
|
- [Get weather forecast events for your location and build automation routines on them](https://docs.platypush.tech/en/latest/platypush/plugins/weather.darksky.html)
|
||||||
|
- [Create a custom single hub for Zigbee and Z-Wave smart devices](https://blog.platypush.tech/article/Transform-a-RaspberryPi-into-a-universal-Zigbee-and-Z-Wave-bridge)
|
||||||
- Build your own web dashboard with calendar, weather, news and music controls (basically, anything that has a Platypush web widget)
|
- Build your own web dashboard with calendar, weather, news and music controls (basically, anything that has a Platypush web widget)
|
||||||
- ...and much more (basically, anything that comes with a [Platypush plugin](https://platypush.readthedocs.io/en/latest/plugins.html))
|
- ...and much more (basically, anything that comes with a [Platypush plugin](https://docs.platypush.tech/en/latest/plugins.html))
|
||||||
|
|
||||||
Imagine the ability of executing all the actions above through messages delivered through:
|
## Architecture
|
||||||
|
|
||||||
- A web interface
|
The architecture of Platypush consists of a few simple pieces, orchestrated by a configuration file stored by default
|
||||||
- A JSON-RPC API
|
under [`~/.config/platypush/config.yaml`](https://git.platypush.tech/platypush/platypush/-/blob/master/examples/conf/config.yaml):
|
||||||
- Raw TCP messages
|
|
||||||
- Web sockets
|
|
||||||
- [PushBullet](https://pushbullet.com)
|
|
||||||
- [Kafka](https://kafka.apache.org)
|
|
||||||
- [Redis](https://redis.io)
|
|
||||||
- [MQTT](https://mqtt.org)
|
|
||||||
- ...amd much more (basically, anything that comes with a [Platypush backend](https://platypush.readthedocs.io/en/latest/backends.html))
|
|
||||||
|
|
||||||
Imagine the ability of building custom event hooks to automatically trigger any actions:
|
### [Plugins](https://docs.platypush.tech/en/latest/plugins.html)
|
||||||
|
|
||||||
- When your voice assistant recognizes some text
|
They are integrations that do things - like
|
||||||
- When you start playing a new song
|
[modify files](https://docs.platypush.tech/en/latest/platypush/plugins/file.html),
|
||||||
- When a new event is added to your calendar
|
[train and evaluate machine learning models](https://docs.platypush.tech/en/latest/platypush/plugins/tensorflow.html),
|
||||||
- When a new article is published on your favourite feed
|
[control cameras](https://docs.platypush.tech/en/latest/platypush/plugins/camera.pi.html),
|
||||||
- When the weather conditions change
|
[read sensors](https://docs.platypush.tech/en/latest/platypush/plugins/gpio.sensor.dht.html),
|
||||||
- When your press a [Flic button](https://flic.io) with a certain pattern
|
[parse a web page](https://docs.platypush.tech/en/latest/platypush/plugins/http.webpage.html),
|
||||||
- When you receive a new push on your Pushbullet account
|
[control lights](https://docs.platypush.tech/en/latest/platypush/plugins/light.hue.html),
|
||||||
- When your GPS signal enters a certain area
|
[send emails](https://docs.platypush.tech/en/latest/platypush/plugins/mail.smtp.html),
|
||||||
- Whenever a new MIDI event is received (yes, you heard well :) )
|
[control Chromecasts](https://docs.platypush.tech/en/latest/platypush/plugins/media.chromecast.html),
|
||||||
- Whenever a sensor sends new data
|
[run voice queries](https://docs.platypush.tech/en/latest/platypush/plugins/assistant.google.html),
|
||||||
- At a specific date or time
|
[handle torrent transfers](https://docs.platypush.tech/en/latest/platypush/plugins/torrent.html) or
|
||||||
- ...and so on (basically, anything can send events that can be used to build hooks)
|
control [Zigbee](https://docs.platypush.tech/en/latest/platypush/plugins/zigbee.mqtt.html) or
|
||||||
|
[Z-Wave](https://docs.platypush.tech/en/latest/platypush/plugins/zwave.html) devices.
|
||||||
|
|
||||||
Imagine the ability of running the application, with lots of those bundled features, on any device that can comes with Python (_only compatible with version 3.5 and higher_). Platypush has been designed with performance in mind, it's been heavily tested on slower devices like Raspberry Pis, and it can run the web server features, multiple backends and plugins quite well even on a Raspberry Pi Zero - it's even been tested with some quite impressive performance on an older [Nokia N900](https://en.wikipedia.org/wiki/Nokia_N900), and of course you can run it on any laptop, desktop, server environment. It's been developed mainly with IoT in mind (and some of its features overlap with IoT frameworks like [Mozilla IoT](https://iot.mozilla.com) and [Android Things](https://developer.android.com/things/)), but nothing prevents you from automating any task on any device and environment.
|
The configuration of a plugin matches one-on-one that of its documented class constructor, so it's very straightforward
|
||||||
|
to write a configuration for a plugin by reading its documentation:
|
||||||
|
|
||||||
To get started:
|
```yaml
|
||||||
|
light.hue:
|
||||||
|
# Groups that will be controlled by default
|
||||||
|
groups:
|
||||||
|
- Living Room
|
||||||
|
- Hall
|
||||||
|
```
|
||||||
|
|
||||||
- [Wiki](https://github.com/BlackLight/platypush/wiki) for installation notes, quick start, examples and architecture reference
|
### Actions
|
||||||
- [Read the docs](https://platypush.readthedocs.io/en/latest/) for a complete reference on the available plugins and backends
|
|
||||||
- [Medium articles](https://medium.com/tag/platypush/archive) that describe hands-on applications of platypush
|
|
||||||
|
|
||||||
|
Plugins expose *actions*, that match one-on-one the plugin class methods denoted by `@action`, so it's very
|
||||||
|
straightforward to invoke plugin actions by just reading the plugin documentation. They can be invoked directly from
|
||||||
|
your own scripts or they can be sent to the platform through any supported channel as simple JSON messages:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "request",
|
||||||
|
"action": "light.hue.on",
|
||||||
|
"args": {
|
||||||
|
"lights": ["Entrance Bulb"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### [Backends](https://docs.platypush.tech/en/latest/backends.html)
|
||||||
|
|
||||||
|
They are background services that either listen for messages on channels (like an
|
||||||
|
[HTTP backend](https://docs.platypush.tech/en/latest/platypush/backend/http.html), an
|
||||||
|
[MQTT instance](https://docs.platypush.tech/en/latest/platypush/backend/mqtt.html), a
|
||||||
|
[Kafka instance](https://docs.platypush.tech/en/latest/platypush/backend/kafka.html), a
|
||||||
|
[Websocket service](https://docs.platypush.tech/en/latest/platypush/backend/websocket.html),
|
||||||
|
[Pushbullet](https://docs.platypush.tech/en/latest/platypush/backend/pushbullet.html) etc.) or monitor a device or a
|
||||||
|
service for events (like a [sensor](https://docs.platypush.tech/en/latest/platypush/backend/sensor.html), a custom
|
||||||
|
[voice assistant](https://docs.platypush.tech/en/latest/platypush/backend/assistant.google.html), a bridge running on a
|
||||||
|
[Zigbee](https://docs.platypush.tech/en/latest/platypush/backend/zigbee.mqtt.html) or
|
||||||
|
[Z-Wave](https://docs.platypush.tech/en/latest/platypush/backend/zwave.html), an
|
||||||
|
[NFC card reader](https://docs.platypush.tech/en/latest/platypush/backend/nfc.html), a
|
||||||
|
[MIDI device](https://docs.platypush.tech/en/latest/platypush/backend/midi.html), a
|
||||||
|
[Telegram channel](https://docs.platypush.tech/en/latest/platypush/backend/chat.telegram.html), a
|
||||||
|
[Bluetooth scanner](https://docs.platypush.tech/en/latest/platypush/backend/bluetooth.scanner.ble.html) etc.).
|
||||||
|
|
||||||
|
If a backend supports the execution of requests (e.g. HTTP, MQTT, Kafka, Websocket and TCP) then you can send requests
|
||||||
|
to these services in JSON format. For example, in the case of the HTTP backend:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Get a token
|
||||||
|
curl -XPOST -H 'Content-Type: application/json' -d '
|
||||||
|
{
|
||||||
|
"username": "$YOUR_USER",
|
||||||
|
"password": "$YOUR_PASSWORD"
|
||||||
|
}' http://host:8008/auth
|
||||||
|
|
||||||
|
# Execute a request
|
||||||
|
|
||||||
|
curl -XPOST -H 'Content-Type: application/json' -H "Authorization: Bearer $YOUR_TOKEN" -d '
|
||||||
|
{
|
||||||
|
"type": "request",
|
||||||
|
"action": "tts.say",
|
||||||
|
"args": {
|
||||||
|
"text": "This is a test"
|
||||||
|
}
|
||||||
|
}' http://host:8008/execute
|
||||||
|
```
|
||||||
|
|
||||||
|
### [Events](https://docs.platypush.tech/en/latest/events.html)
|
||||||
|
|
||||||
|
When a certain event occurs (e.g. a JSON request is received, or a
|
||||||
|
[Bluetooth device is connected](https://docs.platypush.tech/en/latest/platypush/events/bluetooth.html#platypush.message.event.bluetooth.BluetoothDeviceConnectedEvent),
|
||||||
|
or a
|
||||||
|
[Flic button is pressed](https://docs.platypush.tech/en/latest/platypush/events/button.flic.html#platypush.message.event.button.flic.FlicButtonEvent),
|
||||||
|
or some
|
||||||
|
[speech is detected on the voice assistant service](https://docs.platypush.tech/en/latest/platypush/events/assistant.html#platypush.message.event.assistant.SpeechRecognizedEvent),
|
||||||
|
or an
|
||||||
|
[RSS feed has new items](https://docs.platypush.tech/en/latest/platypush/events/http.rss.html#platypush.message.event.http.rss.NewFeedEvent),
|
||||||
|
or a
|
||||||
|
[new email is received](https://docs.platypush.tech/en/latest/platypush/events/mail.html#platypush.message.event.mail.MailReceivedEvent),
|
||||||
|
or a
|
||||||
|
[new track is played](https://docs.platypush.tech/en/latest/platypush/events/music.html#platypush.message.event.music.NewPlayingTrackEvent),
|
||||||
|
or an
|
||||||
|
[NFC tag is detected](https://docs.platypush.tech/en/latest/platypush/events/nfc.html#platypush.message.event.nfc.NFCTagDetectedEvent),
|
||||||
|
or
|
||||||
|
[new sensor data is available](https://docs.platypush.tech/en/latest/platypush/events/sensor.html#platypush.message.event.sensor.SensorDataChangeEvent),
|
||||||
|
or
|
||||||
|
[a value of a Zigbee device changes](https://docs.platypush.tech/en/latest/platypush/events/zigbee.mqtt.html#platypush.message.event.zigbee.mqtt.ZigbeeMqttDevicePropertySetEvent),
|
||||||
|
etc.), the associated backend will trigger an [event](https://docs.platypush.tech/en/latest/events.html).
|
||||||
|
|
||||||
|
### Hooks
|
||||||
|
|
||||||
|
Event hooks are custom pieces of logic that will be run when a certain event is triggered. Hooks are the glue that
|
||||||
|
connects events to actions, exposing a paradigm similar to IFTTT (_if a certain event happens then run these actions_).
|
||||||
|
They can declared as:
|
||||||
|
|
||||||
|
- Sections of the [`config.yaml`](https://git.platypush.tech/platypush/platypush/-/blob/master/examples/conf/config.yaml).
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
event.hook.SearchSongVoiceCommand:
|
||||||
|
if:
|
||||||
|
type: platypush.message.event.assistant.SpeechRecognizedEvent
|
||||||
|
phrase: "play ${title} by ${artist}"
|
||||||
|
then:
|
||||||
|
- action: music.mpd.clear
|
||||||
|
- action: music.mpd.search
|
||||||
|
args:
|
||||||
|
filter:
|
||||||
|
artist: ${artist}
|
||||||
|
title: ${title}
|
||||||
|
|
||||||
|
- if ${len(output)}:
|
||||||
|
- action: music.mpd.play
|
||||||
|
args:
|
||||||
|
resource: ${output[0]['file']}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Stand-alone Python scripts stored under `~/.config/platypush/scripts` and will be dynamically imported at start time.
|
||||||
|
[Example](https://git.platypush.tech/platypush/platypush/-/blob/master/examples/conf/hook.py):
|
||||||
|
|
||||||
|
```python
|
||||||
|
from platypush.event.hook import hook
|
||||||
|
from platypush.utils import run
|
||||||
|
from platypush.message.event.assistant import SpeechRecognizedEvent
|
||||||
|
|
||||||
|
@hook(SpeechRecognizedEvent, phrase='play ${title} by ${artist}')
|
||||||
|
def on_music_play_command(event, title=None, artist=None, **context):
|
||||||
|
results = run('music.mpd.search', filter={
|
||||||
|
'artist': artist,
|
||||||
|
'title': title,
|
||||||
|
})
|
||||||
|
|
||||||
|
if results:
|
||||||
|
run('music.mpd.play', results[0]['file'])
|
||||||
|
```
|
||||||
|
|
||||||
|
### Procedures
|
||||||
|
|
||||||
|
Procedures are pieces of custom logic that can be executed as atomic actions using `procedure.<name>` as an action name.
|
||||||
|
They can be defined either in the `config.yaml` or as Python scripts stored under `~/.config/platypush/scripts` -
|
||||||
|
provided that the procedure is also imported in `~/.config/platypush/scripts/__init__.py` so it can be discovered by
|
||||||
|
the service.
|
||||||
|
|
||||||
|
YAML example for a procedure that can be executed when we arrive home and turns on the lights if the luminosity is lower
|
||||||
|
that a certain thresholds, says a welcome home message using the TTS engine and starts playing the music:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
procedure.at_home:
|
||||||
|
# Get luminosity data from a sensor - e.g. LTR559
|
||||||
|
- action: gpio.sensor.ltr559.get_data
|
||||||
|
|
||||||
|
# If it's lower than a certain threshold, turn on the lights
|
||||||
|
- if ${int(light or 0) < 110}:
|
||||||
|
- action: light.hue.on
|
||||||
|
|
||||||
|
# Say a welcome home message
|
||||||
|
- action: tts.google.say
|
||||||
|
args:
|
||||||
|
text: Welcome home
|
||||||
|
|
||||||
|
# Play the music
|
||||||
|
- action: music.mpd.play
|
||||||
|
```
|
||||||
|
|
||||||
|
Python example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Content of ~/.config/platypush/scripts/home.py
|
||||||
|
from platypush.procedure import procedure
|
||||||
|
from platypush.utils import run
|
||||||
|
|
||||||
|
@procedure
|
||||||
|
def at_home(**context):
|
||||||
|
sensor_data = run('gpio.sensor.ltr559.get_data')
|
||||||
|
if sensor_data['light'] < 110:
|
||||||
|
run('light.hue.on')
|
||||||
|
|
||||||
|
run('tts.google.say', text='Welcome home')
|
||||||
|
run('music.mpd.play')
|
||||||
|
```
|
||||||
|
|
||||||
|
In either case, you can easily trigger the at-home procedure by sending an action request message to a backend - for
|
||||||
|
example, over the HTTP backend:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl -XPOST -H 'Content-Type: application/json' -H "Authorization: Bearer $YOUR_TOKEN" -d '
|
||||||
|
{
|
||||||
|
"type": "request",
|
||||||
|
"action": "procedure.at_home"
|
||||||
|
}' http://host:8008/execute
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cronjobs
|
||||||
|
|
||||||
|
Cronjobs are pieces of logic that will be run at regular intervals, expressed in crontab-compatible syntax.
|
||||||
|
They can be defined either in the `config.yaml` or as Python scripts stored under `~/.config/platypush/scripts` as
|
||||||
|
functions labelled by the `@cron` decorator.
|
||||||
|
|
||||||
|
Note that seconds are also supported (unlike the standard crontab definition), but, for back-compatibility with the
|
||||||
|
standard crontab format, they are at the end of the cron expression, so the expression is actually in the format
|
||||||
|
`<minute> <hour> <day_of_month> <month> <day_of_week> <second>`.
|
||||||
|
|
||||||
|
YAML example for a cronjob that is executed every 30 seconds and checks if a Bluetooth device is nearby:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
cron.check_bt_device:
|
||||||
|
cron_expression: '* * * * * */30'
|
||||||
|
actions:
|
||||||
|
- action: bluetooth.lookup_name
|
||||||
|
args:
|
||||||
|
addr: XX:XX:XX:XX:XX:XX
|
||||||
|
|
||||||
|
- if ${name}:
|
||||||
|
- action: procedure.on_device_on
|
||||||
|
- else:
|
||||||
|
- action: procedure.on_device_off
|
||||||
|
```
|
||||||
|
|
||||||
|
Python example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Content of ~/.config/platypush/scripts/bt_cron.py
|
||||||
|
from platypush.cron import cron
|
||||||
|
from platypush.utils import run
|
||||||
|
|
||||||
|
@cron('* * * * * */30')
|
||||||
|
def check_bt_device(**context):
|
||||||
|
name = run('bluetooth.lookup_name').get('name')
|
||||||
|
if name:
|
||||||
|
# on_device_on logic here
|
||||||
|
else:
|
||||||
|
# on_device_off logic here
|
||||||
|
```
|
||||||
|
|
||||||
|
### The web interface
|
||||||
|
|
||||||
|
If [`backend.http`](https://docs.platypush.tech/en/latest/platypush/backend/http.html) is enabled then a web interface
|
||||||
|
will be provided by default on `http://host:8008/`. Besides using the `/execute` endpoint for running requests, the
|
||||||
|
built-in web server also provides a full-featured interface that groups together the controls for most of the plugins -
|
||||||
|
e.g. sensors, switches, music controls and search, media library and torrent management, lights, Zigbee/Z-Wave devices
|
||||||
|
and so on. The UI is responsive and mobile-friendly.
|
||||||
|
|
||||||
|
The web service also provides means for the user to create
|
||||||
|
[custom dashboards](https://git.platypush.tech/platypush/platypush/-/blob/master/examples/conf/dashboard.xml) that can
|
||||||
|
be used to show information from multiple sources on a large screen.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### System installation
|
||||||
|
|
||||||
|
Platypush uses Redis to deliver and store requests and temporary messages:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Example for Debian-based distributions
|
||||||
|
[sudo] apt-get install redis-server
|
||||||
|
|
||||||
|
# Enable and start the service
|
||||||
|
[sudo] systemctl enable redis
|
||||||
|
[sudo] systemctl start redis
|
||||||
|
```
|
||||||
|
|
||||||
|
To install the core platform:
|
||||||
|
|
||||||
|
* The `pip` way:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
[sudo] pip3 install platypush
|
||||||
|
```
|
||||||
|
|
||||||
|
* The sources way:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
git clone https://git.platypush.tech/platypush/platypush.git
|
||||||
|
cd platypush
|
||||||
|
[sudo] python3 setup.py install
|
||||||
|
```
|
||||||
|
|
||||||
|
Then install the extensions that you wish to use. There are a few ways to check the dependencies required by an
|
||||||
|
extension:
|
||||||
|
|
||||||
|
#### Check their `extras` name in [`extras_require` under `setup.py`](https://git.platypush.tech/platypush/platypush/-/blob/master/setup.py#L72).
|
||||||
|
|
||||||
|
If you follow this route then you can install the extra dependencies in one of the following ways:
|
||||||
|
|
||||||
|
1. `pip` installation:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
[sudo] pip3 install 'platypush[extra1,extra2,extra3]'
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Sources installation:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cd $DIR_TO_PLATYPUSH
|
||||||
|
[sudo] pip3 install '.[extra1,extra2,extra3]'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Check the dependencies/installation instructions reported under the plugin/backend documentation.
|
||||||
|
|
||||||
|
If you follow this route then simply run the commands listed in the plugin/backend documentation to get the dependencies
|
||||||
|
installed.
|
||||||
|
|
||||||
|
#### Check/uncomment the associated lines in [`requirements.txt`](https://git.platypush.tech/platypush/platypush/-/blob/master/requirements.txt).
|
||||||
|
|
||||||
|
If you follow this route then uncomment the lines in
|
||||||
|
[`requirements.txt`](https://git.platypush.tech/platypush/platypush/-/blob/master/requirements.txt) associated to the
|
||||||
|
plugins/backends that you want to use and run:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
[sudo] pip3 install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
After installing the dependencies, create a configuration file under `~/.config/platypush/config.yaml` (the application
|
||||||
|
can load the configuration from another location through the `-c` option) containing the configuration of the backend
|
||||||
|
and plugins that you want to use, and add any hooks and procedures for your use case.
|
||||||
|
|
||||||
|
You can then start the service by simply running:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
platypush
|
||||||
|
```
|
||||||
|
|
||||||
|
It's advised to run it as a systemd service though - simply copy the provided
|
||||||
|
[`.service` file](https://git.platypush.tech/platypush/platypush/-/blob/master/examples/systemd/platypush.service) to
|
||||||
|
`~/.config/systemd/user`, check if the path of `platypush` matches the path where it's installed on your system, and
|
||||||
|
start the service via `systemctl`:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
systemctl --user start platypush
|
||||||
|
```
|
||||||
|
|
||||||
|
### [Virtual environment installation](https://git.platypush.tech/platypush/platypush/-/wikis/Run-platypush-in-a-virtual-environment)
|
||||||
|
|
||||||
|
Platypush provides a script named `platyvenv` that can parse a `config.yaml` and automatically create a virtual
|
||||||
|
environment (under `~/.local/share/platypush/venv/<device_id>`) with all the dependencies required by the configured
|
||||||
|
integrations.
|
||||||
|
|
||||||
|
1. Create the environment from a configuration file:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
platyvenv build -c /path/to/config.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Start the service from the virtual environment:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# device_id matches either the hostname or the device_id in config.yaml
|
||||||
|
platyvenv start device_id
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Stop the instance:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
platyvenv stop device_id
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Remove the instance:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
platyvenv rm device_id
|
||||||
|
```
|
||||||
|
|
||||||
|
### [Docker installation](https://git.platypush.tech/platypush/platypush/-/wikis/Run-platypush-in-a-container)
|
||||||
|
|
||||||
|
You can also install Platypush in a container - the application provides a script named `platydock` that automatically
|
||||||
|
creates a container instance from a `config.yaml`:
|
||||||
|
|
||||||
|
1. Create the container from a configuration file:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
platydock build -c /path/to/config.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Start the container:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# device_id matches either the hostname or the device_id in config.yaml
|
||||||
|
platydock start device_id
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Stop the instance:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
platydock stop device_id
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Remove the instance:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
platydock rm device_id
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mobile app
|
||||||
|
|
||||||
|
An [official Android app](https://f-droid.org/en/packages/tech.platypush.platypush/) is provided on the F-Droid store.
|
||||||
|
It allows to easily discover and manage multiple Platypush services on a network through the web interface, and it
|
||||||
|
easily brings the power of Platypush to your fingertips.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
To run the tests simply run `pytest` either from the project root folder or the `tests/` folder.
|
||||||
|
Or run the following command from the project root folder:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python -m tests
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Funding
|
||||||
|
|
||||||
|
If you use and love Platypush, please consider [buying me a coffee/beer](https://paypal.me/fabiomanganiello).
|
||||||
|
|
||||||
|
I've been working on Platypush all by myself in my spare time for the past few years, and I've made sure that it remains
|
||||||
|
open and free.
|
||||||
|
|
||||||
|
If you like this product, please consider supporting - I'm definitely not planning to get rich with this project, but
|
||||||
|
I'd love to have at least the costs for the server covered by users.
|
||||||
|
|
||||||
|
Issues and requests opened by donors will also be given priority over others.
|
||||||
|
|
|
@ -119,7 +119,7 @@ EOF
|
||||||
pip install ${dep}
|
pip install ${dep}
|
||||||
done
|
done
|
||||||
|
|
||||||
pip install --upgrade git+https://github.com/BlackLight/platypush.git
|
pip install --upgrade git+https://git.platypush.tech/platypush/platypush.git
|
||||||
echo "Platypush virtual environment prepared under $envdir"
|
echo "Platypush virtual environment prepared under $envdir"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
21
docs/README.md
Normal file
21
docs/README.md
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# Platypush self-generated reference
|
||||||
|
====================================
|
||||||
|
|
||||||
|
This directory contains the Sphinx self-generated documentation for Platypush.
|
||||||
|
|
||||||
|
Dependencies required to generate the documentation:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ [sudo] pip install sphinx 'git+https://github.com/bashtage/sphinx-material.git'
|
||||||
|
```
|
||||||
|
|
||||||
|
To generate the HTML documentation:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ make html
|
||||||
|
```
|
||||||
|
|
||||||
|
The output will be generated under `build/html`.
|
||||||
|
|
||||||
|
Type `make` with no additional arguments to get a full list of the supported output formats.
|
||||||
|
|
59
docs/source/_ext/sphinx_marshmallow.py
Normal file
59
docs/source/_ext/sphinx_marshmallow.py
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import importlib
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from typing import Union, List
|
||||||
|
|
||||||
|
from docutils import nodes
|
||||||
|
from docutils.parsers.rst import Directive
|
||||||
|
|
||||||
|
|
||||||
|
class SchemaDirective(Directive):
|
||||||
|
"""
|
||||||
|
Support for response/message schemas in the docs. Format: ``.. schema:: rel_path.SchemaClass(arg1=value1, ...)``,
|
||||||
|
where ``rel_path`` is the path of the schema relative to ``platypush/schemas``.
|
||||||
|
"""
|
||||||
|
has_content = True
|
||||||
|
_schema_regex = re.compile(r'^\s*(.+?)\s*(\((.+?)\))?\s*$')
|
||||||
|
_schemas_path = os.path.abspath(
|
||||||
|
os.path.join(
|
||||||
|
os.path.dirname(os.path.relpath(__file__)), '..', '..', '..', 'platypush', 'schemas'))
|
||||||
|
|
||||||
|
sys.path.insert(0, _schemas_path)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_field_value(field) -> str:
|
||||||
|
metadata = getattr(field, 'metadata', {})
|
||||||
|
return metadata.get('example', metadata.get('description', str(field.__class__.__name__).lower()))
|
||||||
|
|
||||||
|
def _parse_schema(self) -> Union[dict, List[dict]]:
|
||||||
|
m = self._schema_regex.match('\n'.join(self.content))
|
||||||
|
schema_module_name = '.'.join(['platypush.schemas', *(m.group(1).split('.')[:-1])])
|
||||||
|
schema_module = importlib.import_module(schema_module_name)
|
||||||
|
schema_class = getattr(schema_module, m.group(1).split('.')[-1])
|
||||||
|
schema_args = eval(f'dict({m.group(3)})')
|
||||||
|
schema = schema_class(**schema_args)
|
||||||
|
output = {
|
||||||
|
name: self._get_field_value(field)
|
||||||
|
for name, field in schema.fields.items()
|
||||||
|
if not field.load_only
|
||||||
|
}
|
||||||
|
|
||||||
|
return [output] if schema.many else output
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
content = json.dumps(self._parse_schema(), sort_keys=True, indent=2)
|
||||||
|
block = nodes.literal_block(content, content)
|
||||||
|
block['language'] = 'json'
|
||||||
|
return [block]
|
||||||
|
|
||||||
|
|
||||||
|
def setup(app):
|
||||||
|
app.add_directive('schema', SchemaDirective)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'version': '0.1',
|
||||||
|
'parallel_read_safe': True,
|
||||||
|
'parallel_write_safe': True,
|
||||||
|
}
|
|
@ -21,7 +21,10 @@ Backends
|
||||||
platypush/backend/chat.telegram.rst
|
platypush/backend/chat.telegram.rst
|
||||||
platypush/backend/clipboard.rst
|
platypush/backend/clipboard.rst
|
||||||
platypush/backend/covid19.rst
|
platypush/backend/covid19.rst
|
||||||
|
platypush/backend/dbus.rst
|
||||||
|
platypush/backend/file.monitor.rst
|
||||||
platypush/backend/foursquare.rst
|
platypush/backend/foursquare.rst
|
||||||
|
platypush/backend/github.rst
|
||||||
platypush/backend/google.fit.rst
|
platypush/backend/google.fit.rst
|
||||||
platypush/backend/google.pubsub.rst
|
platypush/backend/google.pubsub.rst
|
||||||
platypush/backend/gps.rst
|
platypush/backend/gps.rst
|
||||||
|
@ -29,15 +32,20 @@ Backends
|
||||||
platypush/backend/http.poll.rst
|
platypush/backend/http.poll.rst
|
||||||
platypush/backend/inotify.rst
|
platypush/backend/inotify.rst
|
||||||
platypush/backend/joystick.rst
|
platypush/backend/joystick.rst
|
||||||
|
platypush/backend/joystick.jstest.rst
|
||||||
|
platypush/backend/joystick.linux.rst
|
||||||
platypush/backend/kafka.rst
|
platypush/backend/kafka.rst
|
||||||
platypush/backend/light.hue.rst
|
platypush/backend/light.hue.rst
|
||||||
platypush/backend/linode.rst
|
platypush/backend/linode.rst
|
||||||
platypush/backend/local.rst
|
platypush/backend/log.http.rst
|
||||||
|
platypush/backend/mail.rst
|
||||||
platypush/backend/midi.rst
|
platypush/backend/midi.rst
|
||||||
platypush/backend/mqtt.rst
|
platypush/backend/mqtt.rst
|
||||||
platypush/backend/music.mopidy.rst
|
platypush/backend/music.mopidy.rst
|
||||||
platypush/backend/music.mpd.rst
|
platypush/backend/music.mpd.rst
|
||||||
platypush/backend/music.snapcast.rst
|
platypush/backend/music.snapcast.rst
|
||||||
|
platypush/backend/music.spotify.connect.rst
|
||||||
|
platypush/backend/nextcloud.rst
|
||||||
platypush/backend/nfc.rst
|
platypush/backend/nfc.rst
|
||||||
platypush/backend/nodered.rst
|
platypush/backend/nodered.rst
|
||||||
platypush/backend/ping.rst
|
platypush/backend/ping.rst
|
||||||
|
@ -49,6 +57,7 @@ Backends
|
||||||
platypush/backend/sensor.arduino.rst
|
platypush/backend/sensor.arduino.rst
|
||||||
platypush/backend/sensor.battery.rst
|
platypush/backend/sensor.battery.rst
|
||||||
platypush/backend/sensor.bme280.rst
|
platypush/backend/sensor.bme280.rst
|
||||||
|
platypush/backend/sensor.dht.rst
|
||||||
platypush/backend/sensor.distance.rst
|
platypush/backend/sensor.distance.rst
|
||||||
platypush/backend/sensor.distance.vl53l1x.rst
|
platypush/backend/sensor.distance.vl53l1x.rst
|
||||||
platypush/backend/sensor.envirophat.rst
|
platypush/backend/sensor.envirophat.rst
|
||||||
|
@ -66,9 +75,12 @@ Backends
|
||||||
platypush/backend/todoist.rst
|
platypush/backend/todoist.rst
|
||||||
platypush/backend/travisci.rst
|
platypush/backend/travisci.rst
|
||||||
platypush/backend/trello.rst
|
platypush/backend/trello.rst
|
||||||
|
platypush/backend/weather.rst
|
||||||
platypush/backend/weather.buienradar.rst
|
platypush/backend/weather.buienradar.rst
|
||||||
platypush/backend/weather.darksky.rst
|
platypush/backend/weather.darksky.rst
|
||||||
|
platypush/backend/weather.openweathermap.rst
|
||||||
platypush/backend/websocket.rst
|
platypush/backend/websocket.rst
|
||||||
platypush/backend/wiimote.rst
|
platypush/backend/wiimote.rst
|
||||||
platypush/backend/zigbee.mqtt.rst
|
platypush/backend/zigbee.mqtt.rst
|
||||||
platypush/backend/zwave.rst
|
platypush/backend/zwave.rst
|
||||||
|
platypush/backend/zwave.mqtt.rst
|
||||||
|
|
|
@ -18,12 +18,13 @@ import sys
|
||||||
# import os
|
# import os
|
||||||
# import sys
|
# import sys
|
||||||
# sys.path.insert(0, os.path.abspath('.'))
|
# sys.path.insert(0, os.path.abspath('.'))
|
||||||
|
sys.path.insert(0, os.path.abspath("./_ext"))
|
||||||
|
|
||||||
|
|
||||||
# -- Project information -----------------------------------------------------
|
# -- Project information -----------------------------------------------------
|
||||||
|
|
||||||
project = 'platypush'
|
project = 'Platypush'
|
||||||
copyright = '2017-2019, Fabio Manganiello'
|
copyright = '2017-2021, Fabio Manganiello'
|
||||||
author = 'Fabio Manganiello'
|
author = 'Fabio Manganiello'
|
||||||
|
|
||||||
# The short X.Y version
|
# The short X.Y version
|
||||||
|
@ -50,6 +51,7 @@ extensions = [
|
||||||
'sphinx.ext.viewcode',
|
'sphinx.ext.viewcode',
|
||||||
'sphinx.ext.githubpages',
|
'sphinx.ext.githubpages',
|
||||||
'sphinx_rtd_theme',
|
'sphinx_rtd_theme',
|
||||||
|
'sphinx_marshmallow',
|
||||||
]
|
]
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
@ -86,7 +88,8 @@ pygments_style = 'sphinx'
|
||||||
# a list of builtin themes.
|
# a list of builtin themes.
|
||||||
#
|
#
|
||||||
# html_theme = 'haiku'
|
# html_theme = 'haiku'
|
||||||
html_theme = 'sphinx_rtd_theme'
|
# html_theme = 'sphinx_rtd_theme'
|
||||||
|
html_theme = 'sphinx_material'
|
||||||
|
|
||||||
html_domain_indices = True
|
html_domain_indices = True
|
||||||
|
|
||||||
|
@ -94,7 +97,52 @@ html_domain_indices = True
|
||||||
# further. For a list of options available for each theme, see the
|
# further. For a list of options available for each theme, see the
|
||||||
# documentation.
|
# documentation.
|
||||||
#
|
#
|
||||||
# html_theme_options = {}
|
html_theme_options = {
|
||||||
|
'nav_title': 'Platypush documentation',
|
||||||
|
'repo_url': 'https://git.platypush.tech/platypush/platypush',
|
||||||
|
'repo_name': 'Source code',
|
||||||
|
'repo_type': 'gitlab',
|
||||||
|
'color_primary': 'green',
|
||||||
|
'color_accent': 'light-green',
|
||||||
|
'logo_icon': '🕮',
|
||||||
|
'nav_links': [
|
||||||
|
{
|
||||||
|
'href': 'https://platypush.tech/',
|
||||||
|
'title': 'Homepage',
|
||||||
|
'internal': False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'href': 'https://blog.platypush.tech/',
|
||||||
|
'title': 'Blog',
|
||||||
|
'internal': False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'href': 'https://git.platypush.tech/platypush/platypush',
|
||||||
|
'title': 'Repository',
|
||||||
|
'internal': False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'href': 'https://git.platypush.tech/platypush/platypush/-/wikis/home',
|
||||||
|
'title': 'Wiki',
|
||||||
|
'internal': False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'href': 'https://chrome.google.com/webstore/detail/platypush/aphldjclndofhflbbdnmpejbjgomkbie',
|
||||||
|
'title': 'Chrome Extension',
|
||||||
|
'internal': False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'href': 'https://addons.mozilla.org/en-US/firefox/addon/platypush/',
|
||||||
|
'title': 'Firefox Extension',
|
||||||
|
'internal': False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'href': 'https://f-droid.org/en/packages/tech.platypush.platypush/',
|
||||||
|
'title': 'Android App',
|
||||||
|
'internal': False,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
|
@ -109,8 +157,9 @@ html_domain_indices = True
|
||||||
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
|
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
|
||||||
# 'searchbox.html']``.
|
# 'searchbox.html']``.
|
||||||
#
|
#
|
||||||
# html_sidebars = {}
|
html_sidebars = {
|
||||||
|
'**': ['logo-text.html', 'globaltoc.html', 'localtoc.html', 'searchbox.html']
|
||||||
|
}
|
||||||
|
|
||||||
# -- Options for HTMLHelp output ---------------------------------------------
|
# -- Options for HTMLHelp output ---------------------------------------------
|
||||||
|
|
||||||
|
@ -254,6 +303,18 @@ autodoc_mock_imports = ['googlesamples.assistant.grpc.audio_helpers',
|
||||||
'paramiko',
|
'paramiko',
|
||||||
'luma',
|
'luma',
|
||||||
'zeroconf',
|
'zeroconf',
|
||||||
|
'dbus',
|
||||||
|
'gi',
|
||||||
|
'gi.repository',
|
||||||
|
'twilio',
|
||||||
|
'pytz',
|
||||||
|
'Adafruit_Python_DHT',
|
||||||
|
'RPi.GPIO',
|
||||||
|
'RPLCD',
|
||||||
|
'imapclient',
|
||||||
|
'pysmartthings',
|
||||||
|
'aiohttp',
|
||||||
|
'watchdog',
|
||||||
]
|
]
|
||||||
|
|
||||||
sys.path.insert(0, os.path.abspath('../..'))
|
sys.path.insert(0, os.path.abspath('../..'))
|
||||||
|
@ -266,3 +327,5 @@ def skip(app, what, name, obj, skip, options):
|
||||||
def setup(app):
|
def setup(app):
|
||||||
app.connect("autodoc-skip-member", skip)
|
app.connect("autodoc-skip-member", skip)
|
||||||
|
|
||||||
|
|
||||||
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -6,7 +6,6 @@ Events
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
:caption: Events:
|
:caption: Events:
|
||||||
|
|
||||||
platypush/events/.rst
|
|
||||||
platypush/events/adafruit.rst
|
platypush/events/adafruit.rst
|
||||||
platypush/events/alarm.rst
|
platypush/events/alarm.rst
|
||||||
platypush/events/application.rst
|
platypush/events/application.rst
|
||||||
|
@ -17,28 +16,33 @@ Events
|
||||||
platypush/events/chat.telegram.rst
|
platypush/events/chat.telegram.rst
|
||||||
platypush/events/clipboard.rst
|
platypush/events/clipboard.rst
|
||||||
platypush/events/covid19.rst
|
platypush/events/covid19.rst
|
||||||
|
platypush/events/custom.rst
|
||||||
platypush/events/distance.rst
|
platypush/events/distance.rst
|
||||||
|
platypush/events/file.rst
|
||||||
platypush/events/foursquare.rst
|
platypush/events/foursquare.rst
|
||||||
platypush/events/geo.rst
|
platypush/events/geo.rst
|
||||||
|
platypush/events/github.rst
|
||||||
platypush/events/google.rst
|
platypush/events/google.rst
|
||||||
platypush/events/google.fit.rst
|
platypush/events/google.fit.rst
|
||||||
platypush/events/google.pubsub.rst
|
platypush/events/google.pubsub.rst
|
||||||
platypush/events/gps.rst
|
platypush/events/gps.rst
|
||||||
platypush/events/http.rst
|
platypush/events/http.rst
|
||||||
platypush/events/http.hook.rst
|
platypush/events/http.hook.rst
|
||||||
platypush/events/http.ota.booking.rst
|
|
||||||
platypush/events/http.rss.rst
|
platypush/events/http.rss.rst
|
||||||
|
platypush/events/inotify.rst
|
||||||
platypush/events/joystick.rst
|
platypush/events/joystick.rst
|
||||||
platypush/events/kafka.rst
|
platypush/events/kafka.rst
|
||||||
platypush/events/light.rst
|
platypush/events/light.rst
|
||||||
platypush/events/linode.rst
|
platypush/events/linode.rst
|
||||||
|
platypush/events/log.http.rst
|
||||||
|
platypush/events/mail.rst
|
||||||
platypush/events/media.rst
|
platypush/events/media.rst
|
||||||
platypush/events/midi.rst
|
platypush/events/midi.rst
|
||||||
platypush/events/mqtt.rst
|
platypush/events/mqtt.rst
|
||||||
platypush/events/music.rst
|
platypush/events/music.rst
|
||||||
platypush/events/music.snapcast.rst
|
platypush/events/music.snapcast.rst
|
||||||
|
platypush/events/nextcloud.rst
|
||||||
platypush/events/nfc.rst
|
platypush/events/nfc.rst
|
||||||
platypush/events/path.rst
|
|
||||||
platypush/events/ping.rst
|
platypush/events/ping.rst
|
||||||
platypush/events/pushbullet.rst
|
platypush/events/pushbullet.rst
|
||||||
platypush/events/qrcode.rst
|
platypush/events/qrcode.rst
|
||||||
|
|
|
@ -5,13 +5,13 @@ Welcome to the Platypush reference of available plugins, backends and event type
|
||||||
|
|
||||||
For more information on Platypush please check out:
|
For more information on Platypush please check out:
|
||||||
|
|
||||||
* The `GitHub page`_ of the project
|
* The `Gitlab page`_ of the project
|
||||||
* The `online wiki`_ for quickstart and examples
|
* The `online wiki`_ for quickstart and examples
|
||||||
* The `Medium stories`_ for inspiration about possible projects
|
* The `Blog articles`_ for inspiration on use-cases possible projects
|
||||||
|
|
||||||
.. _GitHub page: https://github.com/BlackLight/platypush
|
.. _Gitlab page: https://git.platypush.tech/platypush/platypush
|
||||||
.. _online wiki: https://github.com/BlackLight/platypush/wiki
|
.. _online wiki: https://git.platypush.tech/platypush/platypush/-/wikis/home
|
||||||
.. _Medium stories: https://medium.com/tag/platypush/archive
|
.. _Blog articles: https://blog.platypush.tech
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 3
|
:maxdepth: 3
|
||||||
|
|
5
docs/source/platypush/backend/dbus.rst
Normal file
5
docs/source/platypush/backend/dbus.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.backend.dbus``
|
||||||
|
==========================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.dbus
|
||||||
|
:members:
|
5
docs/source/platypush/backend/file.monitor.rst
Normal file
5
docs/source/platypush/backend/file.monitor.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.backend.file.monitor``
|
||||||
|
==================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.file.monitor
|
||||||
|
:members:
|
5
docs/source/platypush/backend/github.rst
Normal file
5
docs/source/platypush/backend/github.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.backend.github``
|
||||||
|
============================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.github
|
||||||
|
:members:
|
5
docs/source/platypush/backend/joystick.jstest.rst
Normal file
5
docs/source/platypush/backend/joystick.jstest.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.backend.joystick.jstest``
|
||||||
|
=====================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.joystick.jstest
|
||||||
|
:members:
|
5
docs/source/platypush/backend/joystick.linux.rst
Normal file
5
docs/source/platypush/backend/joystick.linux.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.backend.joystick.linux``
|
||||||
|
====================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.joystick.linux
|
||||||
|
:members:
|
|
@ -1,5 +0,0 @@
|
||||||
``platypush.backend.local``
|
|
||||||
===========================
|
|
||||||
|
|
||||||
.. automodule:: platypush.backend.local
|
|
||||||
:members:
|
|
5
docs/source/platypush/backend/log.http.rst
Normal file
5
docs/source/platypush/backend/log.http.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.backend.log.http``
|
||||||
|
==============================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.log.http
|
||||||
|
:members:
|
5
docs/source/platypush/backend/mail.rst
Normal file
5
docs/source/platypush/backend/mail.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.backend.mail``
|
||||||
|
==========================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.mail
|
||||||
|
:members:
|
5
docs/source/platypush/backend/music.spotify.connect.rst
Normal file
5
docs/source/platypush/backend/music.spotify.connect.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.backend.music.spotify.connect``
|
||||||
|
===========================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.music.spotify.connect
|
||||||
|
:members:
|
5
docs/source/platypush/backend/nextcloud.rst
Normal file
5
docs/source/platypush/backend/nextcloud.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.backend.nextcloud``
|
||||||
|
===============================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.nextcloud
|
||||||
|
:members:
|
5
docs/source/platypush/backend/sensor.dht.rst
Normal file
5
docs/source/platypush/backend/sensor.dht.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.backend.sensor.dht``
|
||||||
|
================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.sensor.dht
|
||||||
|
:members:
|
|
@ -1,5 +0,0 @@
|
||||||
``platypush.backend.stt.picovoice``
|
|
||||||
===================================
|
|
||||||
|
|
||||||
.. automodule:: platypush.backend.stt.picovoice
|
|
||||||
:members:
|
|
5
docs/source/platypush/backend/weather.openweathermap.rst
Normal file
5
docs/source/platypush/backend/weather.openweathermap.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.backend.weather.openweathermap``
|
||||||
|
============================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.weather.openweathermap
|
||||||
|
:members:
|
5
docs/source/platypush/backend/weather.rst
Normal file
5
docs/source/platypush/backend/weather.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.backend.weather``
|
||||||
|
=============================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.weather
|
||||||
|
:members:
|
5
docs/source/platypush/backend/zwave.mqtt.rst
Normal file
5
docs/source/platypush/backend/zwave.mqtt.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.backend.zwave.mqtt``
|
||||||
|
================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.zwave.mqtt
|
||||||
|
:members:
|
|
@ -1,5 +0,0 @@
|
||||||
``platypush.message.event``
|
|
||||||
===========================
|
|
||||||
|
|
||||||
.. automodule:: platypush.message.event
|
|
||||||
:members:
|
|
5
docs/source/platypush/events/custom.rst
Normal file
5
docs/source/platypush/events/custom.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.message.event.custom``
|
||||||
|
==================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.message.event.custom
|
||||||
|
:members:
|
5
docs/source/platypush/events/file.rst
Normal file
5
docs/source/platypush/events/file.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.message.event.file``
|
||||||
|
================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.message.event.file
|
||||||
|
:members:
|
5
docs/source/platypush/events/github.rst
Normal file
5
docs/source/platypush/events/github.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.message.event.github``
|
||||||
|
==================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.message.event.github
|
||||||
|
:members:
|
|
@ -1,5 +0,0 @@
|
||||||
``platypush.message.event.http.ota.booking``
|
|
||||||
============================================
|
|
||||||
|
|
||||||
.. automodule:: platypush.message.event.http.ota.booking
|
|
||||||
:members:
|
|
5
docs/source/platypush/events/inotify.rst
Normal file
5
docs/source/platypush/events/inotify.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.message.event.inotify``
|
||||||
|
===================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.message.event.inotify
|
||||||
|
:members:
|
5
docs/source/platypush/events/log.http.rst
Normal file
5
docs/source/platypush/events/log.http.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.message.event.log.http``
|
||||||
|
====================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.message.event.log.http
|
||||||
|
:members:
|
5
docs/source/platypush/events/mail.rst
Normal file
5
docs/source/platypush/events/mail.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.message.event.mail``
|
||||||
|
================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.message.event.mail
|
||||||
|
:members:
|
5
docs/source/platypush/events/nextcloud.rst
Normal file
5
docs/source/platypush/events/nextcloud.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.message.event.nextcloud``
|
||||||
|
=====================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.message.event.nextcloud
|
||||||
|
:members:
|
|
@ -1,6 +0,0 @@
|
||||||
``platypush.message.event.path``
|
|
||||||
================================
|
|
||||||
|
|
||||||
.. automodule:: platypush.message.event.path
|
|
||||||
:members:
|
|
||||||
|
|
5
docs/source/platypush/plugins/camera.cv.rst
Normal file
5
docs/source/platypush/plugins/camera.cv.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.camera.cv``
|
||||||
|
===============================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.camera.cv
|
||||||
|
:members:
|
5
docs/source/platypush/plugins/camera.ffmpeg.rst
Normal file
5
docs/source/platypush/plugins/camera.ffmpeg.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.camera.ffmpeg``
|
||||||
|
===================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.camera.ffmpeg
|
||||||
|
:members:
|
5
docs/source/platypush/plugins/camera.gstreamer.rst
Normal file
5
docs/source/platypush/plugins/camera.gstreamer.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.camera.gstreamer``
|
||||||
|
======================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.camera.gstreamer
|
||||||
|
:members:
|
5
docs/source/platypush/plugins/config.rst
Normal file
5
docs/source/platypush/plugins/config.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.config``
|
||||||
|
============================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.config
|
||||||
|
:members:
|
5
docs/source/platypush/plugins/dbus.rst
Normal file
5
docs/source/platypush/plugins/dbus.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.dbus``
|
||||||
|
==========================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.dbus
|
||||||
|
:members:
|
5
docs/source/platypush/plugins/ffmpeg.rst
Normal file
5
docs/source/platypush/plugins/ffmpeg.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.ffmpeg``
|
||||||
|
============================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.ffmpeg
|
||||||
|
:members:
|
|
@ -1,6 +0,0 @@
|
||||||
``platypush.plugins.google.credentials``
|
|
||||||
========================================
|
|
||||||
|
|
||||||
.. automodule:: platypush.plugins.google.credentials
|
|
||||||
:members:
|
|
||||||
|
|
5
docs/source/platypush/plugins/gpio.sensor.dht.rst
Normal file
5
docs/source/platypush/plugins/gpio.sensor.dht.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.gpio.sensor.dht``
|
||||||
|
=====================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.gpio.sensor.dht
|
||||||
|
:members:
|
|
@ -1,5 +0,0 @@
|
||||||
``platypush.plugins.http.request.ota.booking``
|
|
||||||
==============================================
|
|
||||||
|
|
||||||
.. automodule:: platypush.plugins.http.request.ota.booking
|
|
||||||
:members:
|
|
5
docs/source/platypush/plugins/lcd.gpio.rst
Normal file
5
docs/source/platypush/plugins/lcd.gpio.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.lcd.gpio``
|
||||||
|
==============================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.lcd.gpio
|
||||||
|
:members:
|
5
docs/source/platypush/plugins/lcd.i2c.rst
Normal file
5
docs/source/platypush/plugins/lcd.i2c.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.lcd.i2c``
|
||||||
|
=============================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.lcd.i2c
|
||||||
|
:members:
|
5
docs/source/platypush/plugins/lcd.rst
Normal file
5
docs/source/platypush/plugins/lcd.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.lcd``
|
||||||
|
=========================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.lcd
|
||||||
|
:members:
|
5
docs/source/platypush/plugins/mail.imap.rst
Normal file
5
docs/source/platypush/plugins/mail.imap.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.mail.imap``
|
||||||
|
===============================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.mail.imap
|
||||||
|
:members:
|
5
docs/source/platypush/plugins/mail.rst
Normal file
5
docs/source/platypush/plugins/mail.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.mail``
|
||||||
|
==========================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.mail
|
||||||
|
:members:
|
5
docs/source/platypush/plugins/mail.smtp.rst
Normal file
5
docs/source/platypush/plugins/mail.smtp.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.mail.smtp``
|
||||||
|
===============================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.mail.smtp
|
||||||
|
:members:
|
|
@ -1,5 +0,0 @@
|
||||||
``platypush.plugins.media.ctrl``
|
|
||||||
================================
|
|
||||||
|
|
||||||
.. automodule:: platypush.plugins.media.ctrl
|
|
||||||
:members:
|
|
5
docs/source/platypush/plugins/media.gstreamer.rst
Normal file
5
docs/source/platypush/plugins/media.gstreamer.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.media.gstreamer``
|
||||||
|
=====================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.media.gstreamer
|
||||||
|
:members:
|
|
@ -1,6 +0,0 @@
|
||||||
``platypush.plugins.media.search.local``
|
|
||||||
========================================
|
|
||||||
|
|
||||||
.. automodule:: platypush.plugins.media.search.local
|
|
||||||
:members:
|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
``platypush.plugins.media.search.torrent``
|
|
||||||
==========================================
|
|
||||||
|
|
||||||
.. automodule:: platypush.plugins.media.search.torrent
|
|
||||||
:members:
|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
``platypush.plugins.media.search.youtube``
|
|
||||||
==========================================
|
|
||||||
|
|
||||||
.. automodule:: platypush.plugins.media.search.youtube
|
|
||||||
:members:
|
|
||||||
|
|
5
docs/source/platypush/plugins/nextcloud.rst
Normal file
5
docs/source/platypush/plugins/nextcloud.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.nextcloud``
|
||||||
|
===============================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.nextcloud
|
||||||
|
:members:
|
5
docs/source/platypush/plugins/pwm.pca9685.rst
Normal file
5
docs/source/platypush/plugins/pwm.pca9685.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.pwm.pca9685``
|
||||||
|
=================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.pwm.pca9685
|
||||||
|
:members:
|
5
docs/source/platypush/plugins/rtorrent.rst
Normal file
5
docs/source/platypush/plugins/rtorrent.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.rtorrent``
|
||||||
|
==============================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.rtorrent
|
||||||
|
:members:
|
5
docs/source/platypush/plugins/smartthings.rst
Normal file
5
docs/source/platypush/plugins/smartthings.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.smartthings``
|
||||||
|
=================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.smartthings
|
||||||
|
:members:
|
|
@ -1,5 +0,0 @@
|
||||||
``platypush.plugins.stt.picovoice``
|
|
||||||
===================================
|
|
||||||
|
|
||||||
.. automodule:: platypush.plugins.stt.picovoice
|
|
||||||
:members:
|
|
|
@ -1,6 +0,0 @@
|
||||||
``platypush.plugins.switch.switchbot``
|
|
||||||
======================================
|
|
||||||
|
|
||||||
.. automodule:: platypush.plugins.switch.switchbot
|
|
||||||
:members:
|
|
||||||
|
|
5
docs/source/platypush/plugins/switchbot.bluetooth.rst
Normal file
5
docs/source/platypush/plugins/switchbot.bluetooth.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.switchbot.bluetooth``
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.switchbot.bluetooth
|
||||||
|
:members:
|
5
docs/source/platypush/plugins/switchbot.rst
Normal file
5
docs/source/platypush/plugins/switchbot.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.switchbot``
|
||||||
|
===============================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.switchbot
|
||||||
|
:members:
|
5
docs/source/platypush/plugins/twilio.rst
Normal file
5
docs/source/platypush/plugins/twilio.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.twilio``
|
||||||
|
============================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.twilio
|
||||||
|
:members:
|
|
@ -1,5 +0,0 @@
|
||||||
``platypush.plugins.video.torrentcast``
|
|
||||||
=======================================
|
|
||||||
|
|
||||||
.. automodule:: platypush.plugins.video.torrentcast
|
|
||||||
:members:
|
|
5
docs/source/platypush/plugins/weather.openweathermap.rst
Normal file
5
docs/source/platypush/plugins/weather.openweathermap.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.weather.openweathermap``
|
||||||
|
============================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.weather.openweathermap
|
||||||
|
:members:
|
5
docs/source/platypush/plugins/weather.rst
Normal file
5
docs/source/platypush/plugins/weather.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.weather``
|
||||||
|
=============================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.weather
|
||||||
|
:members:
|
5
docs/source/platypush/plugins/zwave._base.rst
Normal file
5
docs/source/platypush/plugins/zwave._base.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.zwave._base``
|
||||||
|
=================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.zwave._base
|
||||||
|
:members:
|
5
docs/source/platypush/plugins/zwave.mqtt.rst
Normal file
5
docs/source/platypush/plugins/zwave.mqtt.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.zwave.mqtt``
|
||||||
|
================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.zwave.mqtt
|
||||||
|
:members:
|
|
@ -1,5 +0,0 @@
|
||||||
``platypush.message.response.deepspeech``
|
|
||||||
=========================================
|
|
||||||
|
|
||||||
.. automodule:: platypush.message.response.deepspeech
|
|
||||||
:members:
|
|
|
@ -20,15 +20,21 @@ Plugins
|
||||||
platypush/plugins/calendar.ical.rst
|
platypush/plugins/calendar.ical.rst
|
||||||
platypush/plugins/camera.rst
|
platypush/plugins/camera.rst
|
||||||
platypush/plugins/camera.android.ipcam.rst
|
platypush/plugins/camera.android.ipcam.rst
|
||||||
|
platypush/plugins/camera.cv.rst
|
||||||
|
platypush/plugins/camera.ffmpeg.rst
|
||||||
|
platypush/plugins/camera.gstreamer.rst
|
||||||
platypush/plugins/camera.ir.mlx90640.rst
|
platypush/plugins/camera.ir.mlx90640.rst
|
||||||
platypush/plugins/camera.pi.rst
|
platypush/plugins/camera.pi.rst
|
||||||
platypush/plugins/chat.telegram.rst
|
platypush/plugins/chat.telegram.rst
|
||||||
platypush/plugins/clipboard.rst
|
platypush/plugins/clipboard.rst
|
||||||
|
platypush/plugins/config.rst
|
||||||
platypush/plugins/covid19.rst
|
platypush/plugins/covid19.rst
|
||||||
platypush/plugins/csv.rst
|
platypush/plugins/csv.rst
|
||||||
platypush/plugins/db.rst
|
platypush/plugins/db.rst
|
||||||
|
platypush/plugins/dbus.rst
|
||||||
platypush/plugins/dropbox.rst
|
platypush/plugins/dropbox.rst
|
||||||
platypush/plugins/esp.rst
|
platypush/plugins/esp.rst
|
||||||
|
platypush/plugins/ffmpeg.rst
|
||||||
platypush/plugins/file.rst
|
platypush/plugins/file.rst
|
||||||
platypush/plugins/foursquare.rst
|
platypush/plugins/foursquare.rst
|
||||||
platypush/plugins/google.rst
|
platypush/plugins/google.rst
|
||||||
|
@ -44,6 +50,7 @@ Plugins
|
||||||
platypush/plugins/gpio.sensor.rst
|
platypush/plugins/gpio.sensor.rst
|
||||||
platypush/plugins/gpio.sensor.accelerometer.rst
|
platypush/plugins/gpio.sensor.accelerometer.rst
|
||||||
platypush/plugins/gpio.sensor.bme280.rst
|
platypush/plugins/gpio.sensor.bme280.rst
|
||||||
|
platypush/plugins/gpio.sensor.dht.rst
|
||||||
platypush/plugins/gpio.sensor.distance.rst
|
platypush/plugins/gpio.sensor.distance.rst
|
||||||
platypush/plugins/gpio.sensor.distance.vl53l1x.rst
|
platypush/plugins/gpio.sensor.distance.vl53l1x.rst
|
||||||
platypush/plugins/gpio.sensor.envirophat.rst
|
platypush/plugins/gpio.sensor.envirophat.rst
|
||||||
|
@ -54,7 +61,6 @@ Plugins
|
||||||
platypush/plugins/graphite.rst
|
platypush/plugins/graphite.rst
|
||||||
platypush/plugins/homeseer.rst
|
platypush/plugins/homeseer.rst
|
||||||
platypush/plugins/http.request.rst
|
platypush/plugins/http.request.rst
|
||||||
platypush/plugins/http.request.ota.booking.rst
|
|
||||||
platypush/plugins/http.request.rss.rst
|
platypush/plugins/http.request.rss.rst
|
||||||
platypush/plugins/http.webpage.rst
|
platypush/plugins/http.webpage.rst
|
||||||
platypush/plugins/ifttt.rst
|
platypush/plugins/ifttt.rst
|
||||||
|
@ -62,14 +68,20 @@ Plugins
|
||||||
platypush/plugins/inspect.rst
|
platypush/plugins/inspect.rst
|
||||||
platypush/plugins/kafka.rst
|
platypush/plugins/kafka.rst
|
||||||
platypush/plugins/lastfm.rst
|
platypush/plugins/lastfm.rst
|
||||||
|
platypush/plugins/lcd.rst
|
||||||
|
platypush/plugins/lcd.gpio.rst
|
||||||
|
platypush/plugins/lcd.i2c.rst
|
||||||
platypush/plugins/light.rst
|
platypush/plugins/light.rst
|
||||||
platypush/plugins/light.hue.rst
|
platypush/plugins/light.hue.rst
|
||||||
platypush/plugins/linode.rst
|
platypush/plugins/linode.rst
|
||||||
platypush/plugins/logger.rst
|
platypush/plugins/logger.rst
|
||||||
platypush/plugins/luma.oled.rst
|
platypush/plugins/luma.oled.rst
|
||||||
|
platypush/plugins/mail.rst
|
||||||
|
platypush/plugins/mail.imap.rst
|
||||||
|
platypush/plugins/mail.smtp.rst
|
||||||
platypush/plugins/media.rst
|
platypush/plugins/media.rst
|
||||||
platypush/plugins/media.chromecast.rst
|
platypush/plugins/media.chromecast.rst
|
||||||
platypush/plugins/media.ctrl.rst
|
platypush/plugins/media.gstreamer.rst
|
||||||
platypush/plugins/media.kodi.rst
|
platypush/plugins/media.kodi.rst
|
||||||
platypush/plugins/media.mplayer.rst
|
platypush/plugins/media.mplayer.rst
|
||||||
platypush/plugins/media.mpv.rst
|
platypush/plugins/media.mpv.rst
|
||||||
|
@ -85,17 +97,21 @@ Plugins
|
||||||
platypush/plugins/music.rst
|
platypush/plugins/music.rst
|
||||||
platypush/plugins/music.mpd.rst
|
platypush/plugins/music.mpd.rst
|
||||||
platypush/plugins/music.snapcast.rst
|
platypush/plugins/music.snapcast.rst
|
||||||
|
platypush/plugins/nextcloud.rst
|
||||||
platypush/plugins/nmap.rst
|
platypush/plugins/nmap.rst
|
||||||
platypush/plugins/otp.rst
|
platypush/plugins/otp.rst
|
||||||
platypush/plugins/pihole.rst
|
platypush/plugins/pihole.rst
|
||||||
platypush/plugins/ping.rst
|
platypush/plugins/ping.rst
|
||||||
platypush/plugins/printer.cups.rst
|
platypush/plugins/printer.cups.rst
|
||||||
platypush/plugins/pushbullet.rst
|
platypush/plugins/pushbullet.rst
|
||||||
|
platypush/plugins/pwm.pca9685.rst
|
||||||
platypush/plugins/qrcode.rst
|
platypush/plugins/qrcode.rst
|
||||||
platypush/plugins/redis.rst
|
platypush/plugins/redis.rst
|
||||||
|
platypush/plugins/rtorrent.rst
|
||||||
platypush/plugins/sensor.rst
|
platypush/plugins/sensor.rst
|
||||||
platypush/plugins/serial.rst
|
platypush/plugins/serial.rst
|
||||||
platypush/plugins/shell.rst
|
platypush/plugins/shell.rst
|
||||||
|
platypush/plugins/smartthings.rst
|
||||||
platypush/plugins/sound.rst
|
platypush/plugins/sound.rst
|
||||||
platypush/plugins/ssh.rst
|
platypush/plugins/ssh.rst
|
||||||
platypush/plugins/stt.rst
|
platypush/plugins/stt.rst
|
||||||
|
@ -103,9 +119,10 @@ Plugins
|
||||||
platypush/plugins/stt.picovoice.hotword.rst
|
platypush/plugins/stt.picovoice.hotword.rst
|
||||||
platypush/plugins/stt.picovoice.speech.rst
|
platypush/plugins/stt.picovoice.speech.rst
|
||||||
platypush/plugins/switch.rst
|
platypush/plugins/switch.rst
|
||||||
platypush/plugins/switch.switchbot.rst
|
|
||||||
platypush/plugins/switch.tplink.rst
|
platypush/plugins/switch.tplink.rst
|
||||||
platypush/plugins/switch.wemo.rst
|
platypush/plugins/switch.wemo.rst
|
||||||
|
platypush/plugins/switchbot.rst
|
||||||
|
platypush/plugins/switchbot.bluetooth.rst
|
||||||
platypush/plugins/system.rst
|
platypush/plugins/system.rst
|
||||||
platypush/plugins/tcp.rst
|
platypush/plugins/tcp.rst
|
||||||
platypush/plugins/tensorflow.rst
|
platypush/plugins/tensorflow.rst
|
||||||
|
@ -116,15 +133,19 @@ Plugins
|
||||||
platypush/plugins/tts.rst
|
platypush/plugins/tts.rst
|
||||||
platypush/plugins/tts.google.rst
|
platypush/plugins/tts.google.rst
|
||||||
platypush/plugins/tv.samsung.ws.rst
|
platypush/plugins/tv.samsung.ws.rst
|
||||||
|
platypush/plugins/twilio.rst
|
||||||
platypush/plugins/udp.rst
|
platypush/plugins/udp.rst
|
||||||
platypush/plugins/user.rst
|
platypush/plugins/user.rst
|
||||||
platypush/plugins/utils.rst
|
platypush/plugins/utils.rst
|
||||||
platypush/plugins/variable.rst
|
platypush/plugins/variable.rst
|
||||||
platypush/plugins/video.torrentcast.rst
|
platypush/plugins/weather.rst
|
||||||
platypush/plugins/weather.buienradar.rst
|
platypush/plugins/weather.buienradar.rst
|
||||||
platypush/plugins/weather.darksky.rst
|
platypush/plugins/weather.darksky.rst
|
||||||
|
platypush/plugins/weather.openweathermap.rst
|
||||||
platypush/plugins/websocket.rst
|
platypush/plugins/websocket.rst
|
||||||
platypush/plugins/wiimote.rst
|
platypush/plugins/wiimote.rst
|
||||||
platypush/plugins/zeroconf.rst
|
platypush/plugins/zeroconf.rst
|
||||||
platypush/plugins/zigbee.mqtt.rst
|
platypush/plugins/zigbee.mqtt.rst
|
||||||
platypush/plugins/zwave.rst
|
platypush/plugins/zwave.rst
|
||||||
|
platypush/plugins/zwave._base.rst
|
||||||
|
platypush/plugins/zwave.mqtt.rst
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 6c0e65ccfe020bf6ce2eca43c387e827d4764c12
|
|
|
@ -16,21 +16,21 @@
|
||||||
# Using multiple files is encouraged in the case of large configurations
|
# Using multiple files is encouraged in the case of large configurations
|
||||||
# that can easily end up in a messy config.yaml file, as they help you
|
# that can easily end up in a messy config.yaml file, as they help you
|
||||||
# keep your configuration more organized.
|
# keep your configuration more organized.
|
||||||
include:
|
#include:
|
||||||
- include/logging.yaml
|
# - include/logging.yaml
|
||||||
- include/media.yaml
|
# - include/media.yaml
|
||||||
- include/sensors.yaml
|
# - include/sensors.yaml
|
||||||
|
|
||||||
# platypush logs on stdout by default. You can use the logging section to specify
|
# platypush logs on stdout by default. You can use the logging section to specify
|
||||||
# an alternative file or change the logging level.
|
# an alternative file or change the logging level.
|
||||||
logging:
|
#logging:
|
||||||
filename: ~/.local/log/platypush/platypush.log
|
# filename: ~/.local/log/platypush/platypush.log
|
||||||
level: INFO
|
# level: INFO
|
||||||
|
|
||||||
# The device_id is used by many components of platypush and it should uniquely
|
# The device_id is used by many components of platypush and it should uniquely
|
||||||
# identify a device in your network. If nothing is specified then the hostname
|
# identify a device in your network. If nothing is specified then the hostname
|
||||||
# will be used.
|
# will be used.
|
||||||
device_id: myname
|
#device_id: my_device
|
||||||
|
|
||||||
## --
|
## --
|
||||||
## Plugin configuration examples
|
## Plugin configuration examples
|
||||||
|
@ -40,10 +40,10 @@ device_id: myname
|
||||||
# a plugin class. The methods of the class with @action annotation will
|
# a plugin class. The methods of the class with @action annotation will
|
||||||
# be exported as runnable actions, while the __init__ parameters are
|
# be exported as runnable actions, while the __init__ parameters are
|
||||||
# configuration attributes that you can initialize in your config.yaml.
|
# configuration attributes that you can initialize in your config.yaml.
|
||||||
# Plugin classes are documented at https://platypush.readthedocs.io/en/latest/plugins.html
|
# Plugin classes are documented at https://docs.platypush.tech/en/latest/plugins.html
|
||||||
#
|
#
|
||||||
# In this example we'll configure the light.hue plugin, see
|
# In this example we'll configure the light.hue plugin, see
|
||||||
# https://platypush.readthedocs.io/en/latest/platypush/plugins/light.hue.html
|
# https://docs.platypush.tech/en/latest/platypush/plugins/light.hue.html
|
||||||
# for reference. You can easily install the required dependencies for the plugin through
|
# for reference. You can easily install the required dependencies for the plugin through
|
||||||
# pip install 'platypush[hue]'
|
# pip install 'platypush[hue]'
|
||||||
light.hue:
|
light.hue:
|
||||||
|
@ -54,14 +54,14 @@ light.hue:
|
||||||
- Living Room
|
- Living Room
|
||||||
|
|
||||||
# Example configuration of music.mpd plugin, see
|
# Example configuration of music.mpd plugin, see
|
||||||
# https://platypush.readthedocs.io/en/latest/platypush/plugins/music.mpd.html
|
# https://docs.platypush.tech/en/latest/platypush/plugins/music.mpd.html
|
||||||
# You can easily install the dependencies through pip install 'platypush[mpd]'
|
# You can easily install the dependencies through pip install 'platypush[mpd]'
|
||||||
music.mpd:
|
music.mpd:
|
||||||
host: localhost
|
host: localhost
|
||||||
port: 6600
|
port: 6600
|
||||||
|
|
||||||
# Example configuration of media.chromecast plugin, see
|
# Example configuration of media.chromecast plugin, see
|
||||||
# https://platypush.readthedocs.io/en/latest/platypush/plugins/media.chromecast.html
|
# https://docs.platypush.tech/en/latest/platypush/plugins/media.chromecast.html
|
||||||
# You can easily install the dependencies through pip install 'platypush[chromecast]'
|
# You can easily install the dependencies through pip install 'platypush[chromecast]'
|
||||||
media.chromecast:
|
media.chromecast:
|
||||||
chromecast: Living Room TV
|
chromecast: Living Room TV
|
||||||
|
@ -69,25 +69,16 @@ media.chromecast:
|
||||||
# Plugins with empty configuration can also be explicitly enabled by specifying
|
# Plugins with empty configuration can also be explicitly enabled by specifying
|
||||||
# enabled=True or disabled=False (it's a good practice if you want the
|
# enabled=True or disabled=False (it's a good practice if you want the
|
||||||
# corresponding web panel to be enabled, if available)
|
# corresponding web panel to be enabled, if available)
|
||||||
camera:
|
camera.pi:
|
||||||
enabled: True
|
enabled: True
|
||||||
|
|
||||||
# Support for last.fm scrobbling. Install dependencies with 'pip install "platypush[lastfm]"
|
|
||||||
lastfm:
|
|
||||||
api_key: your_api_key
|
|
||||||
api_secret: your_api_secret
|
|
||||||
username: your_username
|
|
||||||
password: your_password
|
|
||||||
|
|
||||||
# Support for calendars - in this case Google and Facebook calendars
|
# Support for calendars - in this case Google and Facebook calendars
|
||||||
# Installing the dependencies: pip install 'platypush[ical,google]'
|
# Installing the dependencies: pip install 'platypush[ical,google]'
|
||||||
calendar:
|
calendar:
|
||||||
calendars:
|
calendars:
|
||||||
-
|
- type: platypush.plugins.google.calendar.GoogleCalendarPlugin
|
||||||
type: platypush.plugins.google.calendar.GoogleCalendarPlugin
|
- type: platypush.plugins.calendar.ical.CalendarIcalPlugin
|
||||||
-
|
url: https://www.facebook.com/events/ical/upcoming/?uid=your_user_id&key=your_key
|
||||||
type: platypush.plugins.calendar.ical.CalendarIcalPlugin
|
|
||||||
url: https://www.facebook.com/events/ical/upcoming/?uid=your_user_id&key=your_key
|
|
||||||
|
|
||||||
## --
|
## --
|
||||||
## Backends configuration examples
|
## Backends configuration examples
|
||||||
|
@ -97,10 +88,10 @@ calendar:
|
||||||
# to happen and either trigger events or provide additional services on top of platypush.
|
# to happen and either trigger events or provide additional services on top of platypush.
|
||||||
# Just like plugins, backends are classes whose configuration matches one-to-one the
|
# Just like plugins, backends are classes whose configuration matches one-to-one the
|
||||||
# supported parameters on the __init__ methods. You can check the documentation for the
|
# supported parameters on the __init__ methods. You can check the documentation for the
|
||||||
# available backends here: https://platypush.readthedocs.io/en/latest/backends.html.
|
# available backends here: https://docs.platypush.tech/en/latest/backends.html.
|
||||||
# Moreover, most of the backends will generate events that you can react to through custom
|
# Moreover, most of the backends will generate events that you can react to through custom
|
||||||
# event hooks. Check here for the events documentation:
|
# event hooks. Check here for the events documentation:
|
||||||
# https://platypush.readthedocs.io/en/latest/events.html
|
# https://docs.platypush.tech/en/latest/events.html
|
||||||
#
|
#
|
||||||
# You may usually want to enable the HTTP backend, as it provides many useful features on
|
# You may usually want to enable the HTTP backend, as it provides many useful features on
|
||||||
# top of platypush. Among those:
|
# top of platypush. Among those:
|
||||||
|
@ -118,40 +109,14 @@ calendar:
|
||||||
backend.http:
|
backend.http:
|
||||||
# Listening port
|
# Listening port
|
||||||
port: 8008
|
port: 8008
|
||||||
|
# Websocket port
|
||||||
|
websocket_port: 8009
|
||||||
|
|
||||||
# Through resource_dirs you can specify external folders whose content can be accessed on
|
# Through resource_dirs you can specify external folders whose content can be accessed on
|
||||||
# the web server through a custom URL. In the case below we have a Dropbox folder containing
|
# the web server through a custom URL. In the case below we have a Dropbox folder containing
|
||||||
# our pictures and we mount it to the '/carousel' endpoint.
|
# our pictures and we mount it to the '/carousel' endpoint.
|
||||||
resource_dirs:
|
resource_dirs:
|
||||||
carousel: ~/Dropbox/Photos/carousel
|
carousel: /mnt/hd/photos/carousel
|
||||||
|
|
||||||
# Dashboard configuration. The dashboard is a collection of widgets and it's organized in
|
|
||||||
# multiple rows. Each rows can be split in 12 columns. Therefore 'columns: 12' will make
|
|
||||||
# a widget span over the whole row, while 'columns: 6' will make a widget take half the
|
|
||||||
# horizontal space of a column.
|
|
||||||
dashboard:
|
|
||||||
widgets:
|
|
||||||
-
|
|
||||||
widget: calendar
|
|
||||||
columns: 6
|
|
||||||
-
|
|
||||||
widget: music
|
|
||||||
columns: 3
|
|
||||||
-
|
|
||||||
widget: date-time-weather
|
|
||||||
columns: 3
|
|
||||||
-
|
|
||||||
widget: image-carousel
|
|
||||||
columns: 6
|
|
||||||
images_path: ~/Dropbox/Photos/carousel
|
|
||||||
refresh_seconds: 15
|
|
||||||
-
|
|
||||||
widget: rss-news
|
|
||||||
# Requires backend.http.poll to be enabled with some
|
|
||||||
# RSS sources and write them to sqlite db
|
|
||||||
columns: 6
|
|
||||||
limit: 25
|
|
||||||
db: "sqlite:////home/user/.local/share/platypush/feeds/rss.db"
|
|
||||||
|
|
||||||
# The HTTP poll backend is a versatile backend that can monitor for HTTP-based resources and
|
# The HTTP poll backend is a versatile backend that can monitor for HTTP-based resources and
|
||||||
# trigger events whenever new entries are available. In the example below we show how to use
|
# trigger events whenever new entries are available. In the example below we show how to use
|
||||||
|
@ -160,35 +125,33 @@ backend.http:
|
||||||
# Install the required dependencies through 'pip install "platypush[rss,db]"'
|
# Install the required dependencies through 'pip install "platypush[rss,db]"'
|
||||||
backend.http.poll:
|
backend.http.poll:
|
||||||
requests:
|
requests:
|
||||||
-
|
- type: platypush.backend.http.request.rss.RssUpdates # HTTP poll type (RSS)
|
||||||
# HTTP poll type (RSS)
|
# Remote URL
|
||||||
type: platypush.backend.http.request.rss.RssUpdates
|
url: http://www.theguardian.com/rss/world
|
||||||
# Remote URL
|
# Custom title
|
||||||
url: http://www.theguardian.com/rss/world
|
title: The Guardian - World News
|
||||||
# Custom title
|
# How often we should check for changes
|
||||||
title: The Guardian - World News
|
poll_seconds: 600
|
||||||
# How often we should check for changes
|
# Maximum number of new entries to be processed
|
||||||
poll_seconds: 600
|
max_entries: 10
|
||||||
# Maximum number of new entries to be processed
|
|
||||||
max_entries: 10
|
- type: platypush.backend.http.request.rss.RssUpdates
|
||||||
-
|
url: http://www.physorg.com/rss-feed
|
||||||
type: platypush.backend.http.request.rss.RssUpdates
|
title: Phys.org
|
||||||
url: http://www.physorg.com/rss-feed
|
poll_seconds: 600
|
||||||
title: Phys.org
|
max_entries: 10
|
||||||
poll_seconds: 600
|
|
||||||
max_entries: 10
|
- type: platypush.backend.http.request.rss.RssUpdates
|
||||||
-
|
url: http://feeds.feedburner.com/Techcrunch
|
||||||
type: platypush.backend.http.request.rss.RssUpdates
|
title: Tech Crunch
|
||||||
url: http://feeds.feedburner.com/Techcrunch
|
poll_seconds: 600
|
||||||
title: Tech Crunch
|
max_entries: 10
|
||||||
poll_seconds: 600
|
|
||||||
max_entries: 10
|
- type: platypush.backend.http.request.rss.RssUpdates
|
||||||
-
|
url: http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml
|
||||||
type: platypush.backend.http.request.rss.RssUpdates
|
title: The New York Times
|
||||||
url: http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml
|
poll_seconds: 300
|
||||||
title: The New York Times
|
max_entries: 10
|
||||||
poll_seconds: 300
|
|
||||||
max_entries: 10
|
|
||||||
|
|
||||||
# MQTT backend. Installed required dependencies through 'pip install "platypush[mqtt]"'
|
# MQTT backend. Installed required dependencies through 'pip install "platypush[mqtt]"'
|
||||||
backend.mqtt:
|
backend.mqtt:
|
||||||
|
@ -196,15 +159,15 @@ backend.mqtt:
|
||||||
host: mqtt-server
|
host: mqtt-server
|
||||||
# By default the backend will listen for messages on the platypush_bus_mq/device_id
|
# By default the backend will listen for messages on the platypush_bus_mq/device_id
|
||||||
# topic, but you can change the prefix using the topic attribute
|
# topic, but you can change the prefix using the topic attribute
|
||||||
topic: my_platypush_bus
|
# topic: MyBus
|
||||||
|
|
||||||
# Raw TCP socket backend. It can run commands sent as JSON over telnet or netcat
|
# Raw TCP socket backend. It can run commands sent as JSON over telnet or netcat
|
||||||
backend.tcp:
|
#backend.tcp:
|
||||||
port: 3333
|
# port: 3333
|
||||||
|
|
||||||
# Websocket backend. Install required dependencies through 'pip install "platypush[http]"'
|
# Websocket backend. Install required dependencies through 'pip install "platypush[http]"'
|
||||||
backend.websocket:
|
#backend.websocket:
|
||||||
port: 8765
|
# port: 8765
|
||||||
|
|
||||||
## --
|
## --
|
||||||
## Assistant configuration examples
|
## Assistant configuration examples
|
||||||
|
@ -254,9 +217,12 @@ backend.assistant.snowboy:
|
||||||
assistant.echo:
|
assistant.echo:
|
||||||
audio_player: mplayer
|
audio_player: mplayer
|
||||||
|
|
||||||
# Install Google Assistant dependencies with 'pip install "platypush[google-assistant]"'
|
# Install Google Assistant dependencies with 'pip install "platypush[google-assistant-legacy]"'
|
||||||
assistant.google.pushtotalk:
|
assistant.google:
|
||||||
language: en-US
|
enabled: True
|
||||||
|
|
||||||
|
backend.assistant.google:
|
||||||
|
enabled: True
|
||||||
|
|
||||||
## --
|
## --
|
||||||
## Procedure examples
|
## Procedure examples
|
||||||
|
@ -327,14 +293,14 @@ procedure.outside_home:
|
||||||
procedure.send_request(target, action, args):
|
procedure.send_request(target, action, args):
|
||||||
- action: mqtt.send_message
|
- action: mqtt.send_message
|
||||||
args:
|
args:
|
||||||
topic: my_platypush_bus/${target}
|
topic: platypush_bus_mq/${target}
|
||||||
host: mqtt-server
|
host: mqtt-server
|
||||||
port: 1883
|
port: 1883
|
||||||
msg:
|
msg:
|
||||||
type: request
|
type: request
|
||||||
target: ${target}
|
target: ${target}
|
||||||
action: ${action}
|
action: ${action}
|
||||||
args: "${context.get('args', {}}"
|
args: ${args}
|
||||||
|
|
||||||
## --
|
## --
|
||||||
## Event hook examples
|
## Event hook examples
|
||||||
|
@ -379,10 +345,8 @@ event.hook.SearchSongVoiceCommand:
|
||||||
- action: music.mpd.search
|
- action: music.mpd.search
|
||||||
args:
|
args:
|
||||||
filter:
|
filter:
|
||||||
- artist
|
artist: ${artist}
|
||||||
- ${artist}
|
title: ${title}
|
||||||
- any
|
|
||||||
- ${title}
|
|
||||||
|
|
||||||
# Play the first search result
|
# Play the first search result
|
||||||
- action: music.mpd.play
|
- action: music.mpd.play
|
||||||
|
|
33
examples/conf/dashboard.xml
Normal file
33
examples/conf/dashboard.xml
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<!-- Dashboard templates are stored as ~/.config/platypush/dashboards/<name>.xml and can be accessed on
|
||||||
|
http://<host>:8008/dashboard/<name>. A dashboard can show a custom set of widgets on a screen - e.g. calendar
|
||||||
|
events, media information, photo carousels, sensors data, weather forecast and news headlines. The available
|
||||||
|
widgets are stored as Vue.js templates under `platypush/backend/http/webapp/src/components/widgets`. -->
|
||||||
|
<Dashboard>
|
||||||
|
<!-- Display the following widgets on the same row. Each row consists of 12 columns.
|
||||||
|
You can specify the width of each widget either through class name (e.g. col-6 means
|
||||||
|
6 columns out of 12, e.g. half the size of the row) or inline style
|
||||||
|
(e.g. `style="width: 50%"`). -->
|
||||||
|
<Row>
|
||||||
|
<!-- Show a calendar widget with the upcoming events. It requires the `calendar` plugin to
|
||||||
|
be enabled and configured. -->
|
||||||
|
<Calendar class="col-6" />
|
||||||
|
|
||||||
|
<!-- Show the current track and other playback info. It requires `music.mpd` plugin or any
|
||||||
|
other music plugin enabled. -->
|
||||||
|
<Music class="col-3" />
|
||||||
|
|
||||||
|
<!-- Show current date, time and weather. It requires a `weather` plugin or backend enabled -->
|
||||||
|
<DateTimeWeather class="col-3" />
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<!-- Display the following widgets on a second row -->
|
||||||
|
<Row>
|
||||||
|
<!-- Show a carousel of images from a local folder. For security reasons, the folder must be
|
||||||
|
explicitly exposed as an HTTP resource through the backend `resource_dirs` attribute. -->
|
||||||
|
<ImageCarousel class="col-6" img-dir="/mnt/hd/photos/carousel" />
|
||||||
|
|
||||||
|
<!-- Show the news headlines parsed from a list of RSS feed and stored locally through the
|
||||||
|
`http.poll` backend -->
|
||||||
|
<RssNews class="col-6" db="sqlite:////path/to/your/rss.db" />
|
||||||
|
</Row>
|
||||||
|
</Dashboard>
|
43
examples/conf/hook.py
Normal file
43
examples/conf/hook.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# A more versatile way to define event hooks than the YAML format of `config.yaml` is through native Python scripts.
|
||||||
|
# You can define hooks as simple Python functions that use the `platypush.event.hook.hook` decorator to specify on
|
||||||
|
# which event type they should be called, and optionally on which event attribute values.
|
||||||
|
#
|
||||||
|
# Event hooks should be stored in Python files under `~/.config/platypush/scripts`. All the functions that use the
|
||||||
|
# @hook decorator will automatically be discovered and imported as event hooks into the platform at runtime.
|
||||||
|
|
||||||
|
# `run` is a utility function that runs a request by name (e.g. `light.hue.on`).
|
||||||
|
from platypush.utils import run
|
||||||
|
|
||||||
|
# @hook decorator
|
||||||
|
from platypush.event.hook import hook
|
||||||
|
|
||||||
|
# Event types that you want to react to
|
||||||
|
from platypush.message.event.assistant import ConversationStartEvent, SpeechRecognizedEvent
|
||||||
|
|
||||||
|
|
||||||
|
@hook(SpeechRecognizedEvent, phrase='play ${title} by ${artist}')
|
||||||
|
def on_music_play_command(event, title=None, artist=None, **context):
|
||||||
|
"""
|
||||||
|
This function will be executed when a SpeechRecognizedEvent with `phrase="play the music"` is triggered.
|
||||||
|
`event` contains the event object and `context` any key-value info from the running context.
|
||||||
|
Note that in this specific case we can leverage the token-extraction feature of SpeechRecognizedEvent through
|
||||||
|
${} that operates on regex-like principles to extract any text that matches the pattern into context variables.
|
||||||
|
"""
|
||||||
|
results = run('music.mpd.search', filter={
|
||||||
|
'artist': artist,
|
||||||
|
'title': title,
|
||||||
|
})
|
||||||
|
|
||||||
|
if results:
|
||||||
|
run('music.mpd.play', results[0]['file'])
|
||||||
|
else:
|
||||||
|
run('tts.say', "I can't find any music matching your query")
|
||||||
|
|
||||||
|
|
||||||
|
@hook(ConversationStartEvent)
|
||||||
|
def on_conversation_start(event, **context):
|
||||||
|
"""
|
||||||
|
A simple hook that gets invoked when a new conversation starts with a voice assistant and simply pauses the music
|
||||||
|
to make sure that your speech is properly detected.
|
||||||
|
"""
|
||||||
|
run('music.mpd.pause_if_playing')
|
|
@ -16,18 +16,17 @@ from .context import register_backends
|
||||||
from .cron.scheduler import CronScheduler
|
from .cron.scheduler import CronScheduler
|
||||||
from .event.processor import EventProcessor
|
from .event.processor import EventProcessor
|
||||||
from .logger import Logger
|
from .logger import Logger
|
||||||
from .message.event import Event, StopEvent
|
from .message.event import Event
|
||||||
from .message.event.application import ApplicationStartedEvent, ApplicationStoppedEvent
|
from .message.event.application import ApplicationStartedEvent
|
||||||
from .message.request import Request
|
from .message.request import Request
|
||||||
from .message.response import Response
|
from .message.response import Response
|
||||||
from .utils import set_thread_name
|
from .utils import set_thread_name
|
||||||
|
|
||||||
|
|
||||||
__author__ = 'Fabio Manganiello <blacklight86@gmail.com>'
|
__author__ = 'Fabio Manganiello <info@fabiomanganiello.com>'
|
||||||
__version__ = '0.13.2'
|
__version__ = '0.21.1'
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
logger = logging.getLogger('platypush')
|
||||||
LOGGER.setLevel(logging.INFO)
|
|
||||||
|
|
||||||
|
|
||||||
class Daemon:
|
class Daemon:
|
||||||
|
@ -42,6 +41,9 @@ class Daemon:
|
||||||
# - plugins will post the responses they process
|
# - plugins will post the responses they process
|
||||||
bus = None
|
bus = None
|
||||||
|
|
||||||
|
# Default bus queue name
|
||||||
|
_default_redis_queue = 'platypush/bus'
|
||||||
|
|
||||||
pidfile = None
|
pidfile = None
|
||||||
|
|
||||||
# backend_name => backend_obj map
|
# backend_name => backend_obj map
|
||||||
|
@ -51,7 +53,7 @@ class Daemon:
|
||||||
n_tries = 2
|
n_tries = 2
|
||||||
|
|
||||||
def __init__(self, config_file=None, pidfile=None, requests_to_process=None,
|
def __init__(self, config_file=None, pidfile=None, requests_to_process=None,
|
||||||
no_capture_stdout=False, no_capture_stderr=False):
|
no_capture_stdout=False, no_capture_stderr=False, redis_queue=None):
|
||||||
"""
|
"""
|
||||||
Constructor
|
Constructor
|
||||||
Params:
|
Params:
|
||||||
|
@ -65,6 +67,7 @@ class Daemon:
|
||||||
capture by the logging system
|
capture by the logging system
|
||||||
no_capture_stderr -- Set to true if you want to disable the stderr
|
no_capture_stderr -- Set to true if you want to disable the stderr
|
||||||
capture by the logging system
|
capture by the logging system
|
||||||
|
redis_queue -- Name of the (Redis) queue used for dispatching messages (default: platypush/bus).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if pidfile:
|
if pidfile:
|
||||||
|
@ -72,15 +75,21 @@ class Daemon:
|
||||||
with open(self.pidfile, 'w') as f:
|
with open(self.pidfile, 'w') as f:
|
||||||
f.write(str(os.getpid()))
|
f.write(str(os.getpid()))
|
||||||
|
|
||||||
|
self.redis_queue = redis_queue or self._default_redis_queue
|
||||||
self.config_file = config_file
|
self.config_file = config_file
|
||||||
Config.init(self.config_file)
|
Config.init(self.config_file)
|
||||||
logging.basicConfig(**Config.get('logging'))
|
logging.basicConfig(**Config.get('logging'))
|
||||||
|
|
||||||
|
redis_conf = Config.get('backend.redis') or {}
|
||||||
|
self.bus = RedisBus(redis_queue=self.redis_queue, on_message=self.on_message(),
|
||||||
|
**redis_conf.get('redis_args', {}))
|
||||||
|
|
||||||
self.no_capture_stdout = no_capture_stdout
|
self.no_capture_stdout = no_capture_stdout
|
||||||
self.no_capture_stderr = no_capture_stderr
|
self.no_capture_stderr = no_capture_stderr
|
||||||
self.event_processor = EventProcessor()
|
self.event_processor = EventProcessor()
|
||||||
self.requests_to_process = requests_to_process
|
self.requests_to_process = requests_to_process
|
||||||
self.processed_requests = 0
|
self.processed_requests = 0
|
||||||
|
self.cron_scheduler = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def build_from_cmdline(cls, args):
|
def build_from_cmdline(cls, args):
|
||||||
|
@ -106,11 +115,17 @@ class Daemon:
|
||||||
help="Set this flag if you have max stack depth " +
|
help="Set this flag if you have max stack depth " +
|
||||||
"exceeded errors so stderr won't be captured by " +
|
"exceeded errors so stderr won't be captured by " +
|
||||||
"the logging system")
|
"the logging system")
|
||||||
|
parser.add_argument('--redis-queue', dest='redis_queue',
|
||||||
|
required=False, action='store_true',
|
||||||
|
default=cls._default_redis_queue,
|
||||||
|
help="Name of the Redis queue to be used to internally deliver messages "
|
||||||
|
"(default: platypush/bus)")
|
||||||
|
|
||||||
opts, args = parser.parse_known_args(args)
|
opts, args = parser.parse_known_args(args)
|
||||||
return cls(config_file=opts.config, pidfile=opts.pidfile,
|
return cls(config_file=opts.config, pidfile=opts.pidfile,
|
||||||
no_capture_stdout=opts.no_capture_stdout,
|
no_capture_stdout=opts.no_capture_stdout,
|
||||||
no_capture_stderr=opts.no_capture_stderr)
|
no_capture_stderr=opts.no_capture_stderr,
|
||||||
|
redis_queue=opts.redis_queue)
|
||||||
|
|
||||||
def on_message(self):
|
def on_message(self):
|
||||||
"""
|
"""
|
||||||
|
@ -128,20 +143,17 @@ class Daemon:
|
||||||
try:
|
try:
|
||||||
msg.execute(n_tries=self.n_tries)
|
msg.execute(n_tries=self.n_tries)
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
LOGGER.info('Dropped unauthorized request: {}'.format(msg))
|
logger.info('Dropped unauthorized request: {}'.format(msg))
|
||||||
|
|
||||||
self.processed_requests += 1
|
self.processed_requests += 1
|
||||||
if self.requests_to_process \
|
if self.requests_to_process \
|
||||||
and self.processed_requests >= self.requests_to_process:
|
and self.processed_requests >= self.requests_to_process:
|
||||||
self.stop_app()
|
self.stop_app()
|
||||||
elif isinstance(msg, Response):
|
elif isinstance(msg, Response):
|
||||||
LOGGER.info('Received response: {}'.format(msg))
|
logger.info('Received response: {}'.format(msg))
|
||||||
elif isinstance(msg, StopEvent) and msg.targets_me():
|
|
||||||
LOGGER.info('Received STOP event: {}'.format(msg))
|
|
||||||
self.stop_app()
|
|
||||||
elif isinstance(msg, Event):
|
elif isinstance(msg, Event):
|
||||||
if not msg.disable_logging:
|
if not msg.disable_logging:
|
||||||
LOGGER.info('Received event: {}'.format(msg))
|
logger.info('Received event: {}'.format(msg))
|
||||||
self.event_processor.process_event(msg)
|
self.event_processor.process_event(msg)
|
||||||
|
|
||||||
return _f
|
return _f
|
||||||
|
@ -150,22 +162,20 @@ class Daemon:
|
||||||
""" Stops the backends and the bus """
|
""" Stops the backends and the bus """
|
||||||
for backend in self.backends.values():
|
for backend in self.backends.values():
|
||||||
backend.stop()
|
backend.stop()
|
||||||
self.bus.stop()
|
|
||||||
|
|
||||||
def start(self):
|
self.bus.stop()
|
||||||
|
if self.cron_scheduler:
|
||||||
|
self.cron_scheduler.stop()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
""" Start the daemon """
|
""" Start the daemon """
|
||||||
if not self.no_capture_stdout:
|
if not self.no_capture_stdout:
|
||||||
sys.stdout = Logger(LOGGER.info)
|
sys.stdout = Logger(logger.info)
|
||||||
if not self.no_capture_stderr:
|
if not self.no_capture_stderr:
|
||||||
sys.stderr = Logger(LOGGER.warning)
|
sys.stderr = Logger(logger.warning)
|
||||||
|
|
||||||
set_thread_name('platypush')
|
set_thread_name('platypush')
|
||||||
|
logger.info('---- Starting platypush v.{}'.format(__version__))
|
||||||
print('---- Starting platypush v.{}'.format(__version__))
|
|
||||||
|
|
||||||
redis_conf = Config.get('backend.redis') or {}
|
|
||||||
self.bus = RedisBus(on_message=self.on_message(),
|
|
||||||
**redis_conf.get('redis_args', {}))
|
|
||||||
|
|
||||||
# Initialize the backends and link them to the bus
|
# Initialize the backends and link them to the bus
|
||||||
self.backends = register_backends(bus=self.bus, global_scope=True)
|
self.backends = register_backends(bus=self.bus, global_scope=True)
|
||||||
|
@ -176,7 +186,8 @@ class Daemon:
|
||||||
|
|
||||||
# Start the cron scheduler
|
# Start the cron scheduler
|
||||||
if Config.get_cronjobs():
|
if Config.get_cronjobs():
|
||||||
CronScheduler(jobs=Config.get_cronjobs()).start()
|
self.cron_scheduler = CronScheduler(jobs=Config.get_cronjobs())
|
||||||
|
self.cron_scheduler.start()
|
||||||
|
|
||||||
self.bus.post(ApplicationStartedEvent())
|
self.bus.post(ApplicationStartedEvent())
|
||||||
|
|
||||||
|
@ -184,9 +195,8 @@ class Daemon:
|
||||||
try:
|
try:
|
||||||
self.bus.poll()
|
self.bus.poll()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
LOGGER.info('SIGINT received, terminating application')
|
logger.info('SIGINT received, terminating application')
|
||||||
finally:
|
finally:
|
||||||
self.bus.post(ApplicationStoppedEvent())
|
|
||||||
self.stop_app()
|
self.stop_app()
|
||||||
|
|
||||||
|
|
||||||
|
@ -194,9 +204,8 @@ def main():
|
||||||
"""
|
"""
|
||||||
Platypush daemon main
|
Platypush daemon main
|
||||||
"""
|
"""
|
||||||
|
|
||||||
app = Daemon.build_from_cmdline(sys.argv[1:])
|
app = Daemon.build_from_cmdline(sys.argv[1:])
|
||||||
app.start()
|
app.run()
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -6,23 +6,22 @@
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
import threading
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from threading import Thread
|
from threading import Thread, Event as ThreadEvent, get_ident
|
||||||
from typing import Optional
|
from typing import Optional, Dict
|
||||||
|
|
||||||
from platypush.bus import Bus
|
from platypush.bus import Bus
|
||||||
from platypush.config import Config
|
from platypush.config import Config
|
||||||
from platypush.context import get_backend
|
from platypush.context import get_backend
|
||||||
from platypush.message.event.zeroconf import ZeroconfServiceAddedEvent, ZeroconfServiceRemovedEvent
|
from platypush.message.event.zeroconf import ZeroconfServiceAddedEvent, ZeroconfServiceRemovedEvent
|
||||||
from platypush.utils import set_timeout, clear_timeout, \
|
from platypush.utils import set_timeout, clear_timeout, \
|
||||||
get_redis_queue_name_by_message, set_thread_name
|
get_redis_queue_name_by_message, set_thread_name, get_backend_name_by_class
|
||||||
|
|
||||||
from platypush import __version__
|
from platypush import __version__
|
||||||
from platypush.event import EventGenerator
|
from platypush.event import EventGenerator
|
||||||
from platypush.message import Message
|
from platypush.message import Message
|
||||||
from platypush.message.event import Event, StopEvent
|
from platypush.message.event import Event
|
||||||
from platypush.message.request import Request
|
from platypush.message.request import Request
|
||||||
from platypush.message.response import Response
|
from platypush.message.response import Response
|
||||||
|
|
||||||
|
@ -62,10 +61,9 @@ class Backend(Thread, EventGenerator):
|
||||||
self.poll_seconds = float(poll_seconds) if poll_seconds else None
|
self.poll_seconds = float(poll_seconds) if poll_seconds else None
|
||||||
self.device_id = Config.get('device_id')
|
self.device_id = Config.get('device_id')
|
||||||
self.thread_id = None
|
self.thread_id = None
|
||||||
self._should_stop = False
|
self._stop_event = ThreadEvent()
|
||||||
self._stop_event = threading.Event()
|
|
||||||
self._kwargs = kwargs
|
self._kwargs = kwargs
|
||||||
self.logger = logging.getLogger(self.__class__.__name__)
|
self.logger = logging.getLogger('platypush:backend:' + get_backend_name_by_class(self.__class__))
|
||||||
self.zeroconf = None
|
self.zeroconf = None
|
||||||
self.zeroconf_info = None
|
self.zeroconf_info = None
|
||||||
|
|
||||||
|
@ -103,12 +101,8 @@ class Backend(Thread, EventGenerator):
|
||||||
self.stop()
|
self.stop()
|
||||||
return
|
return
|
||||||
|
|
||||||
if isinstance(msg, StopEvent) and msg.targets_me():
|
msg.backend = self # Augment message to be able to process responses
|
||||||
self.logger.info('Received STOP event on {}'.format(self.__class__.__name__))
|
self.bus.post(msg)
|
||||||
self._should_stop = True
|
|
||||||
else:
|
|
||||||
msg.backend = self # Augment message to be able to process responses
|
|
||||||
self.bus.post(msg)
|
|
||||||
|
|
||||||
def _is_expected_response(self, msg):
|
def _is_expected_response(self, msg):
|
||||||
""" Internal only - returns true if we are expecting for a response
|
""" Internal only - returns true if we are expecting for a response
|
||||||
|
@ -225,7 +219,7 @@ class Backend(Thread, EventGenerator):
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
""" Starts the backend thread. To be implemented in the derived classes if the loop method isn't defined. """
|
""" Starts the backend thread. To be implemented in the derived classes if the loop method isn't defined. """
|
||||||
self.thread_id = threading.get_ident()
|
self.thread_id = get_ident()
|
||||||
set_thread_name(self._thread_name)
|
set_thread_name(self._thread_name)
|
||||||
if not callable(self.loop):
|
if not callable(self.loop):
|
||||||
return
|
return
|
||||||
|
@ -263,25 +257,22 @@ class Backend(Thread, EventGenerator):
|
||||||
|
|
||||||
def on_stop(self):
|
def on_stop(self):
|
||||||
""" Callback invoked when the process stops """
|
""" Callback invoked when the process stops """
|
||||||
self.unregister_service()
|
pass
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
""" Stops the backend thread by sending a STOP event on its bus """
|
""" Stops the backend thread by sending a STOP event on its bus """
|
||||||
def _async_stop():
|
def _async_stop():
|
||||||
evt = StopEvent(target=self.device_id, origin=self.device_id,
|
|
||||||
thread_id=self.thread_id)
|
|
||||||
|
|
||||||
self.send_message(evt)
|
|
||||||
self._stop_event.set()
|
self._stop_event.set()
|
||||||
|
self.unregister_service()
|
||||||
self.on_stop()
|
self.on_stop()
|
||||||
|
|
||||||
Thread(target=_async_stop).start()
|
Thread(target=_async_stop).start()
|
||||||
|
|
||||||
def should_stop(self):
|
def should_stop(self):
|
||||||
return self._should_stop
|
return self._stop_event.is_set()
|
||||||
|
|
||||||
def wait_stop(self, timeout=None):
|
def wait_stop(self, timeout=None) -> bool:
|
||||||
self._stop_event.wait(timeout)
|
return self._stop_event.wait(timeout)
|
||||||
|
|
||||||
def _get_redis(self):
|
def _get_redis(self):
|
||||||
import redis
|
import redis
|
||||||
|
@ -321,12 +312,35 @@ class Backend(Thread, EventGenerator):
|
||||||
s.close()
|
s.close()
|
||||||
return addr
|
return addr
|
||||||
|
|
||||||
def register_service(self, port: Optional[int] = None, name: Optional[str] = None, udp: bool = False):
|
def register_service(self,
|
||||||
|
port: Optional[int] = None,
|
||||||
|
name: Optional[str] = None,
|
||||||
|
srv_type: Optional[str] = None,
|
||||||
|
srv_name: Optional[str] = None,
|
||||||
|
udp: bool = False,
|
||||||
|
properties: Optional[Dict] = None):
|
||||||
"""
|
"""
|
||||||
Initialize the Zeroconf service configuration for this backend.
|
Initialize the Zeroconf service configuration for this backend.
|
||||||
|
|
||||||
|
:param port: Service listen port (default: the backend ``port`` attribute if available, or ``None``).
|
||||||
|
:param name: Service short name (default: backend name).
|
||||||
|
:param srv_type: Service type (default: ``_platypush-{name}._{proto}.local.``).
|
||||||
|
:param srv_name: Full service name (default: ``{hostname or device_id}.{type}``).
|
||||||
|
:param udp: Set to True if this is a UDP service.
|
||||||
|
:param properties: Extra properties to be passed on the service. Default:
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Platypush",
|
||||||
|
"vendor": "Platypush",
|
||||||
|
"version": "{platypush_version}"
|
||||||
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from zeroconf import ServiceInfo, Zeroconf
|
from zeroconf import ServiceInfo, Zeroconf
|
||||||
|
from platypush.plugins.zeroconf import ZeroconfListener
|
||||||
except ImportError:
|
except ImportError:
|
||||||
self.logger.warning('zeroconf package not available, service discovery will be disabled.')
|
self.logger.warning('zeroconf package not available, service discovery will be disabled.')
|
||||||
return
|
return
|
||||||
|
@ -336,11 +350,12 @@ class Backend(Thread, EventGenerator):
|
||||||
'name': 'Platypush',
|
'name': 'Platypush',
|
||||||
'vendor': 'Platypush',
|
'vendor': 'Platypush',
|
||||||
'version': __version__,
|
'version': __version__,
|
||||||
|
**(properties or {}),
|
||||||
}
|
}
|
||||||
|
|
||||||
name = name or re.sub(r'Backend$', '', self.__class__.__name__).lower()
|
name = name or re.sub(r'Backend$', '', self.__class__.__name__).lower()
|
||||||
srv_type = '_platypush-{name}._{proto}.local.'.format(name=name, proto='udp' if udp else 'tcp')
|
srv_type = srv_type or '_platypush-{name}._{proto}.local.'.format(name=name, proto='udp' if udp else 'tcp')
|
||||||
srv_name = '{host}.{type}'.format(host=self.device_id, type=srv_type)
|
srv_name = srv_name or '{host}.{type}'.format(host=self.device_id, type=srv_type)
|
||||||
|
|
||||||
if port:
|
if port:
|
||||||
srv_port = port
|
srv_port = port
|
||||||
|
@ -354,18 +369,33 @@ class Backend(Thread, EventGenerator):
|
||||||
priority=0,
|
priority=0,
|
||||||
properties=srv_desc)
|
properties=srv_desc)
|
||||||
|
|
||||||
|
if not self.zeroconf_info:
|
||||||
|
self.logger.warning('Could not register Zeroconf service')
|
||||||
|
return
|
||||||
|
|
||||||
self.zeroconf.register_service(self.zeroconf_info)
|
self.zeroconf.register_service(self.zeroconf_info)
|
||||||
self.bus.post(ZeroconfServiceAddedEvent(service_type=srv_type, service_name=srv_name))
|
self.bus.post(ZeroconfServiceAddedEvent(service_type=srv_type, service_name=srv_name,
|
||||||
|
service_info=ZeroconfListener.parse_service_info(self.zeroconf_info)))
|
||||||
|
|
||||||
def unregister_service(self):
|
def unregister_service(self):
|
||||||
"""
|
"""
|
||||||
Unregister the Zeroconf service configuration if available.
|
Unregister the Zeroconf service configuration if available.
|
||||||
"""
|
"""
|
||||||
if self.zeroconf and self.zeroconf_info:
|
if self.zeroconf and self.zeroconf_info:
|
||||||
self.zeroconf.unregister_service(self.zeroconf_info)
|
try:
|
||||||
self.zeroconf.close()
|
self.zeroconf.unregister_service(self.zeroconf_info)
|
||||||
self.bus.post(ZeroconfServiceRemovedEvent(service_type=self.zeroconf_info.type,
|
except Exception as e:
|
||||||
service_name=self.zeroconf_info.name))
|
self.logger.warning('Could not register Zeroconf service {}: {}: {}'.format(
|
||||||
|
self.zeroconf_info.name, type(e).__name__, str(e)))
|
||||||
|
|
||||||
|
if self.zeroconf:
|
||||||
|
self.zeroconf.close()
|
||||||
|
|
||||||
|
if self.zeroconf_info:
|
||||||
|
self.bus.post(ZeroconfServiceRemovedEvent(service_type=self.zeroconf_info.type,
|
||||||
|
service_name=self.zeroconf_info.name))
|
||||||
|
else:
|
||||||
|
self.bus.post(ZeroconfServiceRemovedEvent(service_type=None, service_name=None))
|
||||||
|
|
||||||
self.zeroconf_info = None
|
self.zeroconf_info = None
|
||||||
self.zeroconf = None
|
self.zeroconf = None
|
||||||
|
|
|
@ -42,6 +42,7 @@ class AdafruitIoBackend(Backend):
|
||||||
if not plugin:
|
if not plugin:
|
||||||
raise RuntimeError('Adafruit IO plugin not configured')
|
raise RuntimeError('Adafruit IO plugin not configured')
|
||||||
|
|
||||||
|
# noinspection PyProtectedMember
|
||||||
self._client = MQTTClient(plugin._username, plugin._key)
|
self._client = MQTTClient(plugin._username, plugin._key)
|
||||||
self._client.on_connect = self.on_connect()
|
self._client.on_connect = self.on_connect()
|
||||||
self._client.on_disconnect = self.on_disconnect()
|
self._client.on_disconnect = self.on_disconnect()
|
||||||
|
@ -52,18 +53,25 @@ class AdafruitIoBackend(Backend):
|
||||||
for feed in self.feeds:
|
for feed in self.feeds:
|
||||||
client.subscribe(feed)
|
client.subscribe(feed)
|
||||||
self.bus.post(ConnectedEvent())
|
self.bus.post(ConnectedEvent())
|
||||||
|
|
||||||
return _handler
|
return _handler
|
||||||
|
|
||||||
def on_disconnect(self):
|
def on_disconnect(self):
|
||||||
def _handler(client):
|
def _handler(client):
|
||||||
self.bus.post(DisconnectedEvent())
|
self.bus.post(DisconnectedEvent())
|
||||||
|
|
||||||
return _handler
|
return _handler
|
||||||
|
|
||||||
def on_message(self):
|
def on_message(self, msg):
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
def _handler(client, feed, data):
|
def _handler(client, feed, data):
|
||||||
try: data = float(data)
|
try:
|
||||||
except: pass
|
data = float(data)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.debug('Not a number: {}: {}'.format(data, e))
|
||||||
|
|
||||||
self.bus.post(FeedUpdateEvent(feed=feed, data=data))
|
self.bus.post(FeedUpdateEvent(feed=feed, data=data))
|
||||||
|
|
||||||
return _handler
|
return _handler
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
@ -81,5 +89,4 @@ class AdafruitIoBackend(Backend):
|
||||||
self.logger.exception(e)
|
self.logger.exception(e)
|
||||||
self._client = None
|
self._client = None
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import datetime
|
import datetime
|
||||||
|
import enum
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import threading
|
import threading
|
||||||
|
@ -6,7 +7,7 @@ import threading
|
||||||
from typing import Optional, Union, Dict, Any, List
|
from typing import Optional, Union, Dict, Any, List
|
||||||
|
|
||||||
import croniter
|
import croniter
|
||||||
import enum
|
from dateutil.tz import gettz
|
||||||
|
|
||||||
from platypush.backend import Backend
|
from platypush.backend import Backend
|
||||||
from platypush.context import get_bus, get_plugin
|
from platypush.context import get_bus, get_plugin
|
||||||
|
@ -54,18 +55,20 @@ class Alarm:
|
||||||
self._runtime_snooze_interval = snooze_interval
|
self._runtime_snooze_interval = snooze_interval
|
||||||
|
|
||||||
def get_next(self) -> float:
|
def get_next(self) -> float:
|
||||||
now = time.time()
|
now = datetime.datetime.now().replace(tzinfo=gettz()) # lgtm [py/call-to-non-callable]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cron = croniter.croniter(self.when, now)
|
cron = croniter.croniter(self.when, now)
|
||||||
return cron.get_next()
|
return cron.get_next()
|
||||||
except (AttributeError, croniter.CroniterBadCronError):
|
except (AttributeError, croniter.CroniterBadCronError):
|
||||||
try:
|
try:
|
||||||
timestamp = datetime.datetime.fromisoformat(self.when).timestamp()
|
timestamp = datetime.datetime.fromisoformat(self.when).replace(
|
||||||
|
tzinfo=gettz()) # lgtm [py/call-to-non-callable]
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
timestamp = (datetime.datetime.now() + datetime.timedelta(seconds=int(self.when))).timestamp()
|
timestamp = (datetime.datetime.now().replace(tzinfo=gettz()) + # lgtm [py/call-to-non-callable]
|
||||||
|
datetime.timedelta(seconds=int(self.when)))
|
||||||
|
|
||||||
return timestamp if timestamp >= now else None
|
return timestamp.timestamp() if timestamp >= now else None
|
||||||
|
|
||||||
def is_enabled(self):
|
def is_enabled(self):
|
||||||
return self._enabled
|
return self._enabled
|
||||||
|
|
|
@ -21,12 +21,10 @@ class AssistantGoogleBackend(AssistantBackend):
|
||||||
|
|
||||||
It listens for voice commands and post conversation events on the bus.
|
It listens for voice commands and post conversation events on the bus.
|
||||||
|
|
||||||
**WARNING**: This backend is deprecated, as the underlying Google Assistant
|
**WARNING**: The Google Assistant library used by this backend has officially been deprecated:
|
||||||
library has been deprecated too: https://developers.google.com/assistant/sdk/reference/library/python/
|
https://developers.google.com/assistant/sdk/reference/library/python/. This backend still works on most of the
|
||||||
The old library might still work on some systems but its proper functioning
|
devices where I use it, but its correct functioning is not guaranteed as the assistant library is no longer
|
||||||
is not guaranteed.
|
maintained.
|
||||||
Please use the Snowboy backend for hotword detection and the Google Assistant
|
|
||||||
push-to-talk plugin for assistant interaction instead.
|
|
||||||
|
|
||||||
Triggers:
|
Triggers:
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
from typing import Dict, Optional
|
import time
|
||||||
|
from threading import Thread, RLock
|
||||||
|
from typing import Dict, Optional, List
|
||||||
|
|
||||||
from platypush.backend.sensor import SensorBackend
|
from platypush.backend.sensor import SensorBackend
|
||||||
|
from platypush.context import get_plugin
|
||||||
from platypush.message.event.bluetooth import BluetoothDeviceFoundEvent, BluetoothDeviceLostEvent
|
from platypush.message.event.bluetooth import BluetoothDeviceFoundEvent, BluetoothDeviceLostEvent
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,10 +23,12 @@ class BluetoothScannerBackend(SensorBackend):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, device_id: Optional[int] = None, scan_duration: int = 10, **kwargs):
|
def __init__(self, device_id: Optional[int] = None, scan_duration: int = 10,
|
||||||
|
track_devices: Optional[List[str]] = None, **kwargs):
|
||||||
"""
|
"""
|
||||||
:param device_id: Bluetooth adapter ID to use (default configured on the ``bluetooth`` plugin if None).
|
:param device_id: Bluetooth adapter ID to use (default configured on the ``bluetooth`` plugin if None).
|
||||||
:param scan_duration: How long the scan should run (default: 10 seconds).
|
:param scan_duration: How long the scan should run (default: 10 seconds).
|
||||||
|
:param track_devices: List of addresses of devices to actively track, even if they aren't discoverable.
|
||||||
"""
|
"""
|
||||||
super().__init__(plugin='bluetooth', plugin_args={
|
super().__init__(plugin='bluetooth', plugin_args={
|
||||||
'device_id': device_id,
|
'device_id': device_id,
|
||||||
|
@ -31,17 +36,72 @@ class BluetoothScannerBackend(SensorBackend):
|
||||||
}, **kwargs)
|
}, **kwargs)
|
||||||
|
|
||||||
self._last_seen_devices = {}
|
self._last_seen_devices = {}
|
||||||
|
self._tracking_thread: Optional[Thread] = None
|
||||||
|
self._bt_lock = RLock()
|
||||||
|
self.track_devices = set(track_devices or [])
|
||||||
|
self.scan_duration = scan_duration
|
||||||
|
|
||||||
|
def _add_last_seen_device(self, dev):
|
||||||
|
addr = dev.pop('addr')
|
||||||
|
if addr not in self._last_seen_devices:
|
||||||
|
self.bus.post(BluetoothDeviceFoundEvent(address=addr, **dev))
|
||||||
|
self._last_seen_devices[addr] = {'addr': addr, **dev}
|
||||||
|
|
||||||
|
def _remove_last_seen_device(self, addr: str):
|
||||||
|
dev = self._last_seen_devices.get(addr)
|
||||||
|
if not dev:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.bus.post(BluetoothDeviceLostEvent(address=addr, **dev))
|
||||||
|
del self._last_seen_devices[addr]
|
||||||
|
|
||||||
|
def _addr_tracker(self, addr):
|
||||||
|
with self._bt_lock:
|
||||||
|
name = get_plugin('bluetooth').lookup_name(addr, timeout=self.scan_duration).name
|
||||||
|
|
||||||
|
if name is None:
|
||||||
|
self._remove_last_seen_device(addr)
|
||||||
|
else:
|
||||||
|
self._add_last_seen_device({'addr': addr, 'name': name})
|
||||||
|
|
||||||
|
def _bt_tracker(self):
|
||||||
|
self.logger.info('Starting Bluetooth tracker')
|
||||||
|
while not self.should_stop():
|
||||||
|
trackers = []
|
||||||
|
for addr in self.track_devices:
|
||||||
|
tracker = Thread(target=self._addr_tracker, args=(addr,))
|
||||||
|
tracker.start()
|
||||||
|
trackers.append(tracker)
|
||||||
|
|
||||||
|
for tracker in trackers:
|
||||||
|
tracker.join(timeout=self.scan_duration)
|
||||||
|
|
||||||
|
time.sleep(self.scan_duration)
|
||||||
|
|
||||||
|
self.logger.info('Bluetooth tracker stopped')
|
||||||
|
|
||||||
|
def get_measurement(self):
|
||||||
|
with self._bt_lock:
|
||||||
|
return super().get_measurement()
|
||||||
|
|
||||||
def process_data(self, data: Dict[str, dict], new_data: Dict[str, dict]):
|
def process_data(self, data: Dict[str, dict], new_data: Dict[str, dict]):
|
||||||
for addr, dev in data.items():
|
for addr, dev in data.items():
|
||||||
if addr not in self._last_seen_devices:
|
self._add_last_seen_device(dev)
|
||||||
self.bus.post(BluetoothDeviceFoundEvent(address=dev.pop('addr'), **dev))
|
|
||||||
self._last_seen_devices[addr] = {'addr': addr, **dev}
|
|
||||||
|
|
||||||
for addr, dev in self._last_seen_devices.copy().items():
|
for addr, dev in self._last_seen_devices.copy().items():
|
||||||
if addr not in data:
|
if addr not in data and addr not in self.track_devices:
|
||||||
self.bus.post(BluetoothDeviceLostEvent(address=dev.pop('addr'), **dev))
|
self._remove_last_seen_device(addr)
|
||||||
del self._last_seen_devices[addr]
|
|
||||||
|
def run(self):
|
||||||
|
self._tracking_thread = Thread(target=self._bt_tracker)
|
||||||
|
self._tracking_thread.start()
|
||||||
|
super().run()
|
||||||
|
|
||||||
|
def on_stop(self):
|
||||||
|
super().on_stop()
|
||||||
|
if self._tracking_thread and self._tracking_thread.is_alive():
|
||||||
|
self.logger.info('Waiting for the Bluetooth tracking thread to stop')
|
||||||
|
self._tracking_thread.join(timeout=self.scan_duration)
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -13,25 +13,28 @@ Bd addr are represented as standard python strings, e.g. "aa:bb:cc:dd:ee:ff".
|
||||||
import asyncio
|
import asyncio
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
import time
|
|
||||||
import struct
|
import struct
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
|
|
||||||
class CreateConnectionChannelError(Enum):
|
class CreateConnectionChannelError(Enum):
|
||||||
NoError = 0
|
NoError = 0
|
||||||
MaxPendingConnectionsReached = 1
|
MaxPendingConnectionsReached = 1
|
||||||
|
|
||||||
|
|
||||||
class ConnectionStatus(Enum):
|
class ConnectionStatus(Enum):
|
||||||
Disconnected = 0
|
Disconnected = 0
|
||||||
Connected = 1
|
Connected = 1
|
||||||
Ready = 2
|
Ready = 2
|
||||||
|
|
||||||
|
|
||||||
class DisconnectReason(Enum):
|
class DisconnectReason(Enum):
|
||||||
Unspecified = 0
|
Unspecified = 0
|
||||||
ConnectionEstablishmentFailed = 1
|
ConnectionEstablishmentFailed = 1
|
||||||
TimedOut = 2
|
TimedOut = 2
|
||||||
BondingKeysMismatch = 3
|
BondingKeysMismatch = 3
|
||||||
|
|
||||||
|
|
||||||
class RemovedReason(Enum):
|
class RemovedReason(Enum):
|
||||||
RemovedByThisClient = 0
|
RemovedByThisClient = 0
|
||||||
ForceDisconnectedByThisClient = 1
|
ForceDisconnectedByThisClient = 1
|
||||||
|
@ -44,6 +47,7 @@ class RemovedReason(Enum):
|
||||||
|
|
||||||
CouldntLoadDevice = 7
|
CouldntLoadDevice = 7
|
||||||
|
|
||||||
|
|
||||||
class ClickType(Enum):
|
class ClickType(Enum):
|
||||||
ButtonDown = 0
|
ButtonDown = 0
|
||||||
ButtonUp = 1
|
ButtonUp = 1
|
||||||
|
@ -52,20 +56,24 @@ class ClickType(Enum):
|
||||||
ButtonDoubleClick = 4
|
ButtonDoubleClick = 4
|
||||||
ButtonHold = 5
|
ButtonHold = 5
|
||||||
|
|
||||||
|
|
||||||
class BdAddrType(Enum):
|
class BdAddrType(Enum):
|
||||||
PublicBdAddrType = 0
|
PublicBdAddrType = 0
|
||||||
RandomBdAddrType = 1
|
RandomBdAddrType = 1
|
||||||
|
|
||||||
|
|
||||||
class LatencyMode(Enum):
|
class LatencyMode(Enum):
|
||||||
NormalLatency = 0
|
NormalLatency = 0
|
||||||
LowLatency = 1
|
LowLatency = 1
|
||||||
HighLatency = 2
|
HighLatency = 2
|
||||||
|
|
||||||
|
|
||||||
class BluetoothControllerState(Enum):
|
class BluetoothControllerState(Enum):
|
||||||
Detached = 0
|
Detached = 0
|
||||||
Resetting = 1
|
Resetting = 1
|
||||||
Attached = 2
|
Attached = 2
|
||||||
|
|
||||||
|
|
||||||
class ScanWizardResult(Enum):
|
class ScanWizardResult(Enum):
|
||||||
WizardSuccess = 0
|
WizardSuccess = 0
|
||||||
WizardCancelledByUser = 1
|
WizardCancelledByUser = 1
|
||||||
|
@ -75,6 +83,7 @@ class ScanWizardResult(Enum):
|
||||||
WizardInternetBackendError = 5
|
WizardInternetBackendError = 5
|
||||||
WizardInvalidData = 6
|
WizardInvalidData = 6
|
||||||
|
|
||||||
|
|
||||||
class ButtonScanner:
|
class ButtonScanner:
|
||||||
"""ButtonScanner class.
|
"""ButtonScanner class.
|
||||||
|
|
||||||
|
@ -90,6 +99,7 @@ class ButtonScanner:
|
||||||
self._scan_id = next(ButtonScanner._cnt)
|
self._scan_id = next(ButtonScanner._cnt)
|
||||||
self.on_advertisement_packet = lambda scanner, bd_addr, name, rssi, is_private, already_verified: None
|
self.on_advertisement_packet = lambda scanner, bd_addr, name, rssi, is_private, already_verified: None
|
||||||
|
|
||||||
|
|
||||||
class ScanWizard:
|
class ScanWizard:
|
||||||
"""ScanWizard class
|
"""ScanWizard class
|
||||||
|
|
||||||
|
@ -113,6 +123,7 @@ class ScanWizard:
|
||||||
self.on_button_connected = lambda scan_wizard, bd_addr, name: None
|
self.on_button_connected = lambda scan_wizard, bd_addr, name: None
|
||||||
self.on_completed = lambda scan_wizard, result, bd_addr, name: None
|
self.on_completed = lambda scan_wizard, result, bd_addr, name: None
|
||||||
|
|
||||||
|
|
||||||
class ButtonConnectionChannel:
|
class ButtonConnectionChannel:
|
||||||
"""ButtonConnectionChannel class.
|
"""ButtonConnectionChannel class.
|
||||||
|
|
||||||
|
@ -133,7 +144,7 @@ class ButtonConnectionChannel:
|
||||||
|
|
||||||
_cnt = itertools.count()
|
_cnt = itertools.count()
|
||||||
|
|
||||||
def __init__(self, bd_addr, latency_mode = LatencyMode.NormalLatency, auto_disconnect_time = 511):
|
def __init__(self, bd_addr, latency_mode=LatencyMode.NormalLatency, auto_disconnect_time=511):
|
||||||
self._conn_id = next(ButtonConnectionChannel._cnt)
|
self._conn_id = next(ButtonConnectionChannel._cnt)
|
||||||
self._bd_addr = bd_addr
|
self._bd_addr = bd_addr
|
||||||
self._latency_mode = latency_mode
|
self._latency_mode = latency_mode
|
||||||
|
@ -164,7 +175,9 @@ class ButtonConnectionChannel:
|
||||||
|
|
||||||
self._latency_mode = latency_mode
|
self._latency_mode = latency_mode
|
||||||
if not self._client._closed:
|
if not self._client._closed:
|
||||||
self._client._send_command("CmdChangeModeParameters", {"conn_id": self._conn_id, "latency_mode": self._latency_mode, "auto_disconnect_time": self._auto_disconnect_time})
|
self._client._send_command("CmdChangeModeParameters",
|
||||||
|
{"conn_id": self._conn_id, "latency_mode": self._latency_mode,
|
||||||
|
"auto_disconnect_time": self._auto_disconnect_time})
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def auto_disconnect_time(self):
|
def auto_disconnect_time(self):
|
||||||
|
@ -178,7 +191,10 @@ class ButtonConnectionChannel:
|
||||||
|
|
||||||
self._auto_disconnect_time = auto_disconnect_time
|
self._auto_disconnect_time = auto_disconnect_time
|
||||||
if not self._client._closed:
|
if not self._client._closed:
|
||||||
self._client._send_command("CmdChangeModeParameters", {"conn_id": self._conn_id, "latency_mode": self._latency_mode, "auto_disconnect_time": self._auto_disconnect_time})
|
self._client._send_command("CmdChangeModeParameters",
|
||||||
|
{"conn_id": self._conn_id, "latency_mode": self._latency_mode,
|
||||||
|
"auto_disconnect_time": self._auto_disconnect_time})
|
||||||
|
|
||||||
|
|
||||||
class FlicClient(asyncio.Protocol):
|
class FlicClient(asyncio.Protocol):
|
||||||
"""FlicClient class.
|
"""FlicClient class.
|
||||||
|
@ -212,7 +228,8 @@ class FlicClient(asyncio.Protocol):
|
||||||
("EvtButtonSingleOrDoubleClick", "<IBBI", "conn_id click_type was_queued time_diff"),
|
("EvtButtonSingleOrDoubleClick", "<IBBI", "conn_id click_type was_queued time_diff"),
|
||||||
("EvtButtonSingleOrDoubleClickOrHold", "<IBBI", "conn_id click_type was_queued time_diff"),
|
("EvtButtonSingleOrDoubleClickOrHold", "<IBBI", "conn_id click_type was_queued time_diff"),
|
||||||
("EvtNewVerifiedButton", "<6s", "bd_addr"),
|
("EvtNewVerifiedButton", "<6s", "bd_addr"),
|
||||||
("EvtGetInfoResponse", "<B6sBBhBBH", "bluetooth_controller_state my_bd_addr my_bd_addr_type max_pending_connections max_concurrently_connected_buttons current_pending_connections currently_no_space_for_new_connection nb_verified_buttons"),
|
("EvtGetInfoResponse", "<B6sBBhBBH",
|
||||||
|
"bluetooth_controller_state my_bd_addr my_bd_addr_type max_pending_connections max_concurrently_connected_buttons current_pending_connections currently_no_space_for_new_connection nb_verified_buttons"),
|
||||||
("EvtNoSpaceForNewConnection", "<B", "max_concurrently_connected_buttons"),
|
("EvtNoSpaceForNewConnection", "<B", "max_concurrently_connected_buttons"),
|
||||||
("EvtGotSpaceForNewConnection", "<B", "max_concurrently_connected_buttons"),
|
("EvtGotSpaceForNewConnection", "<B", "max_concurrently_connected_buttons"),
|
||||||
("EvtBluetoothControllerStateChange", "<B", "state"),
|
("EvtBluetoothControllerStateChange", "<B", "state"),
|
||||||
|
@ -223,8 +240,8 @@ class FlicClient(asyncio.Protocol):
|
||||||
("EvtScanWizardButtonConnected", "<I", "scan_wizard_id"),
|
("EvtScanWizardButtonConnected", "<I", "scan_wizard_id"),
|
||||||
("EvtScanWizardCompleted", "<IB", "scan_wizard_id result")
|
("EvtScanWizardCompleted", "<IB", "scan_wizard_id result")
|
||||||
]
|
]
|
||||||
_EVENT_STRUCTS = list(map(lambda x: None if x == None else struct.Struct(x[1]), _EVENTS))
|
_EVENT_STRUCTS = list(map(lambda x: None if x is None else struct.Struct(x[1]), _EVENTS))
|
||||||
_EVENT_NAMED_TUPLES = list(map(lambda x: None if x == None else namedtuple(x[0], x[2]), _EVENTS))
|
_EVENT_NAMED_TUPLES = list(map(lambda x: None if x is None else namedtuple(x[0], x[2]), _EVENTS))
|
||||||
|
|
||||||
_COMMANDS = [
|
_COMMANDS = [
|
||||||
("CmdGetInfo", "", ""),
|
("CmdGetInfo", "", ""),
|
||||||
|
@ -244,18 +261,19 @@ class FlicClient(asyncio.Protocol):
|
||||||
_COMMAND_NAMED_TUPLES = list(map(lambda x: namedtuple(x[0], x[2]), _COMMANDS))
|
_COMMAND_NAMED_TUPLES = list(map(lambda x: namedtuple(x[0], x[2]), _COMMANDS))
|
||||||
_COMMAND_NAME_TO_OPCODE = dict((x[0], i) for i, x in enumerate(_COMMANDS))
|
_COMMAND_NAME_TO_OPCODE = dict((x[0], i) for i, x in enumerate(_COMMANDS))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def _bdaddr_bytes_to_string(bdaddr_bytes):
|
def _bdaddr_bytes_to_string(bdaddr_bytes):
|
||||||
return ":".join(map(lambda x: "%02x" % x, reversed(bdaddr_bytes)))
|
return ":".join(map(lambda x: "%02x" % x, reversed(bdaddr_bytes)))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def _bdaddr_string_to_bytes(bdaddr_string):
|
def _bdaddr_string_to_bytes(bdaddr_string):
|
||||||
return bytearray.fromhex("".join(reversed(bdaddr_string.split(":"))))
|
return bytearray.fromhex("".join(reversed(bdaddr_string.split(":"))))
|
||||||
|
|
||||||
def __init__(self, loop,parent=None):
|
def __init__(self, loop, parent=None):
|
||||||
self.loop = loop
|
self.loop = loop
|
||||||
self.buffer=b""
|
self.buffer = b""
|
||||||
self.transport=None
|
self.transport = None
|
||||||
self.parent=parent
|
self.parent = parent
|
||||||
self._scanners = {}
|
self._scanners = {}
|
||||||
self._scan_wizards = {}
|
self._scan_wizards = {}
|
||||||
self._connection_channels = {}
|
self._connection_channels = {}
|
||||||
|
@ -269,11 +287,10 @@ class FlicClient(asyncio.Protocol):
|
||||||
self.on_get_button_uuid = lambda addr, uuid: None
|
self.on_get_button_uuid = lambda addr, uuid: None
|
||||||
|
|
||||||
def connection_made(self, transport):
|
def connection_made(self, transport):
|
||||||
self.transport=transport
|
self.transport = transport
|
||||||
if self.parent:
|
if self.parent:
|
||||||
self.parent.register_protocol(self)
|
self.parent.register_protocol(self)
|
||||||
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""Closes the client. The handle_events() method will return."""
|
"""Closes the client. The handle_events() method will return."""
|
||||||
if self._closed:
|
if self._closed:
|
||||||
|
@ -342,7 +359,9 @@ class FlicClient(asyncio.Protocol):
|
||||||
channel._client = self
|
channel._client = self
|
||||||
|
|
||||||
self._connection_channels[channel._conn_id] = channel
|
self._connection_channels[channel._conn_id] = channel
|
||||||
self._send_command("CmdCreateConnectionChannel", {"conn_id": channel._conn_id, "bd_addr": channel.bd_addr, "latency_mode": channel._latency_mode, "auto_disconnect_time": channel._auto_disconnect_time})
|
self._send_command("CmdCreateConnectionChannel", {"conn_id": channel._conn_id, "bd_addr": channel.bd_addr,
|
||||||
|
"latency_mode": channel._latency_mode,
|
||||||
|
"auto_disconnect_time": channel._auto_disconnect_time})
|
||||||
|
|
||||||
def remove_connection_channel(self, channel):
|
def remove_connection_channel(self, channel):
|
||||||
"""Remove a connection channel.
|
"""Remove a connection channel.
|
||||||
|
@ -384,7 +403,6 @@ class FlicClient(asyncio.Protocol):
|
||||||
"""
|
"""
|
||||||
self._send_command("CmdGetButtonUUID", {"bd_addr": bd_addr})
|
self._send_command("CmdGetButtonUUID", {"bd_addr": bd_addr})
|
||||||
|
|
||||||
|
|
||||||
def run_on_handle_events_thread(self, callback):
|
def run_on_handle_events_thread(self, callback):
|
||||||
"""Run a function on the thread that handles the events."""
|
"""Run a function on the thread that handles the events."""
|
||||||
if threading.get_ident() == self._handle_event_thread_ident:
|
if threading.get_ident() == self._handle_event_thread_ident:
|
||||||
|
@ -399,7 +417,7 @@ class FlicClient(asyncio.Protocol):
|
||||||
items[key] = value.value
|
items[key] = value.value
|
||||||
|
|
||||||
if "bd_addr" in items:
|
if "bd_addr" in items:
|
||||||
items["bd_addr"] = FlicClient._bdaddr_string_to_bytes(items["bd_addr"])
|
items["bd_addr"] = FlicClient._bdaddr_string_to_bytes()
|
||||||
|
|
||||||
opcode = FlicClient._COMMAND_NAME_TO_OPCODE[name]
|
opcode = FlicClient._COMMAND_NAME_TO_OPCODE[name]
|
||||||
data_bytes = FlicClient._COMMAND_STRUCTS[opcode].pack(*FlicClient._COMMAND_NAMED_TUPLES[opcode](**items))
|
data_bytes = FlicClient._COMMAND_STRUCTS[opcode].pack(*FlicClient._COMMAND_NAMED_TUPLES[opcode](**items))
|
||||||
|
@ -415,16 +433,16 @@ class FlicClient(asyncio.Protocol):
|
||||||
return
|
return
|
||||||
opcode = data[0]
|
opcode = data[0]
|
||||||
|
|
||||||
if opcode >= len(FlicClient._EVENTS) or FlicClient._EVENTS[opcode] == None:
|
if opcode >= len(FlicClient._EVENTS) or FlicClient._EVENTS[opcode] is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
event_name = FlicClient._EVENTS[opcode][0]
|
event_name = FlicClient._EVENTS[opcode][0]
|
||||||
data_tuple = FlicClient._EVENT_STRUCTS[opcode].unpack(data[1 : 1 + FlicClient._EVENT_STRUCTS[opcode].size])
|
data_tuple = FlicClient._EVENT_STRUCTS[opcode].unpack(data[1: 1 + FlicClient._EVENT_STRUCTS[opcode].size])
|
||||||
items = FlicClient._EVENT_NAMED_TUPLES[opcode]._make(data_tuple)._asdict()
|
items = FlicClient._EVENT_NAMED_TUPLES[opcode]._make(data_tuple)._asdict()
|
||||||
|
|
||||||
# Process some kind of items whose data type is not supported by struct
|
# Process some kind of items whose data type is not supported by struct
|
||||||
if "bd_addr" in items:
|
if "bd_addr" in items:
|
||||||
items["bd_addr"] = FlicClient._bdaddr_bytes_to_string(items["bd_addr"])
|
items["bd_addr"] = FlicClient._bdaddr_bytes_to_string()
|
||||||
|
|
||||||
if "name" in items:
|
if "name" in items:
|
||||||
items["name"] = items["name"].decode("utf-8")
|
items["name"] = items["name"].decode("utf-8")
|
||||||
|
@ -445,13 +463,14 @@ class FlicClient(asyncio.Protocol):
|
||||||
|
|
||||||
if event_name == "EvtGetInfoResponse":
|
if event_name == "EvtGetInfoResponse":
|
||||||
items["bluetooth_controller_state"] = BluetoothControllerState(items["bluetooth_controller_state"])
|
items["bluetooth_controller_state"] = BluetoothControllerState(items["bluetooth_controller_state"])
|
||||||
items["my_bd_addr"] = FlicClient._bdaddr_bytes_to_string(items["my_bd_addr"])
|
items["my_bd_addr"] = FlicClient._bdaddr_bytes_to_string()
|
||||||
items["my_bd_addr_type"] = BdAddrType(items["my_bd_addr_type"])
|
items["my_bd_addr_type"] = BdAddrType(items["my_bd_addr_type"])
|
||||||
items["bd_addr_of_verified_buttons"] = []
|
items["bd_addr_of_verified_buttons"] = []
|
||||||
|
|
||||||
pos = FlicClient._EVENT_STRUCTS[opcode].size
|
pos = FlicClient._EVENT_STRUCTS[opcode].size
|
||||||
for i in range(items["nb_verified_buttons"]):
|
for i in range(items["nb_verified_buttons"]):
|
||||||
items["bd_addr_of_verified_buttons"].append(FlicClient._bdaddr_bytes_to_string(data[1 + pos : 1 + pos + 6]))
|
items["bd_addr_of_verified_buttons"].append(
|
||||||
|
FlicClient._bdaddr_bytes_to_string())
|
||||||
pos += 6
|
pos += 6
|
||||||
|
|
||||||
if event_name == "EvtBluetoothControllerStateChange":
|
if event_name == "EvtBluetoothControllerStateChange":
|
||||||
|
@ -469,7 +488,8 @@ class FlicClient(asyncio.Protocol):
|
||||||
if event_name == "EvtAdvertisementPacket":
|
if event_name == "EvtAdvertisementPacket":
|
||||||
scanner = self._scanners.get(items["scan_id"])
|
scanner = self._scanners.get(items["scan_id"])
|
||||||
if scanner is not None:
|
if scanner is not None:
|
||||||
scanner.on_advertisement_packet(scanner, items["bd_addr"], items["name"], items["rssi"], items["is_private"], items["already_verified"])
|
scanner.on_advertisement_packet(scanner, items["bd_addr"], items["name"], items["rssi"],
|
||||||
|
items["is_private"], items["already_verified"])
|
||||||
|
|
||||||
if event_name == "EvtCreateConnectionChannelResponse":
|
if event_name == "EvtCreateConnectionChannelResponse":
|
||||||
channel = self._connection_channels[items["conn_id"]]
|
channel = self._connection_channels[items["conn_id"]]
|
||||||
|
@ -494,10 +514,12 @@ class FlicClient(asyncio.Protocol):
|
||||||
channel.on_button_click_or_hold(channel, items["click_type"], items["was_queued"], items["time_diff"])
|
channel.on_button_click_or_hold(channel, items["click_type"], items["was_queued"], items["time_diff"])
|
||||||
if event_name == "EvtButtonSingleOrDoubleClick":
|
if event_name == "EvtButtonSingleOrDoubleClick":
|
||||||
channel = self._connection_channels[items["conn_id"]]
|
channel = self._connection_channels[items["conn_id"]]
|
||||||
channel.on_button_single_or_double_click(channel, items["click_type"], items["was_queued"], items["time_diff"])
|
channel.on_button_single_or_double_click(channel, items["click_type"], items["was_queued"],
|
||||||
|
items["time_diff"])
|
||||||
if event_name == "EvtButtonSingleOrDoubleClickOrHold":
|
if event_name == "EvtButtonSingleOrDoubleClickOrHold":
|
||||||
channel = self._connection_channels[items["conn_id"]]
|
channel = self._connection_channels[items["conn_id"]]
|
||||||
channel.on_button_single_or_double_click_or_hold(channel, items["click_type"], items["was_queued"], items["time_diff"])
|
channel.on_button_single_or_double_click_or_hold(channel, items["click_type"], items["was_queued"],
|
||||||
|
items["time_diff"])
|
||||||
|
|
||||||
if event_name == "EvtNewVerifiedButton":
|
if event_name == "EvtNewVerifiedButton":
|
||||||
self.on_new_verified_button(items["bd_addr"])
|
self.on_new_verified_button(items["bd_addr"])
|
||||||
|
@ -536,19 +558,16 @@ class FlicClient(asyncio.Protocol):
|
||||||
del self._scan_wizards[items["scan_wizard_id"]]
|
del self._scan_wizards[items["scan_wizard_id"]]
|
||||||
scan_wizard.on_completed(scan_wizard, items["result"], scan_wizard._bd_addr, scan_wizard._name)
|
scan_wizard.on_completed(scan_wizard, items["result"], scan_wizard._bd_addr, scan_wizard._name)
|
||||||
|
|
||||||
|
def data_received(self, data):
|
||||||
def data_received(self,data):
|
cdata = self.buffer + data
|
||||||
cdata=self.buffer+data
|
self.buffer = b""
|
||||||
self.buffer=b""
|
|
||||||
while len(cdata):
|
while len(cdata):
|
||||||
packet_len = cdata[0] | (cdata[1] << 8)
|
packet_len = cdata[0] | (cdata[1] << 8)
|
||||||
packet_len += 2
|
packet_len += 2
|
||||||
if len(cdata)>= packet_len:
|
if len(cdata) >= packet_len:
|
||||||
self._dispatch_event(cdata[2:packet_len])
|
self._dispatch_event(cdata[2:packet_len])
|
||||||
cdata=cdata[packet_len:]
|
cdata = cdata[packet_len:]
|
||||||
else:
|
else:
|
||||||
if len(cdata):
|
if len(cdata):
|
||||||
self.buffer=cdata #unlikely to happen but.....
|
self.buffer = cdata # unlikely to happen but.....
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -229,8 +229,8 @@ class FlicClient:
|
||||||
("EvtScanWizardButtonConnected", "<I", "scan_wizard_id"),
|
("EvtScanWizardButtonConnected", "<I", "scan_wizard_id"),
|
||||||
("EvtScanWizardCompleted", "<IB", "scan_wizard_id result")
|
("EvtScanWizardCompleted", "<IB", "scan_wizard_id result")
|
||||||
]
|
]
|
||||||
_EVENT_STRUCTS = list(map(lambda x: None if x == None else struct.Struct(x[1]), _EVENTS))
|
_EVENT_STRUCTS = list(map(lambda x: None if x is None else struct.Struct(x[1]), _EVENTS))
|
||||||
_EVENT_NAMED_TUPLES = list(map(lambda x: None if x == None else namedtuple(x[0], x[2]), _EVENTS))
|
_EVENT_NAMED_TUPLES = list(map(lambda x: None if x is None else namedtuple(x[0], x[2]), _EVENTS))
|
||||||
|
|
||||||
_COMMANDS = [
|
_COMMANDS = [
|
||||||
("CmdGetInfo", "", ""),
|
("CmdGetInfo", "", ""),
|
||||||
|
@ -250,9 +250,11 @@ class FlicClient:
|
||||||
_COMMAND_NAMED_TUPLES = list(map(lambda x: namedtuple(x[0], x[2]), _COMMANDS))
|
_COMMAND_NAMED_TUPLES = list(map(lambda x: namedtuple(x[0], x[2]), _COMMANDS))
|
||||||
_COMMAND_NAME_TO_OPCODE = dict((x[0], i) for i, x in enumerate(_COMMANDS))
|
_COMMAND_NAME_TO_OPCODE = dict((x[0], i) for i, x in enumerate(_COMMANDS))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def _bdaddr_bytes_to_string(bdaddr_bytes):
|
def _bdaddr_bytes_to_string(bdaddr_bytes):
|
||||||
return ":".join(map(lambda x: "%02x" % x, reversed(bdaddr_bytes)))
|
return ":".join(map(lambda x: "%02x" % x, reversed(bdaddr_bytes)))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def _bdaddr_string_to_bytes(bdaddr_string):
|
def _bdaddr_string_to_bytes(bdaddr_string):
|
||||||
return bytearray.fromhex("".join(reversed(bdaddr_string.split(":"))))
|
return bytearray.fromhex("".join(reversed(bdaddr_string.split(":"))))
|
||||||
|
|
||||||
|
@ -438,7 +440,7 @@ class FlicClient:
|
||||||
return
|
return
|
||||||
opcode = data[0]
|
opcode = data[0]
|
||||||
|
|
||||||
if opcode >= len(FlicClient._EVENTS) or FlicClient._EVENTS[opcode] == None:
|
if opcode >= len(FlicClient._EVENTS) or FlicClient._EVENTS[opcode] is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
event_name = FlicClient._EVENTS[opcode][0]
|
event_name = FlicClient._EVENTS[opcode][0]
|
||||||
|
|
|
@ -19,6 +19,10 @@ class CameraPiBackend(Backend):
|
||||||
|
|
||||||
* **picamera** (``pip install picamera``)
|
* **picamera** (``pip install picamera``)
|
||||||
* **redis** (``pip install redis``) for inter-process communication with the camera process
|
* **redis** (``pip install redis``) for inter-process communication with the camera process
|
||||||
|
|
||||||
|
This backend is **DEPRECATED**. Use the plugin :class:`platypush.plugins.camera.pi.CameraPiPlugin` instead to run
|
||||||
|
Pi camera actions. If you want to start streaming the camera on application start then simply create an event hook
|
||||||
|
on :class:`platypush.message.event.application.ApplicationStartedEvent` that runs ``camera.pi.start_streaming``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class CameraAction(Enum):
|
class CameraAction(Enum):
|
||||||
|
@ -30,7 +34,7 @@ class CameraPiBackend(Backend):
|
||||||
return self.value == other
|
return self.value == other
|
||||||
|
|
||||||
# noinspection PyUnresolvedReferences,PyPackageRequirements
|
# noinspection PyUnresolvedReferences,PyPackageRequirements
|
||||||
def __init__(self, listen_port, x_resolution=640, y_resolution=480,
|
def __init__(self, listen_port, bind_address='0.0.0.0', x_resolution=640, y_resolution=480,
|
||||||
redis_queue='platypush/camera/pi',
|
redis_queue='platypush/camera/pi',
|
||||||
start_recording_on_startup=True,
|
start_recording_on_startup=True,
|
||||||
framerate=24, hflip=False, vflip=False,
|
framerate=24, hflip=False, vflip=False,
|
||||||
|
@ -45,13 +49,17 @@ class CameraPiBackend(Backend):
|
||||||
|
|
||||||
:param listen_port: Port where the camera process will provide the video output while recording
|
:param listen_port: Port where the camera process will provide the video output while recording
|
||||||
:type listen_port: int
|
:type listen_port: int
|
||||||
|
|
||||||
|
:param bind_address: Bind address (default: 0.0.0.0).
|
||||||
|
:type bind_address: str
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
self.bind_address = bind_address
|
||||||
self.listen_port = listen_port
|
self.listen_port = listen_port
|
||||||
self.server_socket = socket.socket()
|
self.server_socket = socket.socket()
|
||||||
self.server_socket.bind(('0.0.0.0', self.listen_port))
|
self.server_socket.bind((self.bind_address, self.listen_port))
|
||||||
self.server_socket.listen(0)
|
self.server_socket.listen(0)
|
||||||
|
|
||||||
import picamera
|
import picamera
|
||||||
|
@ -118,7 +126,7 @@ class CameraPiBackend(Backend):
|
||||||
while True:
|
while True:
|
||||||
self.camera.wait_recording(2)
|
self.camera.wait_recording(2)
|
||||||
else:
|
else:
|
||||||
while True:
|
while not self.should_stop():
|
||||||
connection = self.server_socket.accept()[0].makefile('wb')
|
connection = self.server_socket.accept()[0].makefile('wb')
|
||||||
self.logger.info('Accepted client connection on port {}'.format(self.listen_port))
|
self.logger.info('Accepted client connection on port {}'.format(self.listen_port))
|
||||||
|
|
||||||
|
@ -130,13 +138,13 @@ class CameraPiBackend(Backend):
|
||||||
self.logger.info('Client closed connection')
|
self.logger.info('Client closed connection')
|
||||||
try:
|
try:
|
||||||
self.stop_recording()
|
self.stop_recording()
|
||||||
except:
|
except Exception as e:
|
||||||
pass
|
self.logger.warning('Could not stop recording: {}'.format(str(e)))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
connection.close()
|
connection.close()
|
||||||
except:
|
except Exception as e:
|
||||||
pass
|
self.logger.warning('Could not close connection: {}'.format(str(e)))
|
||||||
|
|
||||||
self.send_camera_action(self.CameraAction.START_RECORDING)
|
self.send_camera_action(self.CameraAction.START_RECORDING)
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ class ClipboardBackend(Backend):
|
||||||
self._last_text: Optional[str] = None
|
self._last_text: Optional[str] = None
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
self.logger.info('Started clipboard monitor backend')
|
||||||
while not self.should_stop():
|
while not self.should_stop():
|
||||||
text = pyperclip.paste()
|
text = pyperclip.paste()
|
||||||
if text and text != self._last_text:
|
if text and text != self._last_text:
|
||||||
|
@ -34,5 +35,6 @@ class ClipboardBackend(Backend):
|
||||||
self._last_text = text
|
self._last_text = text
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
self.logger.info('Stopped clipboard monitor backend')
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
86
platypush/backend/dbus.py
Normal file
86
platypush/backend/dbus.py
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
# noinspection PyPackageRequirements,PyUnresolvedReferences
|
||||||
|
from gi.repository import GLib
|
||||||
|
|
||||||
|
import dbus
|
||||||
|
import dbus.service
|
||||||
|
import dbus.mainloop.glib
|
||||||
|
|
||||||
|
from platypush.backend import Backend
|
||||||
|
from platypush.context import get_bus
|
||||||
|
from platypush.message import Message
|
||||||
|
from platypush.message.event import Event
|
||||||
|
from platypush.message.request import Request
|
||||||
|
from platypush.utils import run
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyPep8Naming
|
||||||
|
class DBusService(dbus.service.Object):
|
||||||
|
@classmethod
|
||||||
|
def _parse_msg(cls, msg: Union[dict, list]):
|
||||||
|
import json
|
||||||
|
return Message.build(json.loads(json.dumps(msg)))
|
||||||
|
|
||||||
|
@dbus.service.method('org.platypush.MessageBusInterface', in_signature='a{sv}', out_signature='v')
|
||||||
|
def Post(self, msg: dict):
|
||||||
|
"""
|
||||||
|
This method accepts a message as a dictionary (either representing a valid request or an event) and either
|
||||||
|
executes it (request) or forwards it to the application bus (event).
|
||||||
|
|
||||||
|
:param msg: Request or event, as a dictionary.
|
||||||
|
:return: The return value of the request, or 0 if the message is an event.
|
||||||
|
"""
|
||||||
|
msg = self._parse_msg(msg)
|
||||||
|
if isinstance(msg, Request):
|
||||||
|
ret = run(msg.action, **msg.args)
|
||||||
|
if ret is None:
|
||||||
|
ret = '' # DBus doesn't like None return types
|
||||||
|
|
||||||
|
return ret
|
||||||
|
elif isinstance(msg, Event):
|
||||||
|
get_bus().post(msg)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
class DbusBackend(Backend):
|
||||||
|
"""
|
||||||
|
This backend acts as a proxy that receives messages (requests or events) on the DBus and forwards them to the
|
||||||
|
application bus.
|
||||||
|
|
||||||
|
The name of the messaging interface exposed by Platypush is ``org.platypush.MessageBusInterface`` and it exposes
|
||||||
|
``Post`` method, which accepts a dictionary representing a valid Platypush message (either a request or an event)
|
||||||
|
and either executes it or forwards it to the application bus.
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
|
||||||
|
* **dbus-python** (``pip install dbus-python``)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, bus_name='org.platypush.Bus', service_path='/MessageService', *args, **kwargs):
|
||||||
|
"""
|
||||||
|
:param bus_name: Name of the bus where the application will listen for incoming messages (default:
|
||||||
|
``org.platypush.Bus``).
|
||||||
|
:param service_path: Path to the service exposed by the app (default: ``/MessageService``).
|
||||||
|
"""
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.bus_name = bus_name
|
||||||
|
self.service_path = service_path
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
super().run()
|
||||||
|
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
|
||||||
|
bus = dbus.SessionBus()
|
||||||
|
name = dbus.service.BusName(self.bus_name, bus)
|
||||||
|
srv = DBusService(bus, self.service_path)
|
||||||
|
|
||||||
|
loop = GLib.MainLoop()
|
||||||
|
# noinspection PyProtectedMember
|
||||||
|
self.logger.info('Starting DBus main loop - bus name: {}, service: {}'.format(name._name, srv._object_path))
|
||||||
|
loop.run()
|
||||||
|
|
||||||
|
|
||||||
|
# vim:sw=4:ts=4:et:
|
128
platypush/backend/file/monitor/__init__.py
Normal file
128
platypush/backend/file/monitor/__init__.py
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
from typing import Iterable, Dict, Union, Any
|
||||||
|
|
||||||
|
from watchdog.observers import Observer
|
||||||
|
|
||||||
|
from platypush.backend import Backend
|
||||||
|
from .entities.handlers import EventHandler
|
||||||
|
from .entities.resources import MonitoredResource, MonitoredPattern, MonitoredRegex
|
||||||
|
|
||||||
|
|
||||||
|
class FileMonitorBackend(Backend):
|
||||||
|
"""
|
||||||
|
This backend monitors changes to local files and directories using the Watchdog API.
|
||||||
|
|
||||||
|
Triggers:
|
||||||
|
|
||||||
|
* :class:`platypush.message.event.file.FileSystemCreateEvent` if a resource is created.
|
||||||
|
* :class:`platypush.message.event.file.FileSystemDeleteEvent` if a resource is removed.
|
||||||
|
* :class:`platypush.message.event.file.FileSystemModifyEvent` if a resource is modified.
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
|
||||||
|
* **watchdog** (``pip install watchdog``)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
class EventHandlerFactory:
|
||||||
|
"""
|
||||||
|
Create a file system event handler from a string, dictionary or ``MonitoredResource`` resource.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_resource(resource: Union[str, Dict[str, Any], MonitoredResource]) -> EventHandler:
|
||||||
|
if isinstance(resource, str):
|
||||||
|
resource = MonitoredResource(resource)
|
||||||
|
elif isinstance(resource, dict):
|
||||||
|
if 'patterns' in resource or 'ignore_patterns' in resource:
|
||||||
|
resource = MonitoredPattern(**resource)
|
||||||
|
elif 'regexes' in resource or 'ignore_regexes' in resource:
|
||||||
|
resource = MonitoredRegex(**resource)
|
||||||
|
else:
|
||||||
|
resource = MonitoredResource(**resource)
|
||||||
|
|
||||||
|
return EventHandler.from_resource(resource)
|
||||||
|
|
||||||
|
def __init__(self, paths: Iterable[Union[str, Dict[str, Any], MonitoredResource]], **kwargs):
|
||||||
|
"""
|
||||||
|
:param paths: List of paths to monitor. Paths can either be expressed in any of the following ways:
|
||||||
|
|
||||||
|
- Simple strings. In this case, paths will be interpreted as absolute references to a file or a directory
|
||||||
|
to monitor. Example:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
backend.file.monitor:
|
||||||
|
paths:
|
||||||
|
# Monitor changes on the /tmp folder
|
||||||
|
- /tmp
|
||||||
|
# Monitor changes on /etc/passwd
|
||||||
|
- /etc/passwd
|
||||||
|
|
||||||
|
- Path with monitoring properties expressed as a key-value object. Example showing the supported attributes:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
backend.file.monitor:
|
||||||
|
paths:
|
||||||
|
# Monitor changes on the /tmp folder and its subfolders
|
||||||
|
- path: /tmp
|
||||||
|
recursive: True
|
||||||
|
|
||||||
|
- Path with pattern-based search criteria for the files to monitor and exclude. Example:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
backend.file.monitor:
|
||||||
|
paths:
|
||||||
|
# Recursively monitor changes on the ~/my-project folder that include all
|
||||||
|
# *.py files, excluding those whose name starts with tmp_ and
|
||||||
|
# all the files contained in the __pycache__ folders
|
||||||
|
- path: ~/my-project
|
||||||
|
recursive: True
|
||||||
|
patterns:
|
||||||
|
- "*.py"
|
||||||
|
ignore_patterns:
|
||||||
|
- "tmp_*"
|
||||||
|
ignore_directories:
|
||||||
|
- "__pycache__"
|
||||||
|
|
||||||
|
- Path with regex-based search criteria for the files to monitor and exclude. Example:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
backend.file.monitor:
|
||||||
|
paths:
|
||||||
|
# Recursively monitor changes on the ~/my-images folder that include all
|
||||||
|
# the files matching a JPEG extension in case-insensitive mode,
|
||||||
|
# excluding those whose name starts with tmp_ and
|
||||||
|
# all the files contained in the __MACOSX folders
|
||||||
|
- path: ~/my-images
|
||||||
|
recursive: True
|
||||||
|
case_sensitive: False
|
||||||
|
regexes:
|
||||||
|
- '.*\\.jpe?g$'
|
||||||
|
ignore_patterns:
|
||||||
|
- '^tmp_.*'
|
||||||
|
ignore_directories:
|
||||||
|
- '__MACOSX'
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self._observer = Observer()
|
||||||
|
|
||||||
|
for path in paths:
|
||||||
|
handler = self.EventHandlerFactory.from_resource(path)
|
||||||
|
self._observer.schedule(handler, handler.resource.path, recursive=handler.resource.recursive)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
super().run()
|
||||||
|
self.logger.info('Initializing file monitor backend')
|
||||||
|
self._observer.start()
|
||||||
|
self.wait_stop()
|
||||||
|
|
||||||
|
def on_stop(self):
|
||||||
|
self.logger.info('Stopping file monitor backend')
|
||||||
|
self._observer.stop()
|
||||||
|
self._observer.join()
|
||||||
|
self.logger.info('Stopped file monitor backend')
|
61
platypush/backend/file/monitor/entities/handlers.py
Normal file
61
platypush/backend/file/monitor/entities/handlers.py
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
from watchdog.events import FileSystemEventHandler, PatternMatchingEventHandler, RegexMatchingEventHandler
|
||||||
|
|
||||||
|
from platypush.backend.file.monitor.entities.resources import MonitoredResource, MonitoredPattern, MonitoredRegex
|
||||||
|
from platypush.context import get_bus
|
||||||
|
from platypush.message.event.file import FileSystemModifyEvent, FileSystemCreateEvent, FileSystemDeleteEvent
|
||||||
|
|
||||||
|
|
||||||
|
class EventHandler(FileSystemEventHandler):
|
||||||
|
"""
|
||||||
|
Base class for Watchdog event handlers.
|
||||||
|
"""
|
||||||
|
def __init__(self, resource: MonitoredResource, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
resource.path = os.path.expanduser(resource.path)
|
||||||
|
self.resource = resource
|
||||||
|
|
||||||
|
def on_created(self, event):
|
||||||
|
get_bus().post(FileSystemCreateEvent(path=event.src_path, is_directory=event.is_directory))
|
||||||
|
|
||||||
|
def on_deleted(self, event):
|
||||||
|
get_bus().post(FileSystemDeleteEvent(path=event.src_path, is_directory=event.is_directory))
|
||||||
|
|
||||||
|
def on_modified(self, event):
|
||||||
|
get_bus().post(FileSystemModifyEvent(path=event.src_path, is_directory=event.is_directory))
|
||||||
|
|
||||||
|
def on_moved(self, event):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_resource(cls, resource: MonitoredResource):
|
||||||
|
if isinstance(resource, MonitoredPattern):
|
||||||
|
return PatternEventHandler(resource)
|
||||||
|
if isinstance(resource, MonitoredRegex):
|
||||||
|
return RegexEventHandler(resource)
|
||||||
|
return cls(resource)
|
||||||
|
|
||||||
|
|
||||||
|
class PatternEventHandler(EventHandler, PatternMatchingEventHandler):
|
||||||
|
"""
|
||||||
|
Event handler for file patterns.
|
||||||
|
"""
|
||||||
|
def __init__(self, resource: MonitoredPattern):
|
||||||
|
super().__init__(resource=resource,
|
||||||
|
patterns=resource.patterns,
|
||||||
|
ignore_patterns=resource.ignore_patterns,
|
||||||
|
ignore_directories=resource.ignore_directories,
|
||||||
|
case_sensitive=resource.case_sensitive)
|
||||||
|
|
||||||
|
|
||||||
|
class RegexEventHandler(EventHandler, RegexMatchingEventHandler):
|
||||||
|
"""
|
||||||
|
Event handler for regex-based file patterns.
|
||||||
|
"""
|
||||||
|
def __init__(self, resource: MonitoredRegex):
|
||||||
|
super().__init__(resource=resource,
|
||||||
|
regexes=resource.regexes,
|
||||||
|
ignore_regexes=resource.ignore_regexes,
|
||||||
|
ignore_directories=resource.ignore_directories,
|
||||||
|
case_sensitive=resource.case_sensitive)
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue