Peppy player

Hi MrGlasspoole, I see you made a good progress in the network area.
The whole functionality was developed for the Raspberry OS. I cannot guarantee that it will work on any other Linux distro.

Yes, Windows is my main development platform for the player. Usually I'm trying to make player working on both platforms Windows and Linux/Raspberry OS. For example wifiutil.py has functions for both. The whole UI/Web UI work was done on Windows so far.

I didn't expect so long passwords :) You can change the length here:
Peppy/keyboard.py at 87daa889969de9b0215731785318e903b136219e * project-owner/Peppy * GitHub
I should probably change that to 64.
 
Last edited:
I wonder why i have that rfkill and wpa_supplicant problems with a fresh image.

Is it normal that the "country" entry is gone in the wpa_supplicant.conf after I made a connection through Peppy?

The PLTS (Peppy Laboratory Test Station) :D
attachment.php


I can confirm that the 'Waveshare 3.5inch RPi LCD (A)' also works with your image without problems. Only the colors where not right and that was gone after installing the driver.

And i see now where that eth0 and wlan0 comes from.
You can enable/disable predictable network in the RPi OS.
If you change that setting you can adjust Peppy for newer systems:
NetworkInterfaceNames - Debian Wiki
 

Attachments

  • PLTS.jpg
    PLTS.jpg
    639.1 KB · Views: 426
You have pretty good PLTS :) I wish to start working with hardware as well. Right now the player software takes all my spare time :( I have 2-3 new hardware projects in my mind.

Yes, it looks like it's normal to have wpa_supplicant.conf file without 'country' after setting it initially. I believe it's also saved somewhere else.

I'm sorry, that page is TLDR. Can you tell me which parameter can be set? I can test it. Thank you!
 
I'm making progress.
I'm now slowly understand how that Python/Peppy stuff works.

I'm going through that wifiutil.py and the commands that file is executing.
The reason no WiFi Networks did show up was line 99.
I have to run ifconfig as sudo. So i changed it to:
Code:
n = subprocess.check_output(["sudo", "ifconfig"])
Then i realized that Peppy is overwriting the wpa_supplicant.conf
The reason 'country' is gone is because you don't set it :)
And there is the group missing and 'update_config=1'
You can read it also here: Setting up a wireless LAN via the command line - Raspberry Pi Documentation
Note that with the latest Buster Raspberry Pi OS release, you must ensure that the wpa_supplicant.conf file contains the following information at the top:

ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=<Insert 2 letter ISO 3166-1 country code here>
So i changed it to:
Code:
line1 = "ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev\\n"
line2 = "update_config=1\\n"
line3 = "country=DE\\n"
line4 = "network={\\n"
line5 = "ssid="" + ssid + ""\\n"
line6 = "psk=" + psk + "\\n}\\n"
content = "'" + line1 + line2 + line3 + line4 + line5 + line6 + "'"
I'm now at 'wpa_cli reconfigure' that makes problems and vanilla Debian uses 'dhclient' instead of 'dhcpcd'.

I don't know if restarting dhcpcd is necessary on the Pi.
In vanilla Debian 'sudo dhclient wlp1s0' is enough to get the IP.
 
Hi MrGlasspoole, yes the player overwrites wpa_supplicant.conf after each cahnge. I found that it's not actually required to have those lines which you mentioned. Therefore I don't preserve them.

From where do you want to copy a password?

I hope that at some point we will be able to collect all changes required to make the player working on Debian ;)
 
From the clipboard. Like you can normally paste text in every field on a website.
I know html/css but that is not how its done in Peppy. I just had a quick look...

I realized that if i disconnect from WiFi and go back that i have to enter the password again. I thought its my code change, but it happens also on the RPi with your image. I'm doing something wrong?

I did load RPi OS Lite on another RPi and 'ip' also works there.
So code that works in vanilla Debian should also work there.
wpa_cli does not work here. It seems to be a driver thing. Google is full with people (especially RPi) that have that problem that they get this:
Code:
Failed to connect to non-global ctrl_ifname: wlp1s0 error: No such file or directory
Well there is no need for wpa_cli.
I just do (the last two lines):
Code:
subprocess.call("sudo wpa_supplicant -i wlp1s0 -c /etc/wpa_supplicant/wpa_supplicant.conf -B", shell=True)
subprocess.call("sudo dhclient wlp1s0", shell=True)
I don't want the WiFi adapter to be on if not needed. Its off after booting.
So after line 299 i did insert:
Code:
subprocess.call("sudo ip link set wlp1s0 up", shell=True)
What i don't understand is how disconnect works:
Code:
    def disconnect_wifi_linux(self):
        """ Disconnect from Wi-Fi on Linux """

        self.create_wpa_file(None, None)
