Vzdálené hardwarové ovládání hlasitosti s Raspberry Pi

V tomto projektu představím, jak lze pomocí potenciometru ovládat hlasitost Raspberry Pi a jak lze pomocí internetu nastavovat hlasitost počítači s Windows přes internetové protokoly. Projekt je programován v Pythonu a VB.NET, využívá analogově digitální převodník a pro komunikaci přes internet využívá protokol TCP.

První část projektu ukáže, jak ovládat hlasitost Raspberry Pi a druhá část (IoT varianta) ukáže, jak s pomocí potenciometru ovládat hlasitost vzdáleného počítače s Windows. Osobně využívám druhou variantu, protože Raspberry Pi nepoužívám jako primární PC, ale jako menší server pro různé (zejména mnou naprogramované) služby, nicméně i tak si myslím, že první varianta může být pro někoho užitečná.

Zapojení

Zapojení je jednoduché, je třeba propojit sběrnici SPI s ADC. Zvolil jsem MCP3208, lze zvolit obdoby. Zapojení MCP, ADC a jeho popis je v mém jiném projektu „Ohmmetr“, pokud Vás to zajímá, mrkněte tam. Potenciometr se zapojí „bočními“ vývody na zem a 3.3V, prostřední vývod na nultý kanál ADC.

Část 1 – nastavování hlasitosti Raspberry Pi

Program v pythonu inicializuje knihovny, ADC převodník, a uloží si pomocnou hodnotu s procentuální úrovní potenciometru. V nekonečném cyklu pak budeme sledovat úroveň a pokud se změnila, spustíme příkaz amixer s vhodnými parametry, který nastaví hlasitost Raspberry Pi. Příkaz se volá s parametrem -M, který zajistí relativně lineární a přirozený posun následovaný parametrem set 'PCM' hlasitost%. Místo PCM může být i něco jiného. PCM je pro zvuk z HDMI. Možnosti závisí na konfiguraci Raspberry Pi a lze je získat pomocí příkazu amixer (bez parametrů), který vypíše název (text v uvozovkách).

Například příkaz amixer -M set 'PCM' 75% nastaví hlasitost na 75%. Kód programu vypadá následovně.

from gpiozero import MCP3208
from time import sleep
import subprocess

# ADC převodník
mcp = MCP3208(channel=0)
# aktuální úroveň hlasitosti. Výchozí hodnota musí být přepočítaná, takže tu musí být hodnota menší než -3 nebo větší než 103
current = -10

while True:
    # měření hodnoty 
    value = round(100 * mcp.value) # jednotka jsou procenta
    #print(str(value) + " current=" + str(current)) # logování, pro zapnutí odkomentujte
    sleep(0.1) # šetření procesoru. V tomto čase ho náš program nebude vytěžovat.
    if value != current:
        # uložení hodnoty pro budoucí porovnávání
        current = value
        # sestavení příkazu, který akci provede
        command = ["amixer", "-M", "set", "'PCM'", str(current) + "%"]
        # spuštění procesu pro nastavení hlasitosti
        proc = subprocess.Popen(command, stdout=subprocess.PIPE)
        # zachycení výstupu
        (out, err) = proc.communicate()

Část 2 – nastavování hlasitosti vzdálenému PC s Windows

Následující program musí mít 2 části. Server, což je PC s Windows a klient, což je Raspberry Pi. Na Windows je server napsaný v VB.NET a vyžaduje stažení knihovny AudioSwitcher.AudioApi.CoreAudio, kterou lze stáhnout s pomocí instalátoru závislostí NuGet. Ve Visual Studiu klikněte pravým na projekt, zvolte Manage NuGet Packages…, pak se přepněte na kartu Browse, vyhledejte tuto knihovnu a nechte ji nainstalovat.

Úvod do pokročilého IoT – Práce se sockety

