Pacman-Labyrinth, Stoppuhr und LCD-Display (float, str, usw)

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
Fussel132
User
Beiträge: 23
Registriert: Mittwoch 13. März 2019, 13:55

Hey Leute,
Ich knuspere schon wieder an meinem LCD-Display herum. Ich baue gerade für einen Freund bei einer Veranstaltung einen RPi, der bei GPIO-Input den Pacman-Sound abspielt und die Zeit misst. Das klappt auch alles ganz gut, nur wenn ich die vergangene Zeit in eine Variable packe und an das LCD senden will, meckert er folgendes:

Code: Alles auswählen

Traceback (most recent call last):
  File "/home/pi/Desktop/Marvins Pacman-Boulder/mastercode.py", line 66, in <module>
    Stopwatch()
  File "/home/pi/Desktop/Marvins Pacman-Boulder/mastercode.py", line 59, in Stopwatch
    lcd.lcd_display_string(elapsing_time, 4)
  File "/usr/lib/python2.7/lcddriver.py", line 108, in lcd_display_string
    for char in string:
TypeError: 'float' object is not iterable
Ich hab noch probiert das ganze mit str() zu machen oder int() aber das hat auch nicht zum Erfolg geführt. Das (noch nicht ganz fertige Programm) sieht so aus:

Code: Alles auswählen

# -*- coding: utf-8 -*-
import RPi.GPIO as GPIO
import time
import alsaaudio #sudo apt-get install python-alsaaudio
import pygame
import lcddriver

#Initialisieren des LCD-Treibers
lcd = lcddriver.lcd()
lcd.lcd_clear()

#Vorarbeit pygame für Soundwiedergabe
lcd.lcd_display_string("Laden der Musik...", 1)
pygame.init()
pygame.mixer.music.load('/home/pi/Desktop/Marvins Pacman-Boulder/Pac-Man-Theme-Song.mp3')

#Setzen der PCM-Lautstärke auf 100
m = alsaaudio.Mixer('PCM')
m.setvolume(100)
current_volume = m.getvolume()
lcd.lcd_display_string("Volume set to 100\45", 2)

#Initalisieren der benötigten GPIO-Pins
GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)
GPIO.setup(18, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)
lcd.lcd_display_string("GPIO 17 and 18 ready", 3)

#Setzen der Variablen
startcounter = 0
stopcounter = 1
runningwatch = 0
lcd.lcd_display_string("Variablen definiert", 4)
time.sleep(2)
lcd.lcd_clear()
lcd.lcd_display_string("Nearly ready...", 1)
time.sleep(3)

#Definitionen
def StandbyScreen():
    lcd.lcd_backlight("on")
    lcd.lcd_clear()
    lcd.lcd_display_string("      Marvin`s", 1)
    lcd.lcd_display_string("   PacMan-Boulder", 2)
    time.sleep(8)
    lcd.lcd_backlight("off")

def Stopwatch():
    lcd.lcd_clear()
    lcd.lcd_backlight("on")
    lcd.lcd_display_string("   PacMan-Boulder", 1)
    lcd.lcd_display_string("     Viel Spa\342!", 2)
    start_time = time.time()
    while runningwatch == 1:
        elapsingtime = time.time()-start_time
        elapsing_time = round(elapsingtime, 1)
        str(elapsing_time)
        lcd.lcd_display_string(elapsing_time, 4)
        #print(round(elapsingtime, 1))
        time.sleep(0.1)
    #if runningwatch == 2:

StandbyScreen()
runningwatch += 1
Stopwatch()

#Main Code

while 1:
    if GPIO.input(18) == GPIO.HIGH and startcounter == 0:
        pygame.mixer.music.play()
        print("State HIGH, be like START!!!")
        startcounter += 1
        time.sleep(5)
        stopcounter = 0
    elif GPIO.input(17) == GPIO.HIGH and stopcounter == 0:
        pygame.mixer.music.fadeout(1)
        print("STOP")
        stopcounter += 1
        time.sleep(5)
        startcounter = 0
Ich würde mich über Hilfe freuen. Der im Fehlercode erwähnte "Treiber" ist dieser hier:

