Compare commits
No commits in common. "master" and "v0.12.9" have entirely different histories.
2108 changed files with 22011 additions and 150462 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -3,7 +3,7 @@
|
|||
*.pyc
|
||||
__pycache__
|
||||
build/
|
||||
/dist/
|
||||
dist/
|
||||
*.egg-info/
|
||||
package.sh
|
||||
.pypirc
|
||||
|
@ -17,5 +17,3 @@ platypush/backend/http/static/js/lib/vue.js
|
|||
platypush/notebooks
|
||||
platypush/requests
|
||||
/http-client.env.json
|
||||
/platypush/backend/http/static/css/dist
|
||||
/tests/etc/dashboards
|
||||
|
|
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -1,3 +1,6 @@
|
|||
[submodule "docs/wiki"]
|
||||
path = docs/wiki
|
||||
url = https://github.com/BlackLight/platypush.wiki.git
|
||||
[submodule "platypush/plugins/gpio/sensor/ir/mlx90640/lib"]
|
||||
path = platypush/plugins/camera/ir/mlx90640/lib
|
||||
url = https://github.com/pimoroni/mlx90640-library
|
||||
|
|
7
.readthedocs.yml
Normal file
7
.readthedocs.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
build:
|
||||
image: latest
|
||||
|
||||
python:
|
||||
version: 3.6
|
||||
setup_py_install: true
|
||||
|
9
.travis.requirements
Normal file
9
.travis.requirements
Normal file
|
@ -0,0 +1,9 @@
|
|||
pyyaml
|
||||
requests
|
||||
flask
|
||||
redis
|
||||
python-dateutil
|
||||
websockets
|
||||
bcrypt
|
||||
sqlalchemy
|
||||
croniter
|
22
.travis.yml
Normal file
22
.travis.yml
Normal file
|
@ -0,0 +1,22 @@
|
|||
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
177
CHANGELOG.md
|
@ -1,177 +0,0 @@
|
|||
# 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.
|
|
@ -1,37 +0,0 @@
|
|||
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
|
||||
|
||||
Copyright (c) 2017, 2020 Fabio Manganiello
|
||||
Copyright (c) 2017, 2018 Fabio Manganiello
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
recursive-include platypush/backend/http/webapp/dist *
|
||||
recursive-include platypush/backend/http/static *
|
||||
recursive-include platypush/backend/http/templates *
|
||||
include platypush/plugins/http/webpage/mercury-parser.js
|
||||
|
|
553
README.md
553
README.md
|
@ -1,509 +1,68 @@
|
|||
Platypush
|
||||
=========
|
||||
|
||||
[![Build Status](https://ci.platypush.tech/status.svg)](https://ci.platypush.tech/latest.log)
|
||||
[![Documentation Status](https://ci.platypush.tech/docs/status.svg)](https://ci.platypush.tech/docs/latest.log)
|
||||
[![Build Status](https://travis-ci.org/BlackLight/platypush.svg?branch=master)](https://travis-ci.org/BlackLight/platypush)
|
||||
[![Documentation Status](https://readthedocs.org/projects/platypush/badge/?version=latest)](https://platypush.readthedocs.io/en/latest/?badge=latest)
|
||||
[![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)
|
||||
|
||||
- Recommended read: [**Getting started with Platypush**](https://blog.platypush.tech/article/Ultimate-self-hosted-automation-with-Platypush).
|
||||
Advised read: [**Getting started with Platypush**](https://medium.com/@automationguru/automate-your-house-your-life-and-everything-else-around-with-platypush-dba1cd13e3f6) (Medium article).
|
||||
[Reddit channel](https://www.reddit.com/r/platypush)
|
||||
|
||||
- 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.
|
||||
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.
|
||||
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.
|
||||
|
||||
- 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.
|
||||
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.
|
||||
|
||||
You can use Platypush to do things like:
|
||||
|
||||
- [Control your smart home lights](https://blog.platypush.tech/article/Ultimate-self-hosted-automation-with-Platypush)
|
||||
- [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)
|
||||
- [Create custom and privacy-secure voice assistants that run custom hooks on your phrases](https://blog.platypush.tech/article/Build-custom-voice-assistants)
|
||||
- Build integrations between [sensors](https://docs.platypush.tech/en/latest/platypush/backend/sensor.html),
|
||||
[cameras](https://docs.platypush.tech/en/latest/platypush/plugins/camera.pi.html),
|
||||
[microphones](https://docs.platypush.tech/en/latest/platypush/plugins/sound.html) and
|
||||
[machine learning models](https://docs.platypush.tech/en/latest/platypush/plugins/tensorflow.html) to create smart
|
||||
pieces of automation for e.g.
|
||||
[people detection](https://blog.platypush.tech/article/Detect-people-with-a-RaspberryPi-a-thermal-camera-Platypush-and-a-pinch-of-machine-learning)
|
||||
or [sound detection](https://blog.platypush.tech/article/Create-your-smart-baby-monitor-with-Platypush-and-Tensorflow)
|
||||
- [Get events from your Google or Facebook calendars](https://docs.platypush.tech/en/latest/platypush/plugins/calendar.html)
|
||||
- [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)
|
||||
- [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)
|
||||
- Control your smart home lights
|
||||
- Control your favourite music player
|
||||
- Interact with your voice assistant
|
||||
- Get events from your Google or Facebook calendars
|
||||
- Read data from your sensors and trigger custom events whenever they go above or below some custom thresholds
|
||||
- Control the motors of your robot
|
||||
- Send automated emails
|
||||
- Synchronize the clipboards on your devices
|
||||
- Control your smart switches
|
||||
- Implement custom text-to-speech commands
|
||||
- Build any kind of interaction with your Android device using Tasker
|
||||
- Play local videos, YouTube videos and torrent links
|
||||
- Get weather forecast for your location
|
||||
- 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://docs.platypush.tech/en/latest/plugins.html))
|
||||
- ...and much more (basically, anything that comes with a [Platypush plugin](https://platypush.readthedocs.io/en/latest/plugins.html))
|
||||
|
||||
Imagine the ability of executing all the actions above through messages delivered through:
|
||||
|
||||
- A web interface
|
||||
- A JSON-RPC API
|
||||
- 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:
|
||||
|
||||
- When your voice assistant recognizes some text
|
||||
- When you start playing a new song
|
||||
- When a new event is added to your calendar
|
||||
- When a new article is published on your favourite feed
|
||||
- When the weather conditions change
|
||||
- When your press a [Flic button](https://flic.io) with a certain pattern
|
||||
- When you receive a new push on your Pushbullet account
|
||||
- When your GPS signal enters a certain area
|
||||
- Whenever a new MIDI event is received (yes, you heard well :) )
|
||||
- Whenever a sensor sends new data
|
||||
- At a specific date or time
|
||||
- ...and so on (basically, anything can send events that can be used to build hooks)
|
||||
|
||||
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.
|
||||
|
||||
To get started:
|
||||
|
||||
- [Wiki](https://github.com/BlackLight/platypush/wiki) for installation notes, quick start, examples and architecture reference
|
||||
- [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
|
||||
|
||||
## Architecture
|
||||
|
||||
The architecture of Platypush consists of a few simple pieces, orchestrated by a configuration file stored by default
|
||||
under [`~/.config/platypush/config.yaml`](https://git.platypush.tech/platypush/platypush/-/blob/master/examples/conf/config.yaml):
|
||||
|
||||
### [Plugins](https://docs.platypush.tech/en/latest/plugins.html)
|
||||
|
||||
They are integrations that do things - like
|
||||
[modify files](https://docs.platypush.tech/en/latest/platypush/plugins/file.html),
|
||||
[train and evaluate machine learning models](https://docs.platypush.tech/en/latest/platypush/plugins/tensorflow.html),
|
||||
[control cameras](https://docs.platypush.tech/en/latest/platypush/plugins/camera.pi.html),
|
||||
[read sensors](https://docs.platypush.tech/en/latest/platypush/plugins/gpio.sensor.dht.html),
|
||||
[parse a web page](https://docs.platypush.tech/en/latest/platypush/plugins/http.webpage.html),
|
||||
[control lights](https://docs.platypush.tech/en/latest/platypush/plugins/light.hue.html),
|
||||
[send emails](https://docs.platypush.tech/en/latest/platypush/plugins/mail.smtp.html),
|
||||
[control Chromecasts](https://docs.platypush.tech/en/latest/platypush/plugins/media.chromecast.html),
|
||||
[run voice queries](https://docs.platypush.tech/en/latest/platypush/plugins/assistant.google.html),
|
||||
[handle torrent transfers](https://docs.platypush.tech/en/latest/platypush/plugins/torrent.html) or
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
```yaml
|
||||
light.hue:
|
||||
# Groups that will be controlled by default
|
||||
groups:
|
||||
- Living Room
|
||||
- Hall
|
||||
```
|
||||
|
||||
### Actions
|
||||
|
||||
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}
|
||||
done
|
||||
|
||||
pip install --upgrade git+https://git.platypush.tech/platypush/platypush.git
|
||||
pip install --upgrade git+https://github.com/BlackLight/platypush.git
|
||||
echo "Platypush virtual environment prepared under $envdir"
|
||||
}
|
||||
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
# 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.
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
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,10 +21,7 @@ Backends
|
|||
platypush/backend/chat.telegram.rst
|
||||
platypush/backend/clipboard.rst
|
||||
platypush/backend/covid19.rst
|
||||
platypush/backend/dbus.rst
|
||||
platypush/backend/file.monitor.rst
|
||||
platypush/backend/foursquare.rst
|
||||
platypush/backend/github.rst
|
||||
platypush/backend/google.fit.rst
|
||||
platypush/backend/google.pubsub.rst
|
||||
platypush/backend/gps.rst
|
||||
|
@ -32,20 +29,15 @@ Backends
|
|||
platypush/backend/http.poll.rst
|
||||
platypush/backend/inotify.rst
|
||||
platypush/backend/joystick.rst
|
||||
platypush/backend/joystick.jstest.rst
|
||||
platypush/backend/joystick.linux.rst
|
||||
platypush/backend/kafka.rst
|
||||
platypush/backend/light.hue.rst
|
||||
platypush/backend/linode.rst
|
||||
platypush/backend/log.http.rst
|
||||
platypush/backend/mail.rst
|
||||
platypush/backend/local.rst
|
||||
platypush/backend/midi.rst
|
||||
platypush/backend/mqtt.rst
|
||||
platypush/backend/music.mopidy.rst
|
||||
platypush/backend/music.mpd.rst
|
||||
platypush/backend/music.snapcast.rst
|
||||
platypush/backend/music.spotify.connect.rst
|
||||
platypush/backend/nextcloud.rst
|
||||
platypush/backend/nfc.rst
|
||||
platypush/backend/nodered.rst
|
||||
platypush/backend/ping.rst
|
||||
|
@ -57,7 +49,6 @@ Backends
|
|||
platypush/backend/sensor.arduino.rst
|
||||
platypush/backend/sensor.battery.rst
|
||||
platypush/backend/sensor.bme280.rst
|
||||
platypush/backend/sensor.dht.rst
|
||||
platypush/backend/sensor.distance.rst
|
||||
platypush/backend/sensor.distance.vl53l1x.rst
|
||||
platypush/backend/sensor.envirophat.rst
|
||||
|
@ -75,12 +66,9 @@ Backends
|
|||
platypush/backend/todoist.rst
|
||||
platypush/backend/travisci.rst
|
||||
platypush/backend/trello.rst
|
||||
platypush/backend/weather.rst
|
||||
platypush/backend/weather.buienradar.rst
|
||||
platypush/backend/weather.darksky.rst
|
||||
platypush/backend/weather.openweathermap.rst
|
||||
platypush/backend/websocket.rst
|
||||
platypush/backend/wiimote.rst
|
||||
platypush/backend/zigbee.mqtt.rst
|
||||
platypush/backend/zwave.rst
|
||||
platypush/backend/zwave.mqtt.rst
|
||||
|
|
|
@ -18,13 +18,12 @@ import sys
|
|||
# import os
|
||||
# import sys
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
sys.path.insert(0, os.path.abspath("./_ext"))
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'Platypush'
|
||||
copyright = '2017-2021, Fabio Manganiello'
|
||||
project = 'platypush'
|
||||
copyright = '2017-2019, Fabio Manganiello'
|
||||
author = 'Fabio Manganiello'
|
||||
|
||||
# The short X.Y version
|
||||
|
@ -51,7 +50,6 @@ extensions = [
|
|||
'sphinx.ext.viewcode',
|
||||
'sphinx.ext.githubpages',
|
||||
'sphinx_rtd_theme',
|
||||
'sphinx_marshmallow',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
|
@ -88,8 +86,7 @@ pygments_style = 'sphinx'
|
|||
# a list of builtin themes.
|
||||
#
|
||||
# html_theme = 'haiku'
|
||||
# html_theme = 'sphinx_rtd_theme'
|
||||
html_theme = 'sphinx_material'
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
|
||||
html_domain_indices = True
|
||||
|
||||
|
@ -97,52 +94,7 @@ html_domain_indices = True
|
|||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#
|
||||
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,
|
||||
},
|
||||
],
|
||||
}
|
||||
# html_theme_options = {}
|
||||
|
||||
# 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,
|
||||
|
@ -157,9 +109,8 @@ html_theme_options = {
|
|||
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
|
||||
# 'searchbox.html']``.
|
||||
#
|
||||
html_sidebars = {
|
||||
'**': ['logo-text.html', 'globaltoc.html', 'localtoc.html', 'searchbox.html']
|
||||
}
|
||||
# html_sidebars = {}
|
||||
|
||||
|
||||
# -- Options for HTMLHelp output ---------------------------------------------
|
||||
|
||||
|
@ -301,20 +252,6 @@ autodoc_mock_imports = ['googlesamples.assistant.grpc.audio_helpers',
|
|||
'pandas',
|
||||
'samsungtvws',
|
||||
'paramiko',
|
||||
'luma',
|
||||
'zeroconf',
|
||||
'dbus',
|
||||
'gi',
|
||||
'gi.repository',
|
||||
'twilio',
|
||||
'pytz',
|
||||
'Adafruit_Python_DHT',
|
||||
'RPi.GPIO',
|
||||
'RPLCD',
|
||||
'imapclient',
|
||||
'pysmartthings',
|
||||
'aiohttp',
|
||||
'watchdog',
|
||||
]
|
||||
|
||||
sys.path.insert(0, os.path.abspath('../..'))
|
||||
|
@ -327,5 +264,3 @@ def skip(app, what, name, obj, skip, options):
|
|||
def setup(app):
|
||||
app.connect("autodoc-skip-member", skip)
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
|
|
@ -6,6 +6,7 @@ Events
|
|||
:maxdepth: 2
|
||||
:caption: Events:
|
||||
|
||||
platypush/events/.rst
|
||||
platypush/events/adafruit.rst
|
||||
platypush/events/alarm.rst
|
||||
platypush/events/application.rst
|
||||
|
@ -16,33 +17,28 @@ Events
|
|||
platypush/events/chat.telegram.rst
|
||||
platypush/events/clipboard.rst
|
||||
platypush/events/covid19.rst
|
||||
platypush/events/custom.rst
|
||||
platypush/events/distance.rst
|
||||
platypush/events/file.rst
|
||||
platypush/events/foursquare.rst
|
||||
platypush/events/geo.rst
|
||||
platypush/events/github.rst
|
||||
platypush/events/google.rst
|
||||
platypush/events/google.fit.rst
|
||||
platypush/events/google.pubsub.rst
|
||||
platypush/events/gps.rst
|
||||
platypush/events/http.rst
|
||||
platypush/events/http.hook.rst
|
||||
platypush/events/http.ota.booking.rst
|
||||
platypush/events/http.rss.rst
|
||||
platypush/events/inotify.rst
|
||||
platypush/events/joystick.rst
|
||||
platypush/events/kafka.rst
|
||||
platypush/events/light.rst
|
||||
platypush/events/linode.rst
|
||||
platypush/events/log.http.rst
|
||||
platypush/events/mail.rst
|
||||
platypush/events/media.rst
|
||||
platypush/events/midi.rst
|
||||
platypush/events/mqtt.rst
|
||||
platypush/events/music.rst
|
||||
platypush/events/music.snapcast.rst
|
||||
platypush/events/nextcloud.rst
|
||||
platypush/events/nfc.rst
|
||||
platypush/events/path.rst
|
||||
platypush/events/ping.rst
|
||||
platypush/events/pushbullet.rst
|
||||
platypush/events/qrcode.rst
|
||||
|
@ -65,6 +61,5 @@ Events
|
|||
platypush/events/web.widget.rst
|
||||
platypush/events/wiimote.rst
|
||||
platypush/events/zeroborg.rst
|
||||
platypush/events/zeroconf.rst
|
||||
platypush/events/zigbee.mqtt.rst
|
||||
platypush/events/zwave.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:
|
||||
|
||||
* The `Gitlab page`_ of the project
|
||||
* The `GitHub page`_ of the project
|
||||
* The `online wiki`_ for quickstart and examples
|
||||
* The `Blog articles`_ for inspiration on use-cases possible projects
|
||||
* The `Medium stories`_ for inspiration about possible projects
|
||||
|
||||
.. _Gitlab page: https://git.platypush.tech/platypush/platypush
|
||||
.. _online wiki: https://git.platypush.tech/platypush/platypush/-/wikis/home
|
||||
.. _Blog articles: https://blog.platypush.tech
|
||||
.. _GitHub page: https://github.com/BlackLight/platypush
|
||||
.. _online wiki: https://github.com/BlackLight/platypush/wiki
|
||||
.. _Medium stories: https://medium.com/tag/platypush/archive
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.backend.dbus``
|
||||
==========================
|
||||
|
||||
.. automodule:: platypush.backend.dbus
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.backend.file.monitor``
|
||||
==================================
|
||||
|
||||
.. automodule:: platypush.backend.file.monitor
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.backend.github``
|
||||
============================
|
||||
|
||||
.. automodule:: platypush.backend.github
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.backend.joystick.jstest``
|
||||
=====================================
|
||||
|
||||
.. automodule:: platypush.backend.joystick.jstest
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.backend.joystick.linux``
|
||||
====================================
|
||||
|
||||
.. automodule:: platypush.backend.joystick.linux
|
||||
:members:
|
5
docs/source/platypush/backend/local.rst
Normal file
5
docs/source/platypush/backend/local.rst
Normal file
|
@ -0,0 +1,5 @@
|
|||
``platypush.backend.local``
|
||||
===========================
|
||||
|
||||
.. automodule:: platypush.backend.local
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.backend.log.http``
|
||||
==============================
|
||||
|
||||
.. automodule:: platypush.backend.log.http
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.backend.mail``
|
||||
==========================
|
||||
|
||||
.. automodule:: platypush.backend.mail
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.backend.music.spotify.connect``
|
||||
===========================================
|
||||
|
||||
.. automodule:: platypush.backend.music.spotify.connect
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.backend.nextcloud``
|
||||
===============================
|
||||
|
||||
.. automodule:: platypush.backend.nextcloud
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.backend.sensor.dht``
|
||||
================================
|
||||
|
||||
.. automodule:: platypush.backend.sensor.dht
|
||||
:members:
|
5
docs/source/platypush/backend/stt.picovoice.rst
Normal file
5
docs/source/platypush/backend/stt.picovoice.rst
Normal file
|
@ -0,0 +1,5 @@
|
|||
``platypush.backend.stt.picovoice``
|
||||
===================================
|
||||
|
||||
.. automodule:: platypush.backend.stt.picovoice
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.backend.weather.openweathermap``
|
||||
============================================
|
||||
|
||||
.. automodule:: platypush.backend.weather.openweathermap
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.backend.weather``
|
||||
=============================
|
||||
|
||||
.. automodule:: platypush.backend.weather
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.backend.zwave.mqtt``
|
||||
================================
|
||||
|
||||
.. automodule:: platypush.backend.zwave.mqtt
|
||||
:members:
|
5
docs/source/platypush/events/.rst
Normal file
5
docs/source/platypush/events/.rst
Normal file
|
@ -0,0 +1,5 @@
|
|||
``platypush.message.event``
|
||||
===========================
|
||||
|
||||
.. automodule:: platypush.message.event
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.message.event.custom``
|
||||
==================================
|
||||
|
||||
.. automodule:: platypush.message.event.custom
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.message.event.file``
|
||||
================================
|
||||
|
||||
.. automodule:: platypush.message.event.file
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.message.event.github``
|
||||
==================================
|
||||
|
||||
.. automodule:: platypush.message.event.github
|
||||
:members:
|
5
docs/source/platypush/events/http.ota.booking.rst
Normal file
5
docs/source/platypush/events/http.ota.booking.rst
Normal file
|
@ -0,0 +1,5 @@
|
|||
``platypush.message.event.http.ota.booking``
|
||||
============================================
|
||||
|
||||
.. automodule:: platypush.message.event.http.ota.booking
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.message.event.inotify``
|
||||
===================================
|
||||
|
||||
.. automodule:: platypush.message.event.inotify
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.message.event.log.http``
|
||||
====================================
|
||||
|
||||
.. automodule:: platypush.message.event.log.http
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.message.event.mail``
|
||||
================================
|
||||
|
||||
.. automodule:: platypush.message.event.mail
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.message.event.nextcloud``
|
||||
=====================================
|
||||
|
||||
.. automodule:: platypush.message.event.nextcloud
|
||||
:members:
|
6
docs/source/platypush/events/path.rst
Normal file
6
docs/source/platypush/events/path.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
``platypush.message.event.path``
|
||||
================================
|
||||
|
||||
.. automodule:: platypush.message.event.path
|
||||
:members:
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.message.event.zeroconf``
|
||||
====================================
|
||||
|
||||
.. automodule:: platypush.message.event.zeroconf
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.camera.cv``
|
||||
===============================
|
||||
|
||||
.. automodule:: platypush.plugins.camera.cv
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.camera.ffmpeg``
|
||||
===================================
|
||||
|
||||
.. automodule:: platypush.plugins.camera.ffmpeg
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.camera.gstreamer``
|
||||
======================================
|
||||
|
||||
.. automodule:: platypush.plugins.camera.gstreamer
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.config``
|
||||
============================
|
||||
|
||||
.. automodule:: platypush.plugins.config
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.dbus``
|
||||
==========================
|
||||
|
||||
.. automodule:: platypush.plugins.dbus
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.ffmpeg``
|
||||
============================
|
||||
|
||||
.. automodule:: platypush.plugins.ffmpeg
|
||||
:members:
|
6
docs/source/platypush/plugins/google.credentials.rst
Normal file
6
docs/source/platypush/plugins/google.credentials.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
``platypush.plugins.google.credentials``
|
||||
========================================
|
||||
|
||||
.. automodule:: platypush.plugins.google.credentials
|
||||
:members:
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.google.translate``
|
||||
======================================
|
||||
|
||||
.. automodule:: platypush.plugins.google.translate
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.gpio.sensor.dht``
|
||||
=====================================
|
||||
|
||||
.. automodule:: platypush.plugins.gpio.sensor.dht
|
||||
:members:
|
|
@ -0,0 +1,5 @@
|
|||
``platypush.plugins.http.request.ota.booking``
|
||||
==============================================
|
||||
|
||||
.. automodule:: platypush.plugins.http.request.ota.booking
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.lcd.gpio``
|
||||
==============================
|
||||
|
||||
.. automodule:: platypush.plugins.lcd.gpio
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.lcd.i2c``
|
||||
=============================
|
||||
|
||||
.. automodule:: platypush.plugins.lcd.i2c
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.lcd``
|
||||
=========================
|
||||
|
||||
.. automodule:: platypush.plugins.lcd
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.luma.oled``
|
||||
===============================
|
||||
|
||||
.. automodule:: platypush.plugins.luma.oled
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.mail.imap``
|
||||
===============================
|
||||
|
||||
.. automodule:: platypush.plugins.mail.imap
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.mail``
|
||||
==========================
|
||||
|
||||
.. automodule:: platypush.plugins.mail
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.mail.smtp``
|
||||
===============================
|
||||
|
||||
.. automodule:: platypush.plugins.mail.smtp
|
||||
:members:
|
5
docs/source/platypush/plugins/media.ctrl.rst
Normal file
5
docs/source/platypush/plugins/media.ctrl.rst
Normal file
|
@ -0,0 +1,5 @@
|
|||
``platypush.plugins.media.ctrl``
|
||||
================================
|
||||
|
||||
.. automodule:: platypush.plugins.media.ctrl
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.media.gstreamer``
|
||||
=====================================
|
||||
|
||||
.. automodule:: platypush.plugins.media.gstreamer
|
||||
:members:
|
6
docs/source/platypush/plugins/media.search.local.rst
Normal file
6
docs/source/platypush/plugins/media.search.local.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
``platypush.plugins.media.search.local``
|
||||
========================================
|
||||
|
||||
.. automodule:: platypush.plugins.media.search.local
|
||||
:members:
|
||||
|
6
docs/source/platypush/plugins/media.search.torrent.rst
Normal file
6
docs/source/platypush/plugins/media.search.torrent.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
``platypush.plugins.media.search.torrent``
|
||||
==========================================
|
||||
|
||||
.. automodule:: platypush.plugins.media.search.torrent
|
||||
:members:
|
||||
|
6
docs/source/platypush/plugins/media.search.youtube.rst
Normal file
6
docs/source/platypush/plugins/media.search.youtube.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
``platypush.plugins.media.search.youtube``
|
||||
==========================================
|
||||
|
||||
.. automodule:: platypush.plugins.media.search.youtube
|
||||
:members:
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.nextcloud``
|
||||
===============================
|
||||
|
||||
.. automodule:: platypush.plugins.nextcloud
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.pwm.pca9685``
|
||||
=================================
|
||||
|
||||
.. automodule:: platypush.plugins.pwm.pca9685
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.rtorrent``
|
||||
==============================
|
||||
|
||||
.. automodule:: platypush.plugins.rtorrent
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.smartthings``
|
||||
=================================
|
||||
|
||||
.. automodule:: platypush.plugins.smartthings
|
||||
:members:
|
5
docs/source/platypush/plugins/stt.picovoice.rst
Normal file
5
docs/source/platypush/plugins/stt.picovoice.rst
Normal file
|
@ -0,0 +1,5 @@
|
|||
``platypush.plugins.stt.picovoice``
|
||||
===================================
|
||||
|
||||
.. automodule:: platypush.plugins.stt.picovoice
|
||||
:members:
|
6
docs/source/platypush/plugins/switch.switchbot.rst
Normal file
6
docs/source/platypush/plugins/switch.switchbot.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
``platypush.plugins.switch.switchbot``
|
||||
======================================
|
||||
|
||||
.. automodule:: platypush.plugins.switch.switchbot
|
||||
:members:
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.switchbot.bluetooth``
|
||||
=========================================
|
||||
|
||||
.. automodule:: platypush.plugins.switchbot.bluetooth
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.switchbot``
|
||||
===============================
|
||||
|
||||
.. automodule:: platypush.plugins.switchbot
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.twilio``
|
||||
============================
|
||||
|
||||
.. automodule:: platypush.plugins.twilio
|
||||
:members:
|
5
docs/source/platypush/plugins/video.torrentcast.rst
Normal file
5
docs/source/platypush/plugins/video.torrentcast.rst
Normal file
|
@ -0,0 +1,5 @@
|
|||
``platypush.plugins.video.torrentcast``
|
||||
=======================================
|
||||
|
||||
.. automodule:: platypush.plugins.video.torrentcast
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.weather.openweathermap``
|
||||
============================================
|
||||
|
||||
.. automodule:: platypush.plugins.weather.openweathermap
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.weather``
|
||||
=============================
|
||||
|
||||
.. automodule:: platypush.plugins.weather
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.zeroconf``
|
||||
==============================
|
||||
|
||||
.. automodule:: platypush.plugins.zeroconf
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.zwave._base``
|
||||
=================================
|
||||
|
||||
.. automodule:: platypush.plugins.zwave._base
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.zwave.mqtt``
|
||||
================================
|
||||
|
||||
.. automodule:: platypush.plugins.zwave.mqtt
|
||||
:members:
|
5
docs/source/platypush/responses/deepspeech.rst
Normal file
5
docs/source/platypush/responses/deepspeech.rst
Normal file
|
@ -0,0 +1,5 @@
|
|||
``platypush.message.response.deepspeech``
|
||||
=========================================
|
||||
|
||||
.. automodule:: platypush.message.response.deepspeech
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.message.response.translate``
|
||||
========================================
|
||||
|
||||
.. automodule:: platypush.message.response.translate
|
||||
:members:
|
|
@ -20,21 +20,15 @@ Plugins
|
|||
platypush/plugins/calendar.ical.rst
|
||||
platypush/plugins/camera.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.pi.rst
|
||||
platypush/plugins/chat.telegram.rst
|
||||
platypush/plugins/clipboard.rst
|
||||
platypush/plugins/config.rst
|
||||
platypush/plugins/covid19.rst
|
||||
platypush/plugins/csv.rst
|
||||
platypush/plugins/db.rst
|
||||
platypush/plugins/dbus.rst
|
||||
platypush/plugins/dropbox.rst
|
||||
platypush/plugins/esp.rst
|
||||
platypush/plugins/ffmpeg.rst
|
||||
platypush/plugins/file.rst
|
||||
platypush/plugins/foursquare.rst
|
||||
platypush/plugins/google.rst
|
||||
|
@ -44,13 +38,11 @@ Plugins
|
|||
platypush/plugins/google.mail.rst
|
||||
platypush/plugins/google.maps.rst
|
||||
platypush/plugins/google.pubsub.rst
|
||||
platypush/plugins/google.translate.rst
|
||||
platypush/plugins/google.youtube.rst
|
||||
platypush/plugins/gpio.rst
|
||||
platypush/plugins/gpio.sensor.rst
|
||||
platypush/plugins/gpio.sensor.accelerometer.rst
|
||||
platypush/plugins/gpio.sensor.bme280.rst
|
||||
platypush/plugins/gpio.sensor.dht.rst
|
||||
platypush/plugins/gpio.sensor.distance.rst
|
||||
platypush/plugins/gpio.sensor.distance.vl53l1x.rst
|
||||
platypush/plugins/gpio.sensor.envirophat.rst
|
||||
|
@ -61,6 +53,7 @@ Plugins
|
|||
platypush/plugins/graphite.rst
|
||||
platypush/plugins/homeseer.rst
|
||||
platypush/plugins/http.request.rst
|
||||
platypush/plugins/http.request.ota.booking.rst
|
||||
platypush/plugins/http.request.rss.rst
|
||||
platypush/plugins/http.webpage.rst
|
||||
platypush/plugins/ifttt.rst
|
||||
|
@ -68,20 +61,13 @@ Plugins
|
|||
platypush/plugins/inspect.rst
|
||||
platypush/plugins/kafka.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.hue.rst
|
||||
platypush/plugins/linode.rst
|
||||
platypush/plugins/logger.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.chromecast.rst
|
||||
platypush/plugins/media.gstreamer.rst
|
||||
platypush/plugins/media.ctrl.rst
|
||||
platypush/plugins/media.kodi.rst
|
||||
platypush/plugins/media.mplayer.rst
|
||||
platypush/plugins/media.mpv.rst
|
||||
|
@ -97,21 +83,17 @@ Plugins
|
|||
platypush/plugins/music.rst
|
||||
platypush/plugins/music.mpd.rst
|
||||
platypush/plugins/music.snapcast.rst
|
||||
platypush/plugins/nextcloud.rst
|
||||
platypush/plugins/nmap.rst
|
||||
platypush/plugins/otp.rst
|
||||
platypush/plugins/pihole.rst
|
||||
platypush/plugins/ping.rst
|
||||
platypush/plugins/printer.cups.rst
|
||||
platypush/plugins/pushbullet.rst
|
||||
platypush/plugins/pwm.pca9685.rst
|
||||
platypush/plugins/qrcode.rst
|
||||
platypush/plugins/redis.rst
|
||||
platypush/plugins/rtorrent.rst
|
||||
platypush/plugins/sensor.rst
|
||||
platypush/plugins/serial.rst
|
||||
platypush/plugins/shell.rst
|
||||
platypush/plugins/smartthings.rst
|
||||
platypush/plugins/sound.rst
|
||||
platypush/plugins/ssh.rst
|
||||
platypush/plugins/stt.rst
|
||||
|
@ -119,10 +101,9 @@ Plugins
|
|||
platypush/plugins/stt.picovoice.hotword.rst
|
||||
platypush/plugins/stt.picovoice.speech.rst
|
||||
platypush/plugins/switch.rst
|
||||
platypush/plugins/switch.switchbot.rst
|
||||
platypush/plugins/switch.tplink.rst
|
||||
platypush/plugins/switch.wemo.rst
|
||||
platypush/plugins/switchbot.rst
|
||||
platypush/plugins/switchbot.bluetooth.rst
|
||||
platypush/plugins/system.rst
|
||||
platypush/plugins/tcp.rst
|
||||
platypush/plugins/tensorflow.rst
|
||||
|
@ -133,19 +114,14 @@ Plugins
|
|||
platypush/plugins/tts.rst
|
||||
platypush/plugins/tts.google.rst
|
||||
platypush/plugins/tv.samsung.ws.rst
|
||||
platypush/plugins/twilio.rst
|
||||
platypush/plugins/udp.rst
|
||||
platypush/plugins/user.rst
|
||||
platypush/plugins/utils.rst
|
||||
platypush/plugins/variable.rst
|
||||
platypush/plugins/weather.rst
|
||||
platypush/plugins/video.torrentcast.rst
|
||||
platypush/plugins/weather.buienradar.rst
|
||||
platypush/plugins/weather.darksky.rst
|
||||
platypush/plugins/weather.openweathermap.rst
|
||||
platypush/plugins/websocket.rst
|
||||
platypush/plugins/wiimote.rst
|
||||
platypush/plugins/zeroconf.rst
|
||||
platypush/plugins/zigbee.mqtt.rst
|
||||
platypush/plugins/zwave.rst
|
||||
platypush/plugins/zwave._base.rst
|
||||
platypush/plugins/zwave.mqtt.rst
|
||||
|
|
|
@ -21,6 +21,5 @@ Responses
|
|||
platypush/responses/system.rst
|
||||
platypush/responses/tensorflow.rst
|
||||
platypush/responses/todoist.rst
|
||||
platypush/responses/translate.rst
|
||||
platypush/responses/trello.rst
|
||||
platypush/responses/weather.buienradar.rst
|
||||
|
|
1
docs/wiki
Submodule
1
docs/wiki
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 6c0e65ccfe020bf6ce2eca43c387e827d4764c12
|
|
@ -16,21 +16,21 @@
|
|||
# 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
|
||||
# keep your configuration more organized.
|
||||
#include:
|
||||
# - include/logging.yaml
|
||||
# - include/media.yaml
|
||||
# - include/sensors.yaml
|
||||
include:
|
||||
- include/logging.yaml
|
||||
- include/media.yaml
|
||||
- include/sensors.yaml
|
||||
|
||||
# platypush logs on stdout by default. You can use the logging section to specify
|
||||
# an alternative file or change the logging level.
|
||||
#logging:
|
||||
# filename: ~/.local/log/platypush/platypush.log
|
||||
# level: INFO
|
||||
logging:
|
||||
filename: ~/.local/log/platypush/platypush.log
|
||||
level: INFO
|
||||
|
||||
# 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
|
||||
# will be used.
|
||||
#device_id: my_device
|
||||
device_id: myname
|
||||
|
||||
## --
|
||||
## Plugin configuration examples
|
||||
|
@ -40,10 +40,10 @@
|
|||
# a plugin class. The methods of the class with @action annotation will
|
||||
# be exported as runnable actions, while the __init__ parameters are
|
||||
# configuration attributes that you can initialize in your config.yaml.
|
||||
# Plugin classes are documented at https://docs.platypush.tech/en/latest/plugins.html
|
||||
# Plugin classes are documented at https://platypush.readthedocs.io/en/latest/plugins.html
|
||||
#
|
||||
# In this example we'll configure the light.hue plugin, see
|
||||
# https://docs.platypush.tech/en/latest/platypush/plugins/light.hue.html
|
||||
# https://platypush.readthedocs.io/en/latest/platypush/plugins/light.hue.html
|
||||
# for reference. You can easily install the required dependencies for the plugin through
|
||||
# pip install 'platypush[hue]'
|
||||
light.hue:
|
||||
|
@ -54,14 +54,14 @@ light.hue:
|
|||
- Living Room
|
||||
|
||||
# Example configuration of music.mpd plugin, see
|
||||
# https://docs.platypush.tech/en/latest/platypush/plugins/music.mpd.html
|
||||
# https://platypush.readthedocs.io/en/latest/platypush/plugins/music.mpd.html
|
||||
# You can easily install the dependencies through pip install 'platypush[mpd]'
|
||||
music.mpd:
|
||||
host: localhost
|
||||
port: 6600
|
||||
|
||||
# Example configuration of media.chromecast plugin, see
|
||||
# https://docs.platypush.tech/en/latest/platypush/plugins/media.chromecast.html
|
||||
# https://platypush.readthedocs.io/en/latest/platypush/plugins/media.chromecast.html
|
||||
# You can easily install the dependencies through pip install 'platypush[chromecast]'
|
||||
media.chromecast:
|
||||
chromecast: Living Room TV
|
||||
|
@ -69,15 +69,24 @@ media.chromecast:
|
|||
# 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
|
||||
# corresponding web panel to be enabled, if available)
|
||||
camera.pi:
|
||||
camera:
|
||||
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
|
||||
# Installing the dependencies: pip install 'platypush[ical,google]'
|
||||
calendar:
|
||||
calendars:
|
||||
- type: platypush.plugins.google.calendar.GoogleCalendarPlugin
|
||||
- type: platypush.plugins.calendar.ical.CalendarIcalPlugin
|
||||
-
|
||||
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
|
||||
|
||||
## --
|
||||
|
@ -88,10 +97,10 @@ calendar:
|
|||
# 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
|
||||
# supported parameters on the __init__ methods. You can check the documentation for the
|
||||
# available backends here: https://docs.platypush.tech/en/latest/backends.html.
|
||||
# available backends here: https://platypush.readthedocs.io/en/latest/backends.html.
|
||||
# Moreover, most of the backends will generate events that you can react to through custom
|
||||
# event hooks. Check here for the events documentation:
|
||||
# https://docs.platypush.tech/en/latest/events.html
|
||||
# https://platypush.readthedocs.io/en/latest/events.html
|
||||
#
|
||||
# You may usually want to enable the HTTP backend, as it provides many useful features on
|
||||
# top of platypush. Among those:
|
||||
|
@ -109,14 +118,40 @@ calendar:
|
|||
backend.http:
|
||||
# Listening port
|
||||
port: 8008
|
||||
# Websocket port
|
||||
websocket_port: 8009
|
||||
|
||||
# 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
|
||||
# our pictures and we mount it to the '/carousel' endpoint.
|
||||
resource_dirs:
|
||||
carousel: /mnt/hd/photos/carousel
|
||||
carousel: ~/Dropbox/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
|
||||
# trigger events whenever new entries are available. In the example below we show how to use
|
||||
|
@ -125,7 +160,9 @@ backend.http:
|
|||
# Install the required dependencies through 'pip install "platypush[rss,db]"'
|
||||
backend.http.poll:
|
||||
requests:
|
||||
- type: platypush.backend.http.request.rss.RssUpdates # HTTP poll type (RSS)
|
||||
-
|
||||
# HTTP poll type (RSS)
|
||||
type: platypush.backend.http.request.rss.RssUpdates
|
||||
# Remote URL
|
||||
url: http://www.theguardian.com/rss/world
|
||||
# Custom title
|
||||
|
@ -134,20 +171,20 @@ backend.http.poll:
|
|||
poll_seconds: 600
|
||||
# Maximum number of new entries to be processed
|
||||
max_entries: 10
|
||||
|
||||
- type: platypush.backend.http.request.rss.RssUpdates
|
||||
-
|
||||
type: platypush.backend.http.request.rss.RssUpdates
|
||||
url: http://www.physorg.com/rss-feed
|
||||
title: Phys.org
|
||||
poll_seconds: 600
|
||||
max_entries: 10
|
||||
|
||||
- type: platypush.backend.http.request.rss.RssUpdates
|
||||
-
|
||||
type: platypush.backend.http.request.rss.RssUpdates
|
||||
url: http://feeds.feedburner.com/Techcrunch
|
||||
title: Tech Crunch
|
||||
poll_seconds: 600
|
||||
max_entries: 10
|
||||
|
||||
- type: platypush.backend.http.request.rss.RssUpdates
|
||||
-
|
||||
type: platypush.backend.http.request.rss.RssUpdates
|
||||
url: http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml
|
||||
title: The New York Times
|
||||
poll_seconds: 300
|
||||
|
@ -159,15 +196,15 @@ backend.mqtt:
|
|||
host: mqtt-server
|
||||
# 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: MyBus
|
||||
topic: my_platypush_bus
|
||||
|
||||
# Raw TCP socket backend. It can run commands sent as JSON over telnet or netcat
|
||||
#backend.tcp:
|
||||
# port: 3333
|
||||
backend.tcp:
|
||||
port: 3333
|
||||
|
||||
# Websocket backend. Install required dependencies through 'pip install "platypush[http]"'
|
||||
#backend.websocket:
|
||||
# port: 8765
|
||||
backend.websocket:
|
||||
port: 8765
|
||||
|
||||
## --
|
||||
## Assistant configuration examples
|
||||
|
@ -217,12 +254,9 @@ backend.assistant.snowboy:
|
|||
assistant.echo:
|
||||
audio_player: mplayer
|
||||
|
||||
# Install Google Assistant dependencies with 'pip install "platypush[google-assistant-legacy]"'
|
||||
assistant.google:
|
||||
enabled: True
|
||||
|
||||
backend.assistant.google:
|
||||
enabled: True
|
||||
# Install Google Assistant dependencies with 'pip install "platypush[google-assistant]"'
|
||||
assistant.google.pushtotalk:
|
||||
language: en-US
|
||||
|
||||
## --
|
||||
## Procedure examples
|
||||
|
@ -293,14 +327,14 @@ procedure.outside_home:
|
|||
procedure.send_request(target, action, args):
|
||||
- action: mqtt.send_message
|
||||
args:
|
||||
topic: platypush_bus_mq/${target}
|
||||
topic: my_platypush_bus/${target}
|
||||
host: mqtt-server
|
||||
port: 1883
|
||||
msg:
|
||||
type: request
|
||||
target: ${target}
|
||||
action: ${action}
|
||||
args: ${args}
|
||||
args: "${context.get('args', {}}"
|
||||
|
||||
## --
|
||||
## Event hook examples
|
||||
|
@ -345,8 +379,10 @@ event.hook.SearchSongVoiceCommand:
|
|||
- action: music.mpd.search
|
||||
args:
|
||||
filter:
|
||||
artist: ${artist}
|
||||
title: ${title}
|
||||
- artist
|
||||
- ${artist}
|
||||
- any
|
||||
- ${title}
|
||||
|
||||
# Play the first search result
|
||||
- action: music.mpd.play
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
<!-- 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>
|
|
@ -1,43 +0,0 @@
|
|||
# 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')
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
[Unit]
|
||||
Description=Platypush daemon
|
||||
After=network.target redis.service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
# platypush installation path
|
||||
|
|
|
@ -16,17 +16,18 @@ from .context import register_backends
|
|||
from .cron.scheduler import CronScheduler
|
||||
from .event.processor import EventProcessor
|
||||
from .logger import Logger
|
||||
from .message.event import Event
|
||||
from .message.event.application import ApplicationStartedEvent
|
||||
from .message.event import Event, StopEvent
|
||||
from .message.event.application import ApplicationStartedEvent, ApplicationStoppedEvent
|
||||
from .message.request import Request
|
||||
from .message.response import Response
|
||||
from .utils import set_thread_name
|
||||
|
||||
|
||||
__author__ = 'Fabio Manganiello <info@fabiomanganiello.com>'
|
||||
__version__ = '0.21.1'
|
||||
__author__ = 'Fabio Manganiello <blacklight86@gmail.com>'
|
||||
__version__ = '0.12.9'
|
||||
|
||||
logger = logging.getLogger('platypush')
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
LOGGER.setLevel(logging.INFO)
|
||||
|
||||
|
||||
class Daemon:
|
||||
|
@ -41,9 +42,6 @@ class Daemon:
|
|||
# - plugins will post the responses they process
|
||||
bus = None
|
||||
|
||||
# Default bus queue name
|
||||
_default_redis_queue = 'platypush/bus'
|
||||
|
||||
pidfile = None
|
||||
|
||||
# backend_name => backend_obj map
|
||||
|
@ -53,7 +51,7 @@ class Daemon:
|
|||
n_tries = 2
|
||||
|
||||
def __init__(self, config_file=None, pidfile=None, requests_to_process=None,
|
||||
no_capture_stdout=False, no_capture_stderr=False, redis_queue=None):
|
||||
no_capture_stdout=False, no_capture_stderr=False):
|
||||
"""
|
||||
Constructor
|
||||
Params:
|
||||
|
@ -67,7 +65,6 @@ class Daemon:
|
|||
capture by the logging system
|
||||
no_capture_stderr -- Set to true if you want to disable the stderr
|
||||
capture by the logging system
|
||||
redis_queue -- Name of the (Redis) queue used for dispatching messages (default: platypush/bus).
|
||||
"""
|
||||
|
||||
if pidfile:
|
||||
|
@ -75,21 +72,15 @@ class Daemon:
|
|||
with open(self.pidfile, 'w') as f:
|
||||
f.write(str(os.getpid()))
|
||||
|
||||
self.redis_queue = redis_queue or self._default_redis_queue
|
||||
self.config_file = config_file
|
||||
Config.init(self.config_file)
|
||||
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_stderr = no_capture_stderr
|
||||
self.event_processor = EventProcessor()
|
||||
self.requests_to_process = requests_to_process
|
||||
self.processed_requests = 0
|
||||
self.cron_scheduler = None
|
||||
|
||||
@classmethod
|
||||
def build_from_cmdline(cls, args):
|
||||
|
@ -115,17 +106,11 @@ class Daemon:
|
|||
help="Set this flag if you have max stack depth " +
|
||||
"exceeded errors so stderr won't be captured by " +
|
||||
"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)
|
||||
return cls(config_file=opts.config, pidfile=opts.pidfile,
|
||||
no_capture_stdout=opts.no_capture_stdout,
|
||||
no_capture_stderr=opts.no_capture_stderr,
|
||||
redis_queue=opts.redis_queue)
|
||||
no_capture_stderr=opts.no_capture_stderr)
|
||||
|
||||
def on_message(self):
|
||||
"""
|
||||
|
@ -143,17 +128,20 @@ class Daemon:
|
|||
try:
|
||||
msg.execute(n_tries=self.n_tries)
|
||||
except PermissionError:
|
||||
logger.info('Dropped unauthorized request: {}'.format(msg))
|
||||
LOGGER.info('Dropped unauthorized request: {}'.format(msg))
|
||||
|
||||
self.processed_requests += 1
|
||||
if self.requests_to_process \
|
||||
and self.processed_requests >= self.requests_to_process:
|
||||
self.stop_app()
|
||||
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):
|
||||
if not msg.disable_logging:
|
||||
logger.info('Received event: {}'.format(msg))
|
||||
LOGGER.info('Received event: {}'.format(msg))
|
||||
self.event_processor.process_event(msg)
|
||||
|
||||
return _f
|
||||
|
@ -162,20 +150,22 @@ class Daemon:
|
|||
""" Stops the backends and the bus """
|
||||
for backend in self.backends.values():
|
||||
backend.stop()
|
||||
|
||||
self.bus.stop()
|
||||
if self.cron_scheduler:
|
||||
self.cron_scheduler.stop()
|
||||
|
||||
def run(self):
|
||||
def start(self):
|
||||
""" Start the daemon """
|
||||
if not self.no_capture_stdout:
|
||||
sys.stdout = Logger(logger.info)
|
||||
sys.stdout = Logger(LOGGER.info)
|
||||
if not self.no_capture_stderr:
|
||||
sys.stderr = Logger(logger.warning)
|
||||
sys.stderr = Logger(LOGGER.warning)
|
||||
|
||||
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
|
||||
self.backends = register_backends(bus=self.bus, global_scope=True)
|
||||
|
@ -186,8 +176,7 @@ class Daemon:
|
|||
|
||||
# Start the cron scheduler
|
||||
if Config.get_cronjobs():
|
||||
self.cron_scheduler = CronScheduler(jobs=Config.get_cronjobs())
|
||||
self.cron_scheduler.start()
|
||||
CronScheduler(jobs=Config.get_cronjobs()).start()
|
||||
|
||||
self.bus.post(ApplicationStartedEvent())
|
||||
|
||||
|
@ -195,8 +184,9 @@ class Daemon:
|
|||
try:
|
||||
self.bus.poll()
|
||||
except KeyboardInterrupt:
|
||||
logger.info('SIGINT received, terminating application')
|
||||
LOGGER.info('SIGINT received, terminating application')
|
||||
finally:
|
||||
self.bus.post(ApplicationStoppedEvent())
|
||||
self.stop_app()
|
||||
|
||||
|
||||
|
@ -204,8 +194,9 @@ def main():
|
|||
"""
|
||||
Platypush daemon main
|
||||
"""
|
||||
|
||||
app = Daemon.build_from_cmdline(sys.argv[1:])
|
||||
app.run()
|
||||
app.start()
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
|
|
@ -4,24 +4,21 @@
|
|||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
|
||||
from threading import Thread, Event as ThreadEvent, get_ident
|
||||
from typing import Optional, Dict
|
||||
from threading import Thread
|
||||
from typing import Optional
|
||||
|
||||
from platypush.bus import Bus
|
||||
from platypush.config import Config
|
||||
from platypush.context import get_backend
|
||||
from platypush.message.event.zeroconf import ZeroconfServiceAddedEvent, ZeroconfServiceRemovedEvent
|
||||
from platypush.utils import set_timeout, clear_timeout, \
|
||||
get_redis_queue_name_by_message, set_thread_name, get_backend_name_by_class
|
||||
get_redis_queue_name_by_message, set_thread_name
|
||||
|
||||
from platypush import __version__
|
||||
from platypush.event import EventGenerator
|
||||
from platypush.message import Message
|
||||
from platypush.message.event import Event
|
||||
from platypush.message.event import Event, StopEvent
|
||||
from platypush.message.request import Request
|
||||
from platypush.message.response import Response
|
||||
|
||||
|
@ -53,7 +50,7 @@ class Backend(Thread, EventGenerator):
|
|||
|
||||
self._thread_name = self.__class__.__name__
|
||||
EventGenerator.__init__(self)
|
||||
Thread.__init__(self, name=self._thread_name, daemon=True)
|
||||
Thread.__init__(self, name=self._thread_name)
|
||||
|
||||
# If no bus is specified, create an internal queue where
|
||||
# the received messages will be pushed
|
||||
|
@ -61,11 +58,10 @@ class Backend(Thread, EventGenerator):
|
|||
self.poll_seconds = float(poll_seconds) if poll_seconds else None
|
||||
self.device_id = Config.get('device_id')
|
||||
self.thread_id = None
|
||||
self._stop_event = ThreadEvent()
|
||||
self._should_stop = False
|
||||
self._stop_event = threading.Event()
|
||||
self._kwargs = kwargs
|
||||
self.logger = logging.getLogger('platypush:backend:' + get_backend_name_by_class(self.__class__))
|
||||
self.zeroconf = None
|
||||
self.zeroconf_info = None
|
||||
self.logger = logging.getLogger(self.__class__.__name__)
|
||||
|
||||
# Internal-only, we set the request context on a backend if that
|
||||
# backend is intended to react for a response to a specific request
|
||||
|
@ -75,6 +71,8 @@ class Backend(Thread, EventGenerator):
|
|||
if 'logging' in kwargs:
|
||||
self.logger.setLevel(getattr(logging, kwargs.get('logging').upper()))
|
||||
|
||||
Thread.__init__(self)
|
||||
|
||||
def on_message(self, msg):
|
||||
"""
|
||||
Callback when a message is received on the backend.
|
||||
|
@ -101,6 +99,10 @@ class Backend(Thread, EventGenerator):
|
|||
self.stop()
|
||||
return
|
||||
|
||||
if isinstance(msg, StopEvent) and msg.targets_me():
|
||||
self.logger.info('Received STOP event on {}'.format(self.__class__.__name__))
|
||||
self._should_stop = True
|
||||
else:
|
||||
msg.backend = self # Augment message to be able to process responses
|
||||
self.bus.post(msg)
|
||||
|
||||
|
@ -219,7 +221,7 @@ class Backend(Thread, EventGenerator):
|
|||
|
||||
def run(self):
|
||||
""" Starts the backend thread. To be implemented in the derived classes if the loop method isn't defined. """
|
||||
self.thread_id = get_ident()
|
||||
self.thread_id = threading.get_ident()
|
||||
set_thread_name(self._thread_name)
|
||||
if not callable(self.loop):
|
||||
return
|
||||
|
@ -262,17 +264,20 @@ class Backend(Thread, EventGenerator):
|
|||
def stop(self):
|
||||
""" Stops the backend thread by sending a STOP event on its bus """
|
||||
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.unregister_service()
|
||||
self.on_stop()
|
||||
|
||||
Thread(target=_async_stop).start()
|
||||
|
||||
def should_stop(self):
|
||||
return self._stop_event.is_set()
|
||||
return self._should_stop
|
||||
|
||||
def wait_stop(self, timeout=None) -> bool:
|
||||
return self._stop_event.wait(timeout)
|
||||
def wait_stop(self, timeout=None):
|
||||
self._stop_event.wait(timeout)
|
||||
|
||||
def _get_redis(self):
|
||||
import redis
|
||||
|
@ -301,104 +306,5 @@ class Backend(Thread, EventGenerator):
|
|||
except Exception as e:
|
||||
self.logger.error('Error while processing response to {}: {}'.format(msg, str(e)))
|
||||
|
||||
@staticmethod
|
||||
def _get_ip() -> str:
|
||||
"""
|
||||
Get the IP address of the machine.
|
||||
"""
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.connect(('8.8.8.8', 80))
|
||||
addr = s.getsockname()[0]
|
||||
s.close()
|
||||
return addr
|
||||
|
||||
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.
|
||||
|
||||
: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:
|
||||
from zeroconf import ServiceInfo, Zeroconf
|
||||
from platypush.plugins.zeroconf import ZeroconfListener
|
||||
except ImportError:
|
||||
self.logger.warning('zeroconf package not available, service discovery will be disabled.')
|
||||
return
|
||||
|
||||
self.zeroconf = Zeroconf()
|
||||
srv_desc = {
|
||||
'name': 'Platypush',
|
||||
'vendor': 'Platypush',
|
||||
'version': __version__,
|
||||
**(properties or {}),
|
||||
}
|
||||
|
||||
name = name or re.sub(r'Backend$', '', self.__class__.__name__).lower()
|
||||
srv_type = srv_type or '_platypush-{name}._{proto}.local.'.format(name=name, proto='udp' if udp else 'tcp')
|
||||
srv_name = srv_name or '{host}.{type}'.format(host=self.device_id, type=srv_type)
|
||||
|
||||
if port:
|
||||
srv_port = port
|
||||
else:
|
||||
srv_port = self.port if hasattr(self, 'port') else None
|
||||
|
||||
self.zeroconf_info = ServiceInfo(srv_type, srv_name,
|
||||
addresses=[socket.inet_aton(self._get_ip())],
|
||||
port=srv_port,
|
||||
weight=0,
|
||||
priority=0,
|
||||
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.bus.post(ZeroconfServiceAddedEvent(service_type=srv_type, service_name=srv_name,
|
||||
service_info=ZeroconfListener.parse_service_info(self.zeroconf_info)))
|
||||
|
||||
def unregister_service(self):
|
||||
"""
|
||||
Unregister the Zeroconf service configuration if available.
|
||||
"""
|
||||
if self.zeroconf and self.zeroconf_info:
|
||||
try:
|
||||
self.zeroconf.unregister_service(self.zeroconf_info)
|
||||
except Exception as e:
|
||||
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 = None
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
|
|
@ -42,7 +42,6 @@ class AdafruitIoBackend(Backend):
|
|||
if not plugin:
|
||||
raise RuntimeError('Adafruit IO plugin not configured')
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
self._client = MQTTClient(plugin._username, plugin._key)
|
||||
self._client.on_connect = self.on_connect()
|
||||
self._client.on_disconnect = self.on_disconnect()
|
||||
|
@ -53,25 +52,18 @@ class AdafruitIoBackend(Backend):
|
|||
for feed in self.feeds:
|
||||
client.subscribe(feed)
|
||||
self.bus.post(ConnectedEvent())
|
||||
|
||||
return _handler
|
||||
|
||||
def on_disconnect(self):
|
||||
def _handler(client):
|
||||
self.bus.post(DisconnectedEvent())
|
||||
|
||||
return _handler
|
||||
|
||||
def on_message(self, msg):
|
||||
# noinspection PyUnusedLocal
|
||||
def on_message(self):
|
||||
def _handler(client, feed, data):
|
||||
try:
|
||||
data = float(data)
|
||||
except Exception as e:
|
||||
self.logger.debug('Not a number: {}: {}'.format(data, e))
|
||||
|
||||
try: data = float(data)
|
||||
except: pass
|
||||
self.bus.post(FeedUpdateEvent(feed=feed, data=data))
|
||||
|
||||
return _handler
|
||||
|
||||
def run(self):
|
||||
|
@ -89,4 +81,5 @@ class AdafruitIoBackend(Backend):
|
|||
self.logger.exception(e)
|
||||
self._client = None
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import datetime
|
||||
import enum
|
||||
import os
|
||||
import time
|
||||
import threading
|
||||
|
@ -7,7 +6,7 @@ import threading
|
|||
from typing import Optional, Union, Dict, Any, List
|
||||
|
||||
import croniter
|
||||
from dateutil.tz import gettz
|
||||
import enum
|
||||
|
||||
from platypush.backend import Backend
|
||||
from platypush.context import get_bus, get_plugin
|
||||
|
@ -55,20 +54,18 @@ class Alarm:
|
|||
self._runtime_snooze_interval = snooze_interval
|
||||
|
||||
def get_next(self) -> float:
|
||||
now = datetime.datetime.now().replace(tzinfo=gettz()) # lgtm [py/call-to-non-callable]
|
||||
now = time.time()
|
||||
|
||||
try:
|
||||
cron = croniter.croniter(self.when, now)
|
||||
return cron.get_next()
|
||||
except (AttributeError, croniter.CroniterBadCronError):
|
||||
try:
|
||||
timestamp = datetime.datetime.fromisoformat(self.when).replace(
|
||||
tzinfo=gettz()) # lgtm [py/call-to-non-callable]
|
||||
timestamp = datetime.datetime.fromisoformat(self.when).timestamp()
|
||||
except (TypeError, ValueError):
|
||||
timestamp = (datetime.datetime.now().replace(tzinfo=gettz()) + # lgtm [py/call-to-non-callable]
|
||||
datetime.timedelta(seconds=int(self.when)))
|
||||
timestamp = (datetime.datetime.now() + datetime.timedelta(seconds=int(self.when))).timestamp()
|
||||
|
||||
return timestamp.timestamp() if timestamp >= now else None
|
||||
return timestamp if timestamp >= now else None
|
||||
|
||||
def is_enabled(self):
|
||||
return self._enabled
|
||||
|
|
|
@ -21,10 +21,12 @@ class AssistantGoogleBackend(AssistantBackend):
|
|||
|
||||
It listens for voice commands and post conversation events on the bus.
|
||||
|
||||
**WARNING**: The Google Assistant library used by this backend has officially been deprecated:
|
||||
https://developers.google.com/assistant/sdk/reference/library/python/. This backend still works on most of the
|
||||
devices where I use it, but its correct functioning is not guaranteed as the assistant library is no longer
|
||||
maintained.
|
||||
**WARNING**: This backend is deprecated, as the underlying Google Assistant
|
||||
library has been deprecated too: https://developers.google.com/assistant/sdk/reference/library/python/
|
||||
The old library might still work on some systems but its proper functioning
|
||||
is not guaranteed.
|
||||
Please use the Snowboy backend for hotword detection and the Google Assistant
|
||||
push-to-talk plugin for assistant interaction instead.
|
||||
|
||||
Triggers:
|
||||
|
||||
|
|
|
@ -165,7 +165,6 @@ class AssistantSnowboyBackend(AssistantBackend):
|
|||
return callback
|
||||
|
||||
def on_stop(self):
|
||||
super().on_stop()
|
||||
if self.detector:
|
||||
self.detector.terminate()
|
||||
self.detector = None
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
import time
|
||||
from threading import Thread, RLock
|
||||
from typing import Dict, Optional, List
|
||||
from typing import Dict, Optional
|
||||
|
||||
from platypush.backend.sensor import SensorBackend
|
||||
from platypush.context import get_plugin
|
||||
from platypush.message.event.bluetooth import BluetoothDeviceFoundEvent, BluetoothDeviceLostEvent
|
||||
|
||||
|
||||
|
@ -23,12 +20,10 @@ class BluetoothScannerBackend(SensorBackend):
|
|||
|
||||
"""
|
||||
|
||||
def __init__(self, device_id: Optional[int] = None, scan_duration: int = 10,
|
||||
track_devices: Optional[List[str]] = None, **kwargs):
|
||||
def __init__(self, device_id: Optional[int] = None, scan_duration: int = 10, **kwargs):
|
||||
"""
|
||||
: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 track_devices: List of addresses of devices to actively track, even if they aren't discoverable.
|
||||
"""
|
||||
super().__init__(plugin='bluetooth', plugin_args={
|
||||
'device_id': device_id,
|
||||
|
@ -36,72 +31,17 @@ class BluetoothScannerBackend(SensorBackend):
|
|||
}, **kwargs)
|
||||
|
||||
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]):
|
||||
for addr, dev in data.items():
|
||||
self._add_last_seen_device(dev)
|
||||
if addr not in self._last_seen_devices:
|
||||
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():
|
||||
if addr not in data and addr not in self.track_devices:
|
||||
self._remove_last_seen_device(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)
|
||||
if addr not in data:
|
||||
self.bus.post(BluetoothDeviceLostEvent(address=dev.pop('addr'), **dev))
|
||||
del self._last_seen_devices[addr]
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
|
|
@ -13,28 +13,25 @@ Bd addr are represented as standard python strings, e.g. "aa:bb:cc:dd:ee:ff".
|
|||
import asyncio
|
||||
from enum import Enum
|
||||
from collections import namedtuple
|
||||
import time
|
||||
import struct
|
||||
import itertools
|
||||
|
||||
|
||||
class CreateConnectionChannelError(Enum):
|
||||
NoError = 0
|
||||
MaxPendingConnectionsReached = 1
|
||||
|
||||
|
||||
class ConnectionStatus(Enum):
|
||||
Disconnected = 0
|
||||
Connected = 1
|
||||
Ready = 2
|
||||
|
||||
|
||||
class DisconnectReason(Enum):
|
||||
Unspecified = 0
|
||||
ConnectionEstablishmentFailed = 1
|
||||
TimedOut = 2
|
||||
BondingKeysMismatch = 3
|
||||
|
||||
|
||||
class RemovedReason(Enum):
|
||||
RemovedByThisClient = 0
|
||||
ForceDisconnectedByThisClient = 1
|
||||
|
@ -47,7 +44,6 @@ class RemovedReason(Enum):
|
|||
|
||||
CouldntLoadDevice = 7
|
||||
|
||||
|
||||
class ClickType(Enum):
|
||||
ButtonDown = 0
|
||||
ButtonUp = 1
|
||||
|
@ -56,24 +52,20 @@ class ClickType(Enum):
|
|||
ButtonDoubleClick = 4
|
||||
ButtonHold = 5
|
||||
|
||||
|
||||
class BdAddrType(Enum):
|
||||
PublicBdAddrType = 0
|
||||
RandomBdAddrType = 1
|
||||
|
||||
|
||||
class LatencyMode(Enum):
|
||||
NormalLatency = 0
|
||||
LowLatency = 1
|
||||
HighLatency = 2
|
||||
|
||||
|
||||
class BluetoothControllerState(Enum):
|
||||
Detached = 0
|
||||
Resetting = 1
|
||||
Attached = 2
|
||||
|
||||
|
||||
class ScanWizardResult(Enum):
|
||||
WizardSuccess = 0
|
||||
WizardCancelledByUser = 1
|
||||
|
@ -83,7 +75,6 @@ class ScanWizardResult(Enum):
|
|||
WizardInternetBackendError = 5
|
||||
WizardInvalidData = 6
|
||||
|
||||
|
||||
class ButtonScanner:
|
||||
"""ButtonScanner class.
|
||||
|
||||
|
@ -99,7 +90,6 @@ class ButtonScanner:
|
|||
self._scan_id = next(ButtonScanner._cnt)
|
||||
self.on_advertisement_packet = lambda scanner, bd_addr, name, rssi, is_private, already_verified: None
|
||||
|
||||
|
||||
class ScanWizard:
|
||||
"""ScanWizard class
|
||||
|
||||
|
@ -123,7 +113,6 @@ class ScanWizard:
|
|||
self.on_button_connected = lambda scan_wizard, bd_addr, name: None
|
||||
self.on_completed = lambda scan_wizard, result, bd_addr, name: None
|
||||
|
||||
|
||||
class ButtonConnectionChannel:
|
||||
"""ButtonConnectionChannel class.
|
||||
|
||||
|
@ -144,7 +133,7 @@ class ButtonConnectionChannel:
|
|||
|
||||
_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._bd_addr = bd_addr
|
||||
self._latency_mode = latency_mode
|
||||
|
@ -175,9 +164,7 @@ class ButtonConnectionChannel:
|
|||
|
||||
self._latency_mode = latency_mode
|
||||
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
|
||||
def auto_disconnect_time(self):
|
||||
|
@ -191,10 +178,7 @@ class ButtonConnectionChannel:
|
|||
|
||||
self._auto_disconnect_time = auto_disconnect_time
|
||||
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):
|
||||
"""FlicClient class.
|
||||
|
@ -228,8 +212,7 @@ class FlicClient(asyncio.Protocol):
|
|||
("EvtButtonSingleOrDoubleClick", "<IBBI", "conn_id click_type was_queued time_diff"),
|
||||
("EvtButtonSingleOrDoubleClickOrHold", "<IBBI", "conn_id click_type was_queued time_diff"),
|
||||
("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"),
|
||||
("EvtGotSpaceForNewConnection", "<B", "max_concurrently_connected_buttons"),
|
||||
("EvtBluetoothControllerStateChange", "<B", "state"),
|
||||
|
@ -240,8 +223,8 @@ class FlicClient(asyncio.Protocol):
|
|||
("EvtScanWizardButtonConnected", "<I", "scan_wizard_id"),
|
||||
("EvtScanWizardCompleted", "<IB", "scan_wizard_id result")
|
||||
]
|
||||
_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 is None else namedtuple(x[0], x[2]), _EVENTS))
|
||||
_EVENT_STRUCTS = list(map(lambda x: None if x == 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))
|
||||
|
||||
_COMMANDS = [
|
||||
("CmdGetInfo", "", ""),
|
||||
|
@ -261,19 +244,18 @@ class FlicClient(asyncio.Protocol):
|
|||
_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))
|
||||
|
||||
@staticmethod
|
||||
|
||||
def _bdaddr_bytes_to_string(bdaddr_bytes):
|
||||
return ":".join(map(lambda x: "%02x" % x, reversed(bdaddr_bytes)))
|
||||
|
||||
@staticmethod
|
||||
def _bdaddr_string_to_bytes(bdaddr_string):
|
||||
return bytearray.fromhex("".join(reversed(bdaddr_string.split(":"))))
|
||||
|
||||
def __init__(self, loop, parent=None):
|
||||
def __init__(self, loop,parent=None):
|
||||
self.loop = loop
|
||||
self.buffer = b""
|
||||
self.transport = None
|
||||
self.parent = parent
|
||||
self.buffer=b""
|
||||
self.transport=None
|
||||
self.parent=parent
|
||||
self._scanners = {}
|
||||
self._scan_wizards = {}
|
||||
self._connection_channels = {}
|
||||
|
@ -287,10 +269,11 @@ class FlicClient(asyncio.Protocol):
|
|||
self.on_get_button_uuid = lambda addr, uuid: None
|
||||
|
||||
def connection_made(self, transport):
|
||||
self.transport = transport
|
||||
self.transport=transport
|
||||
if self.parent:
|
||||
self.parent.register_protocol(self)
|
||||
|
||||
|
||||
def close(self):
|
||||
"""Closes the client. The handle_events() method will return."""
|
||||
if self._closed:
|
||||
|
@ -359,9 +342,7 @@ class FlicClient(asyncio.Protocol):
|
|||
channel._client = self
|
||||
|
||||
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):
|
||||
"""Remove a connection channel.
|
||||
|
@ -403,6 +384,7 @@ class FlicClient(asyncio.Protocol):
|
|||
"""
|
||||
self._send_command("CmdGetButtonUUID", {"bd_addr": bd_addr})
|
||||
|
||||
|
||||
def run_on_handle_events_thread(self, callback):
|
||||
"""Run a function on the thread that handles the events."""
|
||||
if threading.get_ident() == self._handle_event_thread_ident:
|
||||
|
@ -417,7 +399,7 @@ class FlicClient(asyncio.Protocol):
|
|||
items[key] = value.value
|
||||
|
||||
if "bd_addr" in items:
|
||||
items["bd_addr"] = FlicClient._bdaddr_string_to_bytes()
|
||||
items["bd_addr"] = FlicClient._bdaddr_string_to_bytes(items["bd_addr"])
|
||||
|
||||
opcode = FlicClient._COMMAND_NAME_TO_OPCODE[name]
|
||||
data_bytes = FlicClient._COMMAND_STRUCTS[opcode].pack(*FlicClient._COMMAND_NAMED_TUPLES[opcode](**items))
|
||||
|
@ -433,16 +415,16 @@ class FlicClient(asyncio.Protocol):
|
|||
return
|
||||
opcode = data[0]
|
||||
|
||||
if opcode >= len(FlicClient._EVENTS) or FlicClient._EVENTS[opcode] is None:
|
||||
if opcode >= len(FlicClient._EVENTS) or FlicClient._EVENTS[opcode] == None:
|
||||
return
|
||||
|
||||
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()
|
||||
|
||||
# Process some kind of items whose data type is not supported by struct
|
||||
if "bd_addr" in items:
|
||||
items["bd_addr"] = FlicClient._bdaddr_bytes_to_string()
|
||||
items["bd_addr"] = FlicClient._bdaddr_bytes_to_string(items["bd_addr"])
|
||||
|
||||
if "name" in items:
|
||||
items["name"] = items["name"].decode("utf-8")
|
||||
|
@ -463,14 +445,13 @@ class FlicClient(asyncio.Protocol):
|
|||
|
||||
if event_name == "EvtGetInfoResponse":
|
||||
items["bluetooth_controller_state"] = BluetoothControllerState(items["bluetooth_controller_state"])
|
||||
items["my_bd_addr"] = FlicClient._bdaddr_bytes_to_string()
|
||||
items["my_bd_addr"] = FlicClient._bdaddr_bytes_to_string(items["my_bd_addr"])
|
||||
items["my_bd_addr_type"] = BdAddrType(items["my_bd_addr_type"])
|
||||
items["bd_addr_of_verified_buttons"] = []
|
||||
|
||||
pos = FlicClient._EVENT_STRUCTS[opcode].size
|
||||
for i in range(items["nb_verified_buttons"]):
|
||||
items["bd_addr_of_verified_buttons"].append(
|
||||
FlicClient._bdaddr_bytes_to_string())
|
||||
items["bd_addr_of_verified_buttons"].append(FlicClient._bdaddr_bytes_to_string(data[1 + pos : 1 + pos + 6]))
|
||||
pos += 6
|
||||
|
||||
if event_name == "EvtBluetoothControllerStateChange":
|
||||
|
@ -488,8 +469,7 @@ class FlicClient(asyncio.Protocol):
|
|||
if event_name == "EvtAdvertisementPacket":
|
||||
scanner = self._scanners.get(items["scan_id"])
|
||||
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":
|
||||
channel = self._connection_channels[items["conn_id"]]
|
||||
|
@ -514,12 +494,10 @@ class FlicClient(asyncio.Protocol):
|
|||
channel.on_button_click_or_hold(channel, items["click_type"], items["was_queued"], items["time_diff"])
|
||||
if event_name == "EvtButtonSingleOrDoubleClick":
|
||||
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":
|
||||
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":
|
||||
self.on_new_verified_button(items["bd_addr"])
|
||||
|
@ -558,16 +536,19 @@ class FlicClient(asyncio.Protocol):
|
|||
del self._scan_wizards[items["scan_wizard_id"]]
|
||||
scan_wizard.on_completed(scan_wizard, items["result"], scan_wizard._bd_addr, scan_wizard._name)
|
||||
|
||||
def data_received(self, data):
|
||||
cdata = self.buffer + data
|
||||
self.buffer = b""
|
||||
|
||||
def data_received(self,data):
|
||||
cdata=self.buffer+data
|
||||
self.buffer=b""
|
||||
while len(cdata):
|
||||
packet_len = cdata[0] | (cdata[1] << 8)
|
||||
packet_len += 2
|
||||
if len(cdata) >= packet_len:
|
||||
if len(cdata)>= packet_len:
|
||||
self._dispatch_event(cdata[2:packet_len])
|
||||
cdata = cdata[packet_len:]
|
||||
cdata=cdata[packet_len:]
|
||||
else:
|
||||
if len(cdata):
|
||||
self.buffer = cdata # unlikely to happen but.....
|
||||
self.buffer=cdata #unlikely to happen but.....
|
||||
break
|
||||
|
||||
|
||||
|
|
|
@ -229,8 +229,8 @@ class FlicClient:
|
|||
("EvtScanWizardButtonConnected", "<I", "scan_wizard_id"),
|
||||
("EvtScanWizardCompleted", "<IB", "scan_wizard_id result")
|
||||
]
|
||||
_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 is None else namedtuple(x[0], x[2]), _EVENTS))
|
||||
_EVENT_STRUCTS = list(map(lambda x: None if x == 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))
|
||||
|
||||
_COMMANDS = [
|
||||
("CmdGetInfo", "", ""),
|
||||
|
@ -250,11 +250,9 @@ class FlicClient:
|
|||
_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))
|
||||
|
||||
@staticmethod
|
||||
def _bdaddr_bytes_to_string(bdaddr_bytes):
|
||||
return ":".join(map(lambda x: "%02x" % x, reversed(bdaddr_bytes)))
|
||||
|
||||
@staticmethod
|
||||
def _bdaddr_string_to_bytes(bdaddr_string):
|
||||
return bytearray.fromhex("".join(reversed(bdaddr_string.split(":"))))
|
||||
|
||||
|
@ -440,7 +438,7 @@ class FlicClient:
|
|||
return
|
||||
opcode = data[0]
|
||||
|
||||
if opcode >= len(FlicClient._EVENTS) or FlicClient._EVENTS[opcode] is None:
|
||||
if opcode >= len(FlicClient._EVENTS) or FlicClient._EVENTS[opcode] == None:
|
||||
return
|
||||
|
||||
event_name = FlicClient._EVENTS[opcode][0]
|
||||
|
|
|
@ -19,10 +19,6 @@ class CameraPiBackend(Backend):
|
|||
|
||||
* **picamera** (``pip install picamera``)
|
||||
* **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):
|
||||
|
@ -34,7 +30,7 @@ class CameraPiBackend(Backend):
|
|||
return self.value == other
|
||||
|
||||
# noinspection PyUnresolvedReferences,PyPackageRequirements
|
||||
def __init__(self, listen_port, bind_address='0.0.0.0', x_resolution=640, y_resolution=480,
|
||||
def __init__(self, listen_port, x_resolution=640, y_resolution=480,
|
||||
redis_queue='platypush/camera/pi',
|
||||
start_recording_on_startup=True,
|
||||
framerate=24, hflip=False, vflip=False,
|
||||
|
@ -49,17 +45,13 @@ class CameraPiBackend(Backend):
|
|||
|
||||
:param listen_port: Port where the camera process will provide the video output while recording
|
||||
:type listen_port: int
|
||||
|
||||
:param bind_address: Bind address (default: 0.0.0.0).
|
||||
:type bind_address: str
|
||||
"""
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.bind_address = bind_address
|
||||
self.listen_port = listen_port
|
||||
self.server_socket = socket.socket()
|
||||
self.server_socket.bind((self.bind_address, self.listen_port))
|
||||
self.server_socket.bind(('0.0.0.0', self.listen_port))
|
||||
self.server_socket.listen(0)
|
||||
|
||||
import picamera
|
||||
|
@ -126,7 +118,7 @@ class CameraPiBackend(Backend):
|
|||
while True:
|
||||
self.camera.wait_recording(2)
|
||||
else:
|
||||
while not self.should_stop():
|
||||
while True:
|
||||
connection = self.server_socket.accept()[0].makefile('wb')
|
||||
self.logger.info('Accepted client connection on port {}'.format(self.listen_port))
|
||||
|
||||
|
@ -138,13 +130,13 @@ class CameraPiBackend(Backend):
|
|||
self.logger.info('Client closed connection')
|
||||
try:
|
||||
self.stop_recording()
|
||||
except Exception as e:
|
||||
self.logger.warning('Could not stop recording: {}'.format(str(e)))
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
connection.close()
|
||||
except Exception as e:
|
||||
self.logger.warning('Could not close connection: {}'.format(str(e)))
|
||||
except:
|
||||
pass
|
||||
|
||||
self.send_camera_action(self.CameraAction.START_RECORDING)
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue