Toon posts:

Android app met raspberry pi two way communication

Pagina: 1
Acties:

Vraag


  • Floris Jan
  • Registratie: Juli 2012
  • Laatst online: 17-01 18:27
Inleiding: ik wil mijn analoge audio versterker upgraden met een remote control. Ik moet de potmeter voor het volume bijvoorbeeld vervangen door een digitale potmeter en nog iets verzinnen voor het selecteren van een kanaal. Een andere optie zou een pre-amp zijn met input selectie en volume control zodat ik niet aan mijn versterker hoef te klooien. De hardware krijg ik wel voor elkaar en is niet het topic wat ik hier wil bespreken. Maar ik wil de focus houden op de software kant gezien ik heb weinig tot geen ervaring met programmeren. Alleen op het werk gebruik ik regelmatig Matlab en Python.

Doel: ik wil een android app maken als remote control. Om simpel te beginnen wil in de audio versterker aan en uit zetten met een Hue schakelaar. Ik heb een Hue bridge, een raspberry pi met web server. Daar heb ik al een beetje mee gespeeld ik kan met een python scriptje de Hue schakelaar bedienen vanaf de raspberry pi.

Mijn grote vraag is dus: hoe kan ik met een android app interfacen met de raspberry pi? Ik vermoed dat ik nog wat meer moet draaien dan alleen een web server of wellicht die hele web server niet eens nodig heb.