Code: Alles auswählen

import sys
sys.path.append("./lib")

import i2c_lib
from time import *

# LCD Address
ADDRESS = 0x27

# commands
LCD_CLEARDISPLAY = 0x01
LCD_RETURNHOME = 0x02
LCD_ENTRYMODESET = 0x04
LCD_DISPLAYCONTROL = 0x08
LCD_CURSORSHIFT = 0x10
LCD_FUNCTIONSET = 0x20
LCD_SETCGRAMADDR = 0x40
LCD_SETDDRAMADDR = 0x80

# flags for display entry mode
LCD_ENTRYRIGHT = 0x00
LCD_ENTRYLEFT = 0x02
LCD_ENTRYSHIFTINCREMENT = 0x01
LCD_ENTRYSHIFTDECREMENT = 0x00

# flags for display on/off control
LCD_DISPLAYON = 0x04
LCD_DISPLAYOFF = 0x00
LCD_CURSORON = 0x02
LCD_CURSOROFF = 0x00
LCD_BLINKON = 0x01
LCD_BLINKOFF = 0x00

# flags for display/cursor shift
LCD_DISPLAYMOVE = 0x08
LCD_CURSORMOVE = 0x00
LCD_MOVERIGHT = 0x04
LCD_MOVELEFT = 0x00

# flags for function set
LCD_8BITMODE = 0x10
LCD_4BITMODE = 0x00
LCD_2LINE = 0x08
LCD_1LINE = 0x00
LCD_5x10DOTS = 0x04
LCD_5x8DOTS = 0x00

# flags for backlight control
LCD_BACKLIGHT = 0x08
LCD_NOBACKLIGHT = 0x00

En = 0b00000100 # Enable bit
Rw = 0b00000010 # Read/Write bit
Rs = 0b00000001 # Register select bit

class lcd:
   #initializes objects and lcd
   def __init__(self):
      self.lcd_device = i2c_lib.i2c_device(ADDRESS)

      self.lcd_write(0x03)
      self.lcd_write(0x03)
      self.lcd_write(0x03)
      self.lcd_write(0x02)

      self.lcd_write(LCD_FUNCTIONSET | LCD_2LINE | LCD_5x8DOTS | LCD_4BITMODE)
      self.lcd_write(LCD_DISPLAYCONTROL | LCD_DISPLAYON)
      self.lcd_write(LCD_CLEARDISPLAY)
      self.lcd_write(LCD_ENTRYMODESET | LCD_ENTRYLEFT)
      sleep(0.2)

   # clocks EN to latch command
   def lcd_strobe(self, data):
      self.lcd_device.write_cmd(data | En | LCD_BACKLIGHT)
      sleep(.0005)
      self.lcd_device.write_cmd(((data & ~En) | LCD_BACKLIGHT))
      sleep(.0001)

   def lcd_write_four_bits(self, data):
      self.lcd_device.write_cmd(data | LCD_BACKLIGHT)
      self.lcd_strobe(data)

   # write a command to lcd
   def lcd_write(self, cmd, mode=0):
      self.lcd_write_four_bits(mode | (cmd & 0xF0))
      self.lcd_write_four_bits(mode | ((cmd << 4) & 0xF0))
      
   #turn on/off the lcd backlight
   def lcd_backlight(self, state):
      if state in ("on","On","ON"):
         self.lcd_device.write_cmd(LCD_BACKLIGHT)
      elif state in ("off","Off","OFF"):
         self.lcd_device.write_cmd(LCD_NOBACKLIGHT)
      else:
         print "Unknown State!"

   # put string function
   def lcd_display_string(self, string, line):
      if line == 1:
         self.lcd_write(0x80)
      if line == 2:
         self.lcd_write(0xC0)
      if line == 3:
         self.lcd_write(0x94)
      if line == 4:
         self.lcd_write(0xD4)

      for char in string:
         self.lcd_write(ord(char), Rs)

   # clear lcd and set to home
   def lcd_clear(self):
      self.lcd_write(LCD_CLEARDISPLAY)
      self.lcd_write(LCD_RETURNHOME)