Is that running line 383 with nothing in it? Creating an empty wpa_supplicant.conf or something?

Can i just enter
Code:
subprocess.call("sudo dhclient -r wlp1s0", shell=True)
subprocess.call("sudo ip link set dev wlp1s0 down", shell=True)
there?

I would say it should work like on a Phone/Tab.
WiFi On/Off and if there is on in reach where the password is saved then connect to it.
It seems like at the moment you can have only one connection saved?

And here is my biggest problem at the moment: Getting rid of the deprecated ifconfig. I'm not good at regex or this stuff.

ifconfig looks like this
Code:
~$ sudo ifconfig
eno1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.1.0.184  netmask 255.255.255.0  broadcast 10.1.0.255
        inet6 fe80::225:90ff:feea:3cc4  prefixlen 64  scopeid 0x20<link>
        ether 00:25:90:ea:3c:c4  txqueuelen 1000  (Ethernet)
        RX packets 118065  bytes 75228979 (71.7 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 48686  bytes 34768405 (33.1 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        device memory 0x90900000-9097ffff

wlp1s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.1.0.249  netmask 255.255.255.0  broadcast 10.1.0.255
        inet6 fe80::16f6:d8ff:fe6b:10e8  prefixlen 64  scopeid 0x20<link>
        ether 14:f6:d8:6b:10:e8  txqueuelen 1000  (Ethernet)
        RX packets 23214  bytes 1470249 (1.4 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 227  bytes 46695 (45.6 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
and i understand how you extract the information on line 94 (get_linux_network).

But ip looks like this:
Code:
~$ ip a
2: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:25:90:ea:3c:c4 brd ff:ff:ff:ff:ff:ff
    inet 10.1.0.184/24 brd 10.1.0.255 scope global dynamic eno1
       valid_lft 1067sec preferred_lft 1067sec
    inet6 fe80::225:90ff:feea:3cc4/64 scope link
       valid_lft forever preferred_lft forever
3: eno2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 00:25:90:ea:3c:c5 brd ff:ff:ff:ff:ff:ff
4: wlp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 14:f6:d8:6b:10:e8 brd ff:ff:ff:ff:ff:ff
    inet 10.1.0.249/24 brd 10.1.0.255 scope global dynamic wlp1s0
       valid_lft 907sec preferred_lft 907sec
    inet6 fe80::16f6:d8ff:fe6b:10e8/64 scope link
       valid_lft forever preferred_lft forever
In your terminal you can do
Code:
ip -o addr show scope global | awk '{split($4, a, "/"); print $2" : "a[1]}'
But how do it in Python the right way?

There is also json if its better 'ip -j a'
 
Last edited:
Sorry, I'm a little bit busy today. I'll give you the detailed answers either later today or tomorrow. Regarding your main issue - I'll try to write the function which is using 'ip' instead of 'ifconfig' or you can practice in Python :). Json is much better. In this case you don't need to parse the strings.
 
That will take years for me to figure out :(
Took some hours just to figure this out:
Code:
# some JSON:
n = '[{"ifindex":2,"ifname":"eno1","operstate":"UP","address":"00:25:90:ea:3c:c4","addr_info":[{"family":"inet","local":"10.1.0.184","prefixlen":24,"broadcast":"10.1.0.255","scope":"global","dynamic":true,"label":"eno1","valid_life_time":1077,"preferred_life_time":1077},{"family":"inet6","local":"fe80::225:90ff:feea:3cc4","prefixlen":64,"scope":"link","valid_life_time":4294967295,"preferred_life_time":4294967295}]}]'

# parse n:
y = json.loads(n)

# the result:
for i in y:
    print(i['ifname'], i['operstate'], i['addr_info'][0]['local'])
But if i replace that JSON test line with:
Code:
n = subprocess.call("sudo ip -j a", shell=True)
i get
Code:
raise TypeError(f'the JSON object must be str, bytes or bytearray, '
TypeError: the JSON object must be str, bytes or bytearray, not int
At least its doable. And a Python-God like you will make it in 5 minutes :D
 
Regarding Wi-Fi password, I believe you are talking about Web UI. I thought you were talking about the touchscreen UI. So I was wondering from where you are going to copy a password in that case. The current player's Web UI design doesn't allow to do any copy/paste. Some time ago I was thinking about moving the whole Network configuration UI to the Configuration Web UI. But that breaks the main Wi-Fi network use case when you for example bring your player to your friend's house and need to configure Wi-Fi but you cannot do that from the Configuration Web UI because you don't have IP yet for connecting browser to the player. One trade-off can be to provide one UI in the touchscreen and another in the Configuration Web UI. In this case you could copy/paste password in the Configuration Web UI.

Yes, currently if you disconnect from Wi-Fi network you need to provide a password again next time when you connect.

Please provide the exact commands which can replace 'wpa_cli' on Pi. I will test them. I think something happens when you copy/paste commands.

Your point about Wi-Fi On/Off is arguable. If I connected to Wi-Fi then more likely I need that connection when I start the player next time. Though it can be configurable through some property in the config.txt. I believe the command was also screwed up during copy/paste. Please provide the command.

Yes, when you disconnect Wi-Fi the player just saves the empty wpa_supplicant.conf so that when you start it will not connect again. There is currently no Wi-Fi On/Off functionality. This is probably what you want. If so, please provide the commands which also work on Pi and I'll try to implement that.

Here is the function which is using 'ip' instead of 'ifconfig'. I'll add it in the next release.

Code:
[COLOR=#000000][FONT=Consolas][COLOR=#0000ff]    def[/COLOR][COLOR=#000000] get_linux_network(self):[/COLOR][/FONT][/COLOR]
[COLOR=#000000][FONT=Consolas][COLOR=#a31515]        """ Get Linux network info using 'ip' utility.[/COLOR][/FONT][/COLOR]
[COLOR=#000000][FONT=Consolas] [COLOR=#a31515]            If there many entries of the same type (eth or wlan) the function returns the last one.[/COLOR]
[/FONT][/COLOR]
[COLOR=#000000][FONT=Consolas][COLOR=#a31515]        :return: network info[/COLOR][/FONT][/COLOR]
[COLOR=#000000][FONT=Consolas][COLOR=#a31515]        """[/COLOR][/FONT][/COLOR]
[COLOR=#000000][FONT=Consolas][COLOR=#000000]        result = {}[/COLOR][/FONT][/COLOR]
[COLOR=#000000][FONT=Consolas][COLOR=#000000]        n = subprocess.check_output([[/COLOR][COLOR=#a31515]"ip"[/COLOR][COLOR=#000000], [/COLOR][COLOR=#a31515]"-j"[/COLOR][COLOR=#000000], [/COLOR][COLOR=#a31515]"a"[/COLOR][COLOR=#000000]])[/COLOR][/FONT][/COLOR]
[COLOR=#000000][FONT=Consolas][COLOR=#000000]        js = json.loads(n)[/COLOR][/FONT][/COLOR]

[COLOR=#000000][FONT=Consolas][COLOR=#0000ff]        for[/COLOR][COLOR=#000000] e [/COLOR][COLOR=#0000ff]in[/COLOR][COLOR=#000000] js:[/COLOR][/FONT][/COLOR]
[COLOR=#000000][FONT=Consolas][COLOR=#0000ff]            try[/COLOR][COLOR=#000000]:[/COLOR][/FONT][/COLOR]
[COLOR=#000000][FONT=Consolas][COLOR=#000000]                e[[/COLOR][COLOR=#a31515]"addr_info"[/COLOR][COLOR=#000000]]    [/COLOR][/FONT][/COLOR]
[COLOR=#000000][FONT=Consolas]            [COLOR=#0000ff]except[/COLOR][COLOR=#000000]:[/COLOR]
                [COLOR=#0000ff]continue [/COLOR][COLOR=#008000]# if no addr_info[/COLOR]
           [COLOR=#000000]inet_addr = [/COLOR][COLOR=#0000ff]None[/COLOR]
           [COLOR=#0000ff]try[/COLOR][COLOR=#000000]:[/COLOR]
               [COLOR=#0000ff]for[/COLOR][COLOR=#000000] a [/COLOR][COLOR=#0000ff]in[/COLOR][COLOR=#000000] e[[/COLOR][COLOR=#a31515]"addr_info"[/COLOR][COLOR=#000000]]:[/COLOR]
[/FONT][/COLOR][COLOR=#000000][FONT=Consolas]                    [COLOR=#0000ff]if[/COLOR][COLOR=#000000] a[[/COLOR][COLOR=#a31515]"family"[/COLOR][COLOR=#000000]] == [/COLOR][COLOR=#a31515]"inet"[/COLOR][COLOR=#000000]:[/COLOR][/FONT][/COLOR]
[COLOR=#000000][FONT=Consolas][COLOR=#000000]                        inet_addr = a[[/COLOR][COLOR=#a31515]"local"[/COLOR][COLOR=#000000]][/COLOR][/FONT][/COLOR]
[COLOR=#000000][FONT=Consolas]                        [COLOR=#0000ff]break[/COLOR]
[COLOR=#0000ff]            except[/COLOR][COLOR=#000000]:[/COLOR]
               [COLOR=#0000ff]pass[/COLOR]
[/FONT][/COLOR]
[COLOR=#000000][FONT=Consolas][COLOR=#0000ff]            if[/COLOR][COLOR=#000000] e[[/COLOR][COLOR=#a31515]"ifname"[/COLOR][COLOR=#000000]].startswith([/COLOR][COLOR=#a31515]"e"[/COLOR][COLOR=#000000]) [/COLOR][COLOR=#0000ff]and[/COLOR][COLOR=#000000] inet_addr:[/COLOR][/FONT][/COLOR]
[COLOR=#000000][FONT=Consolas]                [COLOR=#000000]result[ETHERNET_IP] = [/COLOR][/FONT][/COLOR][COLOR=#000000][FONT=Consolas][COLOR=#000000][COLOR=#000000][FONT=Consolas][COLOR=#000000]inet_addr[/COLOR][/FONT][/COLOR][/COLOR]
[/FONT][/COLOR][COLOR=#000000][FONT=Consolas]            [COLOR=#0000ff]elif[/COLOR][COLOR=#000000] e[[/COLOR][COLOR=#a31515]"ifname"[/COLOR][COLOR=#000000]].startswith([/COLOR][COLOR=#a31515]"w"[/COLOR][COLOR=#000000]) [/COLOR][COLOR=#0000ff]and[/COLOR][COLOR=#000000] inet_addr:[/COLOR][/FONT][/COLOR]
[COLOR=#000000][FONT=Consolas][COLOR=#000000]                result[WIFI_IP] = [/COLOR][/FONT][/COLOR][COLOR=#000000][FONT=Consolas][COLOR=#000000]inet_addr[/COLOR][/FONT][/COLOR]

[COLOR=#000000][FONT=Consolas][COLOR=#000000]        info = [/COLOR][COLOR=#0000ff]self[/COLOR][COLOR=#000000].get_linux_networks()[/COLOR][/FONT][/COLOR]
[COLOR=#000000][FONT=Consolas][COLOR=#0000ff]        try[/COLOR][COLOR=#000000]:[/COLOR][/FONT][/COLOR]
[COLOR=#000000][FONT=Consolas][COLOR=#000000]            result[WIFI_NETWORK] = info[[/COLOR][COLOR=#a31515]"profile"[/COLOR][COLOR=#000000]][/COLOR][/FONT][/COLOR]
[COLOR=#000000][FONT=Consolas][COLOR=#0000ff]        except[/COLOR][COLOR=#000000]:[/COLOR][/FONT][/COLOR]
[COLOR=#000000][FONT=Consolas]            [COLOR=#0000ff]pass[/COLOR]

[COLOR=#0000ff]        return[/COLOR][COLOR=#000000] result[/COLOR][/FONT][/COLOR]
 
Last edited:
Sorry your code does not work:
Code:
Traceback (most recent call last):
  File "peppy.py", line 72, in <module>
    from ui.screen.network import NetworkScreen
  File "/home/haegarthehorrible/opt/Peppy/ui/screen/network.py", line 29, in <module>
    from util.wifiutil import WiFiUtil, CONNECTED, ETHERNET_IP, WIFI_NETWORK, WIFI_IP
  File "/home/haegarthehorrible/opt/Peppy/util/wifiutil.py", line 108
    inet_addr = None
                   ^
IndentationError: unindent does not match any outer indentation level
But I don't want to bother you any longer with that network stuff. Its your player and your work. I know how much work that is and as long as it works for you and other people i need to figure it out myself.

I don't have problems doing things in the CMD. But I always struggle with Python.
 
That's copy/paste issue again. You know that Python is indentation based language. There were missing white spaces in several lines. Try this one. If you will see similar errors just add/remove space where error occurs.
Code:
    def get_linux_network(self):
        """ Get Linux network info using 'ip' utility.
             If there many entries of the same type (eth or wlan) the function returns the last one.

        :return: network info
        """
        result = {}
        n = subprocess.check_output(["ip", "-j", "a"])
        js = json.loads(n)

        for e in js:
            try:
                e["addr_info"]    
            except:
                continue # if no addr_info
            inet_addr = None
            try:
                for a in e["addr_info"]:
                    if a["family"] == "inet":
                        inet_addr = a["local"]
                        break
            except:
                pass

            if e["ifname"].startswith("e") and inet_addr:
                result[ETHERNET_IP] = inet_addr
            elif e["ifname"].startswith("w") and inet_addr:
                result[WIFI_IP] = inet_addr

        info = self.get_linux_networks()
        try:
            result[WIFI_NETWORK] = info["profile"]
        except:
            pass

        return result
 
New version (Constable Edition) of the Peppy Player was just released.
The disk images with new release are available here:
https://github.com/project-owner/PeppyP ... isk-Images

New features:

  • Added the following functionality to the File Menu: alignment, sorting, wrapping and layout.
File Menu * project-owner/Peppy.doc Wiki * GitHub


fb-hide-folder-name.png



fb-vertical-layout.png


  • Implemented a graceful poweroff.
Power OnOff * project-owner/Peppy.doc Wiki * GitHub


pms-1.png

  • Patched the bug in the ALSA library. That allowed to enable VU Meter and Spectrum Analyzer screensavers.
vu-meters-sa.jpg

  • Added support for images embedded into mp4 and m4a audio files.
  • Fixed the AirPlay mode issues.
  • Upgraded to the latest version of the Tornado web server.
  • To fix CD player issues replaced the nonfunctional API endpoint freedb.org by gnudb.org.
  • Refactored the Wi-Fi configuration framework.
  • Fixed multi-touch driver
 
Here is the new picoPeppy player.
This is the smallest player in the Peppy family.
It's using the Headless Peppy Player Disk Image.
The player can connect to the Internet via Wi-Fi and to the speaker via Bluetooth.
A Web Browser running on any device in the home network can be used to control the player.
The player can be used to listen to Web Radio, Podcasts and Audiobooks.

More information can be found here:
Home * project-owner/picoPeppy.doc Wiki * GitHub

pp-3.jpg


pp-2.jpg


pp-5.jpg
 
Last edited:
Here is the new addition to the Peppy Player Gallery - BoomBox by Zippo (Germany)

This is very interesting project in the original vintage enclosure. The base of the project is the ghetto blaster which is about 25 years old. It was available for a low price as the cassette player didn't work. Zippo preserved the radio functionality and replaced the cassette player by the Raspberry Pi and display. He connected the audio output of the Raspberry Pi to the magnetic reading head through the low-pass RC filter. That allowed to re-use the original amplifier and speakers. The original volume control can be used as well. The mp3 files can be placed on a separate partition of the USB drive which is automatically mounted via the fstab during booting process.

Here are the components used for the project:
- Raspberry Pi 3B bootable from USB flash drive. The connection to the Ethernet and USB drive is available in the battery compartment
- Display is a waveshare clone where you have to invert the contrasts
- Stop, backward, forward and play buttons connected to the Raspberry Pi GPIO connector
- 5V 3A power supply

Gallery * project-owner/PeppyPlayers.doc Wiki * GitHub

z1.jpg


z2.jpg
 
Hi lazybyby,

Have you tried to follow the steps for connecting Wi-Fi?
Network * project-owner/Peppy.doc Wiki * GitHub
Which Raspberry Pi model do you use?

The player doesn't mount disks (hard or flash drives) for you. You need to do that yourself. After that you can just use the mounted folder from UI as any other folder. I'm planning to add auto-mounting in some of the future releases.

Best regards.