Pokud jste již něco dělali s IoT dost možná to bylo nějaké http API nebo jste něco stahovali z internetu nebo jste posílali nějaký GET či POST požadavek či něco obdobného. Většina tutoriálu totiž učí IoT jen a pouze přes http, protože je to prostě nejjednodušší. Ničemně http jakožto univerzální protokol řeší hromady věcí okolo, které většina vývojářů ani zdaleka nepotřebuje/nevyužije/nezná a přenášejí se tak úplně zbytečně. Navíc tento protokol ani zdaleka nebyl pro IoT navržen. Přestože by se mohlo zdát, že těch pár bajtů nikoho nezabije, spočítejte si kolik dat se však zbytečně přenese na http hlavičkách a věcech okolo za určitou dobu, pokud to Vaše aplikace dělá nějak pravidelně nebo tyto informace přenáší více zařízení. Zjistíte že z malých čísel (desítky až stovky) se stanou tísíce až statisíce a to pouze v omezeném čase. Představte si, že Vaše aplikace poběží někde několik let a nebude data posílat jedno zařízení, ale několik stovek. Fungovat to bude, ale zbytečně vytížíte kapacity síťových linek, které mohly být využity efektivněji. Abychom si ukázali, jak se to dělá nízkoúrovňově, návrhem si svůj jednoduchý protokol, tak jako návrháři http navrhovali ho. Stejně jako http postavíme ho nad TCP. Existuje ještě UDP a pár dalších. UDP je jednoduší a šetří ještě více dat, ale ten negarantuje doručení dat ani doručení dat ve správném pořadí ani hromadu dalších věcí, které TCP řeší a nám se budou hodit. Protokol bude jednoduchý, klient v něm bude posílat byty při změně úrovně hlasitosti (to již máme vyřešené v první části) a server bude posílat pravidelně 0 aby klient věděl, že server žije, případně kvůli NATům, které neaktivní připojení občas zavírají při neaktivitě.

Komunikace vyšší 5,6,7 síťové vrstvy (pro pochopení síťových vrstev si vyhledejte na wikipedii OSI model) se 4 síťovou vrstvou (TCP), kterou již zajišťuje operační systém se využívá takzvaných socketů. Sockety mohou být několika typů, nám bude stačit sockety typu STREAM a fakt že funguje nad internetovým protokolem IP. Sockety jsou však využívaný i u úplně odlišných protokolů, které s internetem nikdy nebyli spjaty, příkladem je Bluetoth. Po vytvoření socketu Ať už v VB.NET nebo pythonu (budeme je dělat na obou platformách) je podstatných 5 metod - Bind, Listen a Connect, Accept, Send.

Bind použijeme na serveru a říká operačnímu systému že určitý TCP nebo UDP (v našem případě TCP) port si chceme vyhradit pro sebe. Krom portu se ještě udává IP adresa síťového zařízení, na kterém chceme poslouchat. Lze použít univerzální adresu 0.0.0.0, která zajistí poslech na všech síťových kartách.

Listen použijeme na serveru říká operačnímu systému, že chceme dedikovat nějakou frontu a čekat klienty v této frontě. Velikost fronty se udává jako parametr této metody.

Connect se používá na klientovi a slouží k sestavení spojení s vzdáleným serverem. Adresa a vzdálený port se obvykle předává jako parametr. Základní kód serveru a klienta bude vypadat následovně.

Metoda Accept slouží na serveru k přijmutí klienta.

Send odešle pole bytů na druhou stranu připojení.

Server - VB.NET

Dim sock As Sockets.Socket = New Socket(SocketType.Stream, ProtocolType.IP)
sock.Bind(New IPEndPoint(IPAddress.Parse("0.0.0.0"), 1234))
sock.Listen(1)
While True 
	Dim client = sock.Accept()
	' čekání a spracovávání dat přijatých od klienta ... 
End While

Klient – Python

clientsocket = socket.socket()
clientsocket.settimeout(10)
clientsocket.connect(("192.168.0.130", 1234))

while True:
    # měření hodnoty ...
    if value != current: # změna hdonoty
        clientsocket.send(bytes([value]))

A teď už zbývá toto jenom napasovat do měření hodnoty a nastavování hlasitosti na serveru. Nastavování hlasitosti ve Windows zajistí tyto dva řádky. Ukázka nastaví hlasitost na 50%.

Dim speaker = (New CoreAudioController()).DefaultPlaybackDevice
speaker.Volume = 50

Při práci se sítí je třeba ještě ošetřit zejména nečekané stavy a odpojení klienta. Vše by mělo již být pochopitelné z kódu.

Kompletní kód serveru (VB.NET)

Imports System.Net
Imports System.Net.Sockets
Imports System.Threading
Imports AudioSwitcher.AudioApi.CoreAudio