LG und vielen Dank im Voraus,
Fussel132
Jankie
User
Beiträge: 592
Registriert: Mittwoch 26. September 2018, 14:06

In Zeile 57 steht

Code: Alles auswählen

str(elapsing_time)
das wird aber an keine Variable gebunden. Versuch mal Zeile 57 zu löschen und die darauffolgende so anzupassen:

Code: Alles auswählen

lcd.lcd_display_string(str(elapsing_time), 4)
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Fussel132: Man sollte keine neue Software mehr mit Python 2 schreiben.

``as`` beim Importieren ist zum Umbenennen. Wenn man nicht wirklich etwas umbenennt, ist das der falsche Weg.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Daraus folgt das Funktionen und Methoden alles was sie ausser Konstanten benötigen, als Argument(e) übergeben bekommen. `StandbyScreen()` braucht also das `lcd`-Objekt als Argument und noch zusätzlich `runningwatch`.

Die ganzen Kommentare sind wertlos. Faustregel: Kommentare beschreiben nicht *was* passiert, denn das steht da ja bereits als Code, sondern *warum* der Code das so macht. Sofern das nicht offensichtlich ist.

Pfade und Dateinamen definiert man üblicherweise als Konstanten am Anfang statt sie irgendwo im Code zu verstecken, damit man sie einfach(er) finden und ändern kann.

Namen sollten nicht abgekürzt werden. Einbuchstabige Namen sind in der Regel keine guten Namen. Das geht für Koordinaten (`x`, `y`) oder ganze Zahlen als Index/Laufvariablen (`i`, `j`), aber nicht für so komplexe Dinge wie ein `Mixer`-Objekt.

Werte sollte man nicht im Code wiederholen sondern als Konstante oder Variable definieren. Insbesondere ”magische” Zahlen machen das Programm schwerer verständlich und damit fehleranfälliger beim entwickeln des Programms. Wenn man so eine ”magische” Zahl mal ändern muss, muss man den gesamten Quelltext danach durchsuchen, an jeder Stelle feststellen ob es ein Wert ist den man ändern muss, oder der gleiche Wert, der aber für etwas anderes verwendet wird, und wenn man ihn ändern muss, muss man das an jeder Stelle gleich tun. Was man besonders leicht übersieht ist wenn der Wert auch innerhalb von Zeichenketten ausgeschrieben vorkommt.

`current_volume` wird definiert, aber nirgends verwendet.

Am Ende eines Programms das `GPIO` verwendet, sollte man immer sicherstellen das `GPIO.cleanup()` aufgerufen wird.

`startcounter`, `stopcounter`, und `runningwatch` sehen so aus als wären sie absichtlich da um Leser zu ärgern. Es sind Zahlen, und Du arbeitest mit ``+=`` also wie mit Zahlen. Aber eigentlich sind das gar keine Zahlen, was man aber erst merkt wenn man den Code liest der damit arbeiten und feststellt, dass die immer nur die Werte 0 und 1 annehmen können. Also eigentlich sind das gar keine Zahlen sondern Wahrheitswerte. Dafür hat Python die Werte `True` und `False` und man verwendet da auch kein ``+=``. Selbst für Zahlen als Wahrheitswerte ist das eine ziemlich schräge Operation.

Wenn man sich den Code dann weiter anschaut, stellt man fest das immer wenn `startcounter` den Wert 1 hat `stopcounter` den Wert 0 hat und umgekehrt. Also sind das gar keine zwei Variablen sondern *eine*. Das Gegenteil vom Wahrheitswert kann man jederzeit mit ``not`` ermitteln.

`startcounter` klingt auch irgendwie als ob da Starts gezählt werden. `counter_is_started` ist ein weniger irreführender Name für den Wert.

`runningwatch` ist auch komisch. Das wird erst auf 0 gesetzt und dieser Wert wird nie irgendwo verwendet bevor der Wert auf 1 erhöht wird. Was soll `runningwatch` eigentlich aussagen/bedeuten? Das ist doch nicht am Ende noch ein dritter Name für `counter_is_started`?

Namen werden klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase). Funktions- und Methodennamen beschreiben in der Regel die Tätigkeit die sie durchführen. `standby_screen` und `stopwatch` sind aber keine Tätigkeiten und damit Namen für passive(re) ”Dinge”.

