Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/nununo/pycandle2017

Candle (2017) in Python
https://github.com/nununo/pycandle2017

art candle interactive-installation python raspberry-pi twisted video

Last synced: 8 days ago
JSON representation

Candle (2017) in Python

Awesome Lists containing this project

README

        

Candle 2017 Code Overview
=========================

Preamble
--------

Please refer to [Candle 2017](README.md) for general project information and references to other documents.

All code is internally documented with both docstrings and comments, while efforts have been made to use consistent and meaningful names all around.

Given that most of **Candle 2017** is about input, output and, generally, system level interactions (spawning processes, sending messages, handling external data and events, etc.) no automated tests are put in place; testing such interactions *is difficult* and error-prone, to say the last; suggestions will be welcomed, however.

High-level Overview
-------------------

**Candle 2017** is a [Twisted](http://twistedmatrix.com/) application, with mostly asynchronous code, distributed across the following top-level components:

| module or package | Description |
|-------------------|-------------|
| `candle2017.py` | Main entry point: loads the settings file, sets up the logging system, creates and starts an *input manager* and a *player manager*; ensures both are stopped on exit. |
| `log` | Log setup and management code. |
| `common` | Process spawning and tracking code used by `inputs` and `player`. |
| `inputs` | Input related code: details below. |
| `player` | Video playing code: details below. |

Both the `inputs` and `player` packages export a single name each: `InputManager` and `PlayerManager`, respectively, that implement a common interface:

```
def __init__(self, reactor, wiring, settings):
"""
Initializer:
- `reactor` is the Twisted reactor.
- `wiring` is a Wiring instance, used to fire/handle event-like calls.
- `settings` is a dict built out of the `settings.json` file.
"""

def start(self):
"""
Returns a Twisted Deferred that fires on success or failure.
"""

def stop(self):
"""
Returns a Twisted Deferred that fires on success or failure.
"""
```

Notes:

* Making things run is just:
* Instantiating one `InputManager` and one `PlayerManager`.
* Calling `start` on each.

* `wiring` is a [Wires](https://pypi.python.org/pypi/wires) instance:
* Used as a callable-based event/notification system.
* `PlayerManager` handles `wiring.change_play_level` calls.
* `InputManager` triggers `wiring.change_play_level` calls.
* Also used for cross-input communication and to push logs to web clients.

The `player` package
--------------------

Exports the `PlayerManager` class which handles all video playing:

* Spawns and tracks a private DBus instance process: see `dbus_manager.py`.
* Spawns one OMXPlayer process per level, attached to the private DBus instance:
* The level 0 player is spawned such that it plays in a loop.
* The remaining players are spawned and paused, ready to fade in and play at any time.
* Each level N player displays on a visual layer above players for levels ` calls to process "sensor readings".
* `wiring.` depends on the AGD input configuration from the settings file, where \ will be one of `arduino`, `audio` or `hid`, matching the wiring calls on respective inputs.

For each reading:

* Updates its aggregated derivative calculation (details in the code docstrings and comments).
* Depending on the calculated value and level thresholds, calls `wiring.change_play_level` to trigger video playing level changes.
* Always calls `wiring.agd_output` with the current raw reading and calculated aggregated derivative (these will be used by the web interface).

About the thresholds:

* Initially sourced from the settings file.
* Can be monitored and changed at run-time:
* Handles `wiring.request_agd_thresholds` calls, responded to with `wiring.notify_agd_threshold` calls containing the current threshold levels.
* Handles `wiring.set_agd_threshold` calls that change a given level's threshold.

The `inputs.web` package
------------------------

This input is a hybrid thing:

* Acts as an input, able to trigger video playing level changes.
* Acts as a monitoring tool, displaying input readings in a chart, AGD thresholds and logs.
* Supports changing AGD thresholds and setting log levels at run-time.

It is also hybrid in the sense that part of it is written in Python while the web client side code is written in JavaScript.

Overview:

* Web clients request the root resource.
* `index.html` is served including the HTML layout with embedded CSS, containing links to:
* A bundled version of the [charts.js](https://www.chartjs.org) JavaScript compressed and minified code.
* A bundled version of the [chartjs-plugin-annotation.js](https://github.com/chartjs/chartjs-plugin-annotation) JavaScript compressed and minified code.
* `index.js`, the JavaScript web client code.
* Once loaded, the client initiates a WebSocket connection to the server.

The WebSocket connection is used for bidirectional communication:

* Server to client pushes:
* Log messages, depending on the log level settings.
* Input sensor readings and AGD values, to be displayed in the chart.
* AGD threshold levels, to be displayed in the chart.

* Client to server change requests:
* Video playing level.
* AGD thresholds.
* Log levels.

WebSocket messages are single JSON objects:

* Server to client pushes:
* The `type` property indicates the type of push.
* Other properties hold the pushed data.

* Client to server requests:
* The `action` property indicates the nature of the request.
* Other properties hold request data.

Server side WebSocket connection life-cycle:

* At WebSocket connection establishment time, the server side protocol instance:
* Adds itself as a Twisted logging observer, to push log messages to the client.
* Sets itself to handle `wiring.agd_output` calls, to push raw readings and AGD values to the client.
* Sets itself to handle `wiring.notify_agd_threshold` calls, to push AGD threshold values to the client.
* Calls `wiring.request_agd_thresholds` asking AGD to notify about the current thresholds.

* For each WebSocket received message:
* Video playing level change requests call `wiring.change_play_level`.
* AGD threshold change requests call `wiring.set_agd_threshold`.
* Log level change requests call `wiring.set_log_level`.

* At WebSocket disconnection time, the server side protocol instance:
* Removes itself as a Twisted logging observer.
* Stops handling `wiring.agd_output` calls.
* Stops handling `wiring.notify_agd_threshold` calls.

For more details refer to the included docstrings and comments in either Python or JavaScript code.

The `inputs.network` package
----------------------------

This input is a primitive, yet flexible way of controlling the video playing level.

At start time it listens for TCP connections on the configured network interface/port.

For each CRLF terminated message:

* Parses it as an ASCII integer.
* Calls `wiring.change_play_level` if a valid integer was obtained.

Odds and Ends
-------------

### Wires

[Wires](https://pypi.python.org/pypi/wires) is used as a simple, callable-based, event/notification system to simplify cross-component communication, while striving for very loose coupling between them. It originated in the context of this project, having just recently started a life of its own.

With Wires, cross-component communication of arbitrary complexity and topology is made simple, including changing it at run-time.

### Code linting

The code was continuously linted with:
```
$ pylint candle2017 common/ inputs/ player/ log/
```

No efforts were made to address or mute some of `pylint`'s errors, warnings or recommendations. In particular:

* The `Catching too general exception Exception` warnings are on code that really needs to catch broad exceptions.
* The `Instance of '' has no 'factory' member` errors result from a `pylint` limitation with regards to the dynamic `factory` attribute added by Twisted to protocol instances.
* The `Method name "" doesn't conform to snake_case naming style` convention recommendation results from the fact that Twisted predates PEP-8.

The remaining `pylint` (very minor) outputs could be addressed; but weren't. Linting output was taken as useful advise, not as *strict law*.

More
----

Please refer to [Candle 2017](README.md) for general project information and references to other documents.