Module Module1

	Sub Main()
		' ovladač hlasitosti
		Dim speaker = (New CoreAudioController()).DefaultPlaybackDevice

		' TCP server
		Dim sock As Sockets.Socket = New Socket(SocketType.Stream, ProtocolType.IP)
		sock.Bind(New IPEndPoint(IPAddress.Parse("0.0.0.0"), 1234))
		sock.Listen(1)

		' čekání a obsluhování klientů
		While True
			Dim client = sock.Accept()
			Console.WriteLine("Klient připojen. Linka obsazena.")
			' vyjimka může nastat při pádu nebo odpojení klienta. V obou dvou případech se vrátíme čekat na dalšího klienta.
			Try
				Dim buffer(100) As Byte

				' čekání a spracovávání dat přijatých od klienta
				While True
					If client.Available > 0 Then
						' čtení poslední přijaté hodnoty
						Dim received = client.Receive(buffer)
						If received > 0 Then
							Dim val = buffer(received - 1)
							If val >= 0 And val <= 100 Then
								' nastavování hlasitosti
								Console.WriteLine("Volume is set " & val)
								speaker.Volume = val
							End If
						End If
					Else
						' data nepřišla. Kontrola, že klient žije.
						If client.Poll(50, Sockets.SelectMode.SelectRead) And client.Available = 0 Then
							client.Close()
							Throw New Exception()
						End If
						client.Send(New Byte() {0})

						' je čas jít spát
						Thread.Sleep(10)
					End If
				End While

			Catch ex As Exception
				Console.WriteLine("Klient odpojen. Linka je volná.")
			End Try
		End While

	End Sub

End Module

Kompletní kód klienta (Python)

from gpiozero import MCP3208
from time import sleep
import subprocess
import socket
import sys

clientsocket = socket.socket()
clientsocket.settimeout(10)
clientsocket.connect(("192.168.0.130", 1234))

# ADC převodník
mcp = MCP3208(channel=0)
# aktuální úroveň hlasitosti. Výchozí hodnota musí být přepočítaná, takže tu musí být hodnota menší než -3 nebo větší než 103
current = -10

while True:
    # měření hodnoty 
    value = round(100 * mcp.value) # jednotka jsou procenta
    #print(str(value) + " current=" + str(current)) # logování, pro zapnutí odkomentujte
    sleep(0.1) # šetření procesoru. V tomto čase ho náš program nebude vytěžovat.
    if value != current: # +-3 je tolerance kolísajících hodnot.
        # uložení hodnoty pro budoucí porovnávání
        current = value
        # zaslání dat
        clientsocket.send(bytes([current]))

Běh na pozadí a po spuštění počítače

Server v pythonu

Běh serveru v pythonu po spuštění Raspberry Pi lze jednoduše zajistit přidáním řádku příkazu následovaného ampersandem do souboru /etc/rc.local. Řádek musíte přidat před příkaz exit a měl by vypadat cca následovně.

python3 /cesta/ke/skriptu.py &

Na ampersand na konci nesmíte zapomenout, pak by se Vám zaseklo bootování Raspberry Pi.

Klient v VB.NET

Základem je aby klient neměl viditelné konzolové okno. To lze zajistit přepnutí projektu na formulářovou aplikaci. Lze to udělat ve vlastnostech projektu (Nabídka Project > Properties) a na kartě Applications zvolit v nabídce Application Type jako typ aplikace Windows Forms Application. Zajistit aby se aplikace spustila po spuštění PC lze několika způsoby. Jeden z nejjednodušších je plánovač úloh. Otevřete je vyhledáním v nabídce start v českých Windows pod pojmem Plánovač úloh. V pravém panelu zvolíte Vytvořit úlohu, vyberete že chcete úlohu konfigurovat pro Windows 10 (pokud máte jinou verzi Windows, tak vyberte tu co máte), na kartě Aktivační události přidáte přihlášení uživatele a na kartě Akce přidáte akci spustit program a nastavíte tam cestu k vašemu programu.

Závěr

Viděli jste aplikaci, která komunikuje přes síť, neposílá zbytečná data, viděli jste sestavení socketu a komunikaci přes síť. Výsledkem je potenciometr, pro ovládání hlasitosti vzdáleného počítače řízený pomoci Raspberry Pi.

Zdrojové kódy můžete stáhnout zde.

Použité součástky

Další podobné články

Ovládání teploty pomocí relé a senzoru DS18B20

Tento projekt umožňuje měřit teplotu pomocí senzoru DS18B20, zobrazit ji na I2C LCD displeji, a ovládat relé podle teplotních limitů. Relé bude aktivní, když je teplota mezi 0 a 22 stupni Celsia. Projekt může sloužit k ovládání topení, ventilace nebo jiného zařízení podle okolní teploty.