Wat ik al geprobeerd heb: ik kan met een appje een txt file van de web server halen. En ik kan met python een txt file aan maken met daarin true of false afhankelijk of de Hue schakelaar aan staan. Daarna heb ik in Android Studio iets gemaakt met een Switch (zo'n android knopje om iets aan of uit te zetten) en de stand van de Switch is afhankelijk van de content van het txt bestandje op de web server. Dit werkt bijzonder matig: 1) ik kan alleen van de webserver naar de app communiceren en niet terug 2) als ik het bestandje op de webserver aanpas crashed de app (blijkbaar kan maar 1 persoon tegelijk het txt bestandje openen). In eerste instantie zou ik zeggen dat ik dit helemaal niet met een webserver wil doen, ik hoef immers niet mijn audio versterker te bedienen als ik niet thuis ben. Als het alleen op mijn locale netwerk werkt dan maakt het dat een stuk makkelijker qua beveiliging gezien er dan geen beveiliging nodig is. Ik heb echt geen benul hoe ik dit dan wel moet aanpakken.

Alle reacties


  • Floris Jan
  • Registratie: Juli 2012
  • Laatst online: 17-01 18:27
Ik heb iets voor elkaar gekregen met flask aan de server kant. Het ging vooral fout met Android studio, wat een gedrocht is dat met Java. Het is nog steeds erg lelijk wat ik doe, de app leest een txt bestandje van de server via de normale poort 80. En de app stuurt commando's terug naar de server via poort 5000. Daar draait een python script met flask dat ook direct het txt bestandje schrijft voor de communicatie naar de app. Alles via poort 5000 zou wat netter zijn gezien ik poort 80 open heb staan op de router en de hele wereld dus mee kan lezen. Maar goed, morgen weer een dag.

  • Boudewijn
  • Registratie: Februari 2004
  • Niet online

Boudewijn

omdat het kan

Waarom een file? Een REST API lijkt me handiger? En de poort waar het geheel op draait moet niet uitmaken.

Ik ben verslaafd aan koken. Volg me op https://www.kookjunk.nl


  • Skamba
  • Registratie: Mei 2006
  • Laatst online: 23:41
Je zou eens kunnen kijken naar ESPHome met bijvoorbeeld een D1 mini kunnen kijken. Die heeft ook een web server component. Dat is allemaal een stuk simpeler dan de oplossingsrichting waar je nu naar toe lijkt te gaan.

  • Floris Jan
  • Registratie: Juli 2012
  • Laatst online: 17-01 18:27
Boudewijn schreef op donderdag 5 januari 2023 @ 03:01:
Waarom een file? Een REST API lijkt me handiger? En de poort waar het geheel op draait moet niet uitmaken.
Omdat ik geen idee heb waar ik zou moeten beginnen, ik had nog nooit van REST gehoord. Ik ben allang blij dat ik iets werkend heb gekregen. Maar ik denk dat REST inderdaad de nette manier zou zijn. Bedankt!
Skamba schreef op donderdag 5 januari 2023 @ 05:57:
Je zou eens kunnen kijken naar ESPHome met bijvoorbeeld een D1 mini kunnen kijken. Die heeft ook een web server component. Dat is allemaal een stuk simpeler dan de oplossingsrichting waar je nu naar toe lijkt te gaan.
Dat kende ik helemaal niet. En dat lijk inderdaad een stuk makkelijker. Wellicht nuttig voor een toekomstig project, bedankt voor de tip.

  • Floris Jan
  • Registratie: Juli 2012
  • Laatst online: 17-01 18:27
Het is uiteindelijk gelukt en heb inderdaad een soort van REST protocol gebruikt in plaats van een text bestand over te sturen. Hieronder alle details, misschien dat het nog nuttig kan zijn voor het geval iemand wat vergelijkbaars wil doen.

Op de raspberry pi draait een flask server met python code. Als voorbeeld heb ik hier de volume control. Het volume is een waarde tussen 0 en 100 in dit voorbeeld. De server ontvangt data en schrijft die weg naar txt bestand en wanneer je de getVolume aanroept leest hij de waarde uit het txt bestand en geeft hij deze waarde als reply (de reply is geen txt bestand). Het txt bestand moet in de toekomst nog vervangen worden door waardes te zetten op de GPIO uitgang van de raspberry pi. Maar de hardware om het volume daadwerkelijk aan te passen mist nog.

Python:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from flask import Flask
import logging

app = Flask(__name__)

@app.route('/remote/setVolume/<status>')
def setVolume(status):
    #dummy due to lack of functional hardware
    f = open("/home/pi/Documents/remote/volume", "w")
    f.write(status)
    f.close()
    return status
    
@app.route('/remote/getVolume')
def getVolume():
    #dummy due to lack of functional hardware
    f = open("/home/pi/Documents/remote/volume", "r")
    status = f.readline()
    f.close()
    return status
    
if __name__ == '__main__':
    app.run(host='0.0.0.0', debug = True)


In de app draait de volgende code (hier had ik de meeste moeite mee om het werkend te krijgen). Hiervoor heb ik Android Studio gebruikt. Dit is alleen de code van MainActivity.kt. Ik heb nog wat met binding gedaan zodat je automatisch de content van main_activity.xml kan gebruiken in MainActivity.kt. Voor details over binding zie https://developer.android.com/topic/libraries/view-binding en https://androidforums.com...tup.1345596/#post-8094147. En je moet nog wat extras doen om http (in plaats van https) urls aan te vragen. Gezien dit alleen op mijn locale netwerk werkt heb ik verder geen beveiliging toegevoegd.

Kotlin:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package com.example.remote

import android.os.Bundle
import android.widget.SeekBar
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.example.remote.databinding.ActivityMainBinding
import java.net.HttpURLConnection
import java.net.URL
import java.util.concurrent.LinkedBlockingQueue


class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //setContentView(R.layout.activity_main)
        binding = ActivityMainBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)

        val passOn = LinkedBlockingQueue<String>() // used to get data from one to another thread

        //requests URL and returns reply
        fun callURL(url:String):String{
            Thread{ // seperate for calling URLs is mandatory
                //start of URL is defined here
                val urlFull = "http://192.168.1.10:5000".plus(url)
                try {
                    //HttpURLConnection required to be able to set timeout
                    val url = URL(urlFull).openConnection() as HttpURLConnection
                    url.connectTimeout = 100 //value in milliseconds
                    url.readTimeout = 100 //value in milliseconds
                    val reply = url.inputStream.bufferedReader().readText()
                    url.disconnect()
                    passOn.add(reply)
                }
                catch(e: Exception) {
                    passOn.add("error")
                }
            }.start()
            return passOn.take()
        }

        fun setVolume(){
            var url = "/remote/setVolume/"
            // read seekBar status
            val volume = binding.seekBarVolumeSlider.progress.toString()
            url = url.plus(volume)
            // execute
            callURL(url)
        }

        fun getVolume(){
            val url = "/remote/getVolume"
            // execute
            var reply = callURL(url)
            if (reply=="error"){
                reply = "0" //set volume to zero in case of no connection
                val text = "No connection with server"
                val duration = Toast.LENGTH_SHORT
                //show "No connection with server" to user
                Toast.makeText(applicationContext, text, duration).show()             }
            // change seekbar value
            binding.seekBarVolumeSlider.progress = reply.toInt()
        }

        //execute at start
        getVolume()// sync UI with server

        binding.seekBarVolumeSlider.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
            override fun onProgressChanged(volumeSlider: SeekBar, progress: Int, fromUser: Boolean) {}
            override fun onStartTrackingTouch(volumeSlider: SeekBar) {}
            override fun onStopTrackingTouch(volumeSlider: SeekBar) {
                setVolume()
                getVolume()
            }
        })

    }

}


