From 6a4a902dbd666166ad6f23e690a8db30c33dd1fe Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Thu, 28 Jan 2021 20:59:57 +0100 Subject: [PATCH] Migrated 5th article --- static/img/arduino-1.gif | Bin 0 -> 18569 bytes ...rver-with-Platypush-Mopidy-and-Snapcast.md | 190 ++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 static/img/arduino-1.gif diff --git a/static/img/arduino-1.gif b/static/img/arduino-1.gif new file mode 100644 index 0000000000000000000000000000000000000000..a869aa960a175d7b9b395113dff04bb3be9ba7f0 GIT binary patch literal 18569 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 MArFa2H3k6yI|z>rkpKVy literal 0 HcmV?d00001 diff --git a/static/pages/Build-your-open-source-multi-room-and-multi-provider-sound-server-with-Platypush-Mopidy-and-Snapcast.md b/static/pages/Build-your-open-source-multi-room-and-multi-provider-sound-server-with-Platypush-Mopidy-and-Snapcast.md index 0244edf..7489db3 100644 --- a/static/pages/Build-your-open-source-multi-room-and-multi-provider-sound-server-with-Platypush-Mopidy-and-Snapcast.md +++ b/static/pages/Build-your-open-source-multi-room-and-multi-provider-sound-server-with-Platypush-Mopidy-and-Snapcast.md @@ -314,3 +314,193 @@ If you use Iris as a web interface to Mopidy you can now head to settings and en will appear in the bottom bar, and you’ll be able to control your music setup from there as well. Time to enjoy your low-cost but powerful multi-room music setup! + +## Build your remote to control the music + +All of us have some unused infrared remote collecting dust somewhere in the living room. In this section I’ll show how +to turn it into a universal remote for controlling your music (and not only) with some Platypush automation. You’ll need +the following: + +- An infrared receiver — they’re usually very cheap. Any of them will do, even though I personally + used [this model](https://www.banggood.in/Universal-IR-Infrared-Receiver-Head-With-Iron-Shell-TL1838-VS1838B-1838-38Khz-p-1204379.html?p=1L111111347088201706). + +- An Arduino or Arduino-compatible device (or an ESP8266, or a RaspberryPi Pico, or any other microcontroller, although + the code may be different). Most of the infrared sensors around communicate over an analog interface, but + the RaspberryPi doesn’t come with an ADC converter. The solution is to plug an Arduino over USB and let it monitor for + changes on the detected infrared signal. + +- A breadboard. + +Once you’ve got all the hardware you can set up your receiver: + +- Plug the infrared receiver to GND and Vcc, and the data PIN to e.g. the Arduino PIN 2, as shown in the figure below: + +![Arduino IR sensor connection](../img/arduino-1.gif) + +- Download and install the Arduino [IRremote library](https://github.com/z3t0/Arduino-IRremote). + +- Prepare a sketch that reads the data from the infrared receiver PIN and writes it over serial interface as a JSON: + +```c +#include + +// When a signal with all bits set to 1 is received it +// usually means that the previous pressed key is still +// being pressed, until a signal with all bits set to +// zero is received. +#define IR_REPEAT 0xFFFFFFFF + +const int RECV_PIN = 2; + +IRrecv irrecv(RECV_PIN); +decode_results results; +unsigned int latest_value = 0; + +void setup(){ + Serial.begin(9600); + irrecv.enableIRIn(); + irrecv.blink13(true); +} + +void send_value(unsigned int value) { + Serial.print("{\"ir\":"); + Serial.print(value, HEX); + Serial.println("}"); +} + +void loop(){ + if (irrecv.decode(&results)){ + if (results.value == IR_REPEAT && latest_value != 0) { + send_value(latest_value); + } else if (results.value && results.value != latest_value) { + send_value(results.value); + } + + latest_value = results.value; + irrecv.resume(); + } +} +``` + +- Compile the sketch and upload it to the Arduino. + +- Open the Arduino serial monitor and verify that you see the JSON string when you press a key on the remote. + +- Enable the serial plugin and backend in your platypush configuration: + +```yaml +serial: + device: /dev/ttyUSB0 + +backend.sensor.serial: + enabled: True +``` + +- Restart platypush, check the output and press a key on your remote. You should see an event in the logs that looks + like this: + +``` +INFO|platypush|Received event: {"type": "event", "target": "hostname", "origin": "hostname", "args": {"type": "platypush.message.event.sensor.SensorDataChangeEvent", "data": {"ir": "4b34d827"}}} +``` + +- Take note of the hexadecimal code reported on the event, that’s the decoded data associated to that specific remote + button. Then add an event hook to deal with the actions to be run when a certain button is pressed: + +```python +from platypush.config import Config +from platypush.event.hook import hook +from platypush.utils import run + +from platypush.message.event.sensor import SensorDataChangeEvent + +@hook(SensorDataChangeEvent) +def on_remote_key_press(event, **context): + ir_code = event.data.get('ir') + if not ir_code: + return + + # Playback control logic + if ir_code == 'code1': + run('music.mpd.play') + elif ir_code == 'code2': + run('music.mpd.pause') + elif ir_code == 'code3': + run('music.mpd.stop') + elif ir_code == 'code5': + run('music.mpd.previous') + elif ir_code == 'code6': + run('music.mpd.next') + # ... + # Multi-room setup logic + elif ir_code == 'code7': + # Un-mute the stream to another host + run('music.snapcast.mute', host=Config.get('device_id'), client='some-client', + mute=False) + elif ir_code == 'code8': + # Mute the stream to another host + run('music.snapcast.mute', host=Config.get('device_id'), client='some-client', + mute=True) +``` + +Congratulations, you’ve just built your own customizable and universal music remote! + +## Voice assistant integration + +A smart music setup isn’t really complete without a voice assistant integration. I’ve covered +[in a previous article](https://blog.platypush.tech/article/Build-your-customizable-voice-assistant-with-Platypush) how +to set up platypush to turn your device into a full-featured Google Assistant. If you’ve managed to get your +assistant up and running, you can add some rules to control your music, play specific content, or synchronize your audio +stream to another room. Let’s see a couple of examples: + +```python +from platypush.config import Config +from platypush.event.hook import hook +from platypush.utils import run + +from platypush.message.event.assistant import SpeechRecognizedEvent + +@hook(SpeechRecognizedEvent, phrase='play (the)? music') +def on_music_play(*args, **context): + run('music.mpd.play') + +@hook(SpeechRecognizedEvent, phrase='stop (the)? music') +def on_music_pause(*args, **context): + run('music.mpd.stop') + +@hook(SpeechRecognizedEvent, phrase='play (the)? radio') +def on_play_radio(*args, **context): + run('music.mpd.play', resource='tunein:station:s13606') + +@hook(SpeechRecognizedEvent, phrase='play playlist ${name}') +def on_play_playlist(event, name=None, **context): + run('music.mpd.load', resource=name) + +@hook(SpeechRecognizedEvent, phrase='play ${title} by ${artist}') +def search_and_play_song(event, title=None, artist=None, **context): + results = run('music.mpd.search', artist=artist, title=title) + if results > 0: + run('music.mpd.play', resource=results[0]['file']) + +@hook(SpeechRecognizedEvent, phrase='play (the)? music to (the)? bedroom') +def sync_music_to_bedroom(event, **context): + run('music.snapcast.mute', host=Config.get('device_id'), client='bedroom', mute=False) + run('music.snapcast.volume', host=Config.get('device_id'), client='bedroom', volume=90) +``` + +## Conclusions + +The current situation when it comes to music streaming and multi-room setup in a home automation environment is still +extremely fragmented. Each commercial solution out there seems more interested in building its own walled garden, and a +proper multi-room setup usually comes with high costs and in most of the cases it won’t be compatible with your existing +speakers. With the ingredients provided in this article you should be able to walk around most of these limitations and: + +- Set up your multi-service music player controllable by any interface you like + +- Set up your multi-room configuration that makes it possible to add a new room by simply adding one more RaspberryPi + +- Use any existing infrared remote to control the music + +- Integrate custom music actions into a voice assistant + +With these foundations in place the only limit to what you can do with your new music set up comes from your own +imagination!