Die Namen `elapsingtime` und `elapsing_time` sind *sehr* leicht zu verwechseln. `round()` sollte man auch nur verwenden wenn man tatsächlich mit dem gerundeten Wert weiterrechnen will. Die Anzahl der Nachkommastellen für die Anzeige löst man per Zeichenkettenformatierung.

Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf-8
import time

import alsaaudio
import lcddriver
import pygame
from RPi import GPIO

SOUND_FILENAME = (
    "/home/pi/Desktop/Marvins Pacman-Boulder/Pac-Man-Theme-Song.mp3"
)

START_BUTTON_PIN = 18
STOP_BUTTON_PIN = 17


def show_intro(lcd):
    lcd.lcd_backlight("on")
    lcd.lcd_clear()
    lcd.lcd_display_string("      Marvin`s", 1)
    lcd.lcd_display_string("   PacMan-Boulder", 2)
    time.sleep(8)
    lcd.lcd_backlight("off")


def show_stopwatch(lcd):
    lcd.lcd_clear()
    lcd.lcd_backlight("on")
    lcd.lcd_display_string("   PacMan-Boulder", 1)
    lcd.lcd_display_string("     Viel Spa\342!", 2)
    start_time = time.time()
    while True:
        elapsed_seconds = time.time() - start_time
        lcd.lcd_display_string("{:.1f}".format(elapsed_seconds), 4)
        time.sleep(0.05)


def main():
    lcd = lcddriver.lcd()
    lcd.lcd_clear()

    lcd.lcd_display_string("Laden der Musik...", 1)
    pygame.init()
    pygame.mixer.music.load(SOUND_FILENAME)

    mixer = alsaaudio.Mixer("PCM")
    mixer.setvolume(100)
    lcd.lcd_display_string("Volume set to {}\45".format(mixer.getvolume()), 2)

    try:
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(
            [START_BUTTON_PIN, STOP_BUTTON_PIN],
            GPIO.IN,
            pull_up_down=GPIO.PUD_DOWN,
        )
        lcd.lcd_display_string("Buttons ready", 3)
        show_intro(lcd)

        show_stopwatch(lcd, True)

        counter_is_started = 0
        while True:
            if not counter_is_started and GPIO.input(START_BUTTON_PIN):
                pygame.mixer.music.play()
                print("State HIGH, be like START!!!")
                counter_is_started = True
                time.sleep(5)
            elif counter_is_started and GPIO.input(STOP_BUTTON_PIN):
                pygame.mixer.music.fadeout(True)
                print("STOP")
                time.sleep(5)
                counter_is_started = False
    finally:
        GPIO.cleanup()


if __name__ == "__main__":
    main()
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Fussel132
User
Beiträge: 23
Registriert: Mittwoch 13. März 2019, 13:55

Hey Blackjack,
Ich habe es erst in P3 versucht, aber der LCD-Treiber hat da irgendwie nicht funktioniert. Deswegen habe ich es in 2 gemacht. Du hast ja recht, das Programm hat einige wenn nicht sogar viele "Fehler". Ich habe das auch nur als zwischendurchprojekt gemacht und nicht wirklich auf die Syntax oder anderes geachtet. Mir ging es in erster Linie darum, dass alles funktioniert. Ich probiere aber trotzdem später mal deinen Code ausprobieren und schauen, ob ich es nicht doch nach P3 kriege. Ich danke dir jetzt schon für deine Hilfe, mit so viel hab ich gar nicht gerechnet :). Bis später mal,
Fussel132
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Fussel123: Ja, das lcddriver-Modul müsste man dann auch nach Python 3 portieren.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Ich hab jetzt auf den ersten Blick nichts entdeckt, was nicht unter Python3 lauffähig wäre.
Fussel132
User
Beiträge: 23
Registriert: Mittwoch 13. März 2019, 13:55

@Sirius3, er meckert bei i2clib rum aber ich glaub das Problem krieg ich hin. Ich hab dafür aber erst heute nachmittag Zeit. Aber du hast recht, daran liegt nicht.
LG Fussel
Antworten