Voor de volledigheid ook nog de XML file (activity_main.xml), al kan je die ook in elkaar klikken zonder te coden:

XML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <SeekBar
        android:id="@+id/seekBarVolumeSlider"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="96dp"
        android:layout_marginEnd="96dp"
        android:max="100"
        android:progress="0"
        android:scaleX="1.5"
        android:scaleY="1.5"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        />

    <TextView
        android:id="@+id/textViewVolume"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="12dp"
        android:text="Volume"
        app:layout_constraintBottom_toTopOf="@+id/seekBarVolumeSlider"
        app:layout_constraintEnd_toEndOf="@+id/seekBarVolumeSlider"
        app:layout_constraintStart_toStartOf="@+id/seekBarVolumeSlider" />

</androidx.constraintlayout.widget.ConstraintLayout>


En voor de volledigheid nog een screenshot van de app zelf

[Voor 3% gewijzigd door Floris Jan op 09-01-2023 19:51]

Pagina: 1


Tweakers maakt gebruik van cookies

Tweakers plaatst functionele en analytische cookies voor het functioneren van de website en het verbeteren van de website-ervaring. Deze cookies zijn noodzakelijk. Om op Tweakers relevantere advertenties te tonen en om ingesloten content van derden te tonen (bijvoorbeeld video's), vragen we je toestemming. Via ingesloten content kunnen derde partijen diensten leveren en verbeteren, bezoekersstatistieken bijhouden, gepersonaliseerde content tonen, gerichte advertenties tonen en gebruikersprofielen opbouwen. Hiervoor worden apparaatgegevens, IP-adres, geolocatie en surfgedrag vastgelegd.

Meer informatie vind je in ons cookiebeleid.

Sluiten

Toestemming beheren

Hieronder kun je per doeleinde of partij toestemming geven of intrekken. Meer informatie vind je in ons cookiebeleid.

Functioneel en analytisch

Deze cookies zijn noodzakelijk voor het functioneren van de website en het verbeteren van de website-ervaring. Klik op het informatie-icoon voor meer informatie. Meer details

janee

    Relevantere advertenties

    Dit beperkt het aantal keer dat dezelfde advertentie getoond wordt (frequency capping) en maakt het mogelijk om binnen Tweakers contextuele advertenties te tonen op basis van pagina's die je hebt bezocht. Meer details

    Tweakers genereert een willekeurige unieke code als identifier. Deze data wordt niet gedeeld met adverteerders of andere derde partijen en je kunt niet buiten Tweakers gevolgd worden. Indien je bent ingelogd, wordt deze identifier gekoppeld aan je account. Indien je niet bent ingelogd, wordt deze identifier gekoppeld aan je sessie die maximaal 4 maanden actief blijft. Je kunt deze toestemming te allen tijde intrekken.

    Ingesloten content van derden

    Deze cookies kunnen door derde partijen geplaatst worden via ingesloten content. Klik op het informatie-icoon voor meer informatie over de verwerkingsdoeleinden. Meer details

    janee