0

I have made a talking vending machine catalog for the blind, it runs inside a raspberry pi put inside the vending machine with a speaker.

The program will be running all the time, headless, and the csv where the vending machine snack information and numbers is kept, will be hosted online (its temporarily in the Pi for now though, as of this question). Its structured like so:
66, Mrs Freshleys Cupcakes
14, Flamin Hot Cheetos
etc.
It doesnt matter what order items are added to the csv, as I sort the list in my code. The Vending Machine company guy said they will happily update the csv with new contents if they change things inside the vending machine; and so when they save that csv, I want my program, which will be running constantly, to pull that fresh data in and update my VL (VendyList) variable. That way, if item "70, gummy bears" is added, or number 66 is changed, it will automatically update it. Is there a way to refresh the import csv? Or refresh the whole program whenever someone presses the '00' for 'help' function?

Here is the code

from gtts import gTTS
import pygame
from io import BytesIO
import os
import sys
import time

pygame.init()
if sys.version_info[0] == 3:
    # for Python3
    from tkinter import *   ## notice lowercase 't' in tkinter here
else:
    # for Python2
    from Tkinter import *

def say(text):
    tts = gTTS(text=text, slow=False, lang='en-us', lang_check=False)
    fp = BytesIO()
    tts.write_to_fp(fp)
    fp.seek(0)
    pygame.mixer.init()
    pygame.mixer.music.load(fp)
    pygame.mixer.music.play()



from csv import reader
infile = open(r'/home/pi/VendyLogProject/vendylist.csv',mode='r')

vl = sorted(list(reader(infile)))
vl2 = [item[0] for item in vl]


baseposition = vl[0] # 0 is the first entry index in the vl list




def current(event=None):
        global baseposition # baseposition was defined outside of the function, therefore we call global
        say(baseposition[1]+baseposition[0])



def back(event=None):
        global baseposition
        currentposition = vl.index(baseposition)
        if currentposition == 0: 
                say(baseposition[1]+baseposition[0])

        else:
                previousposition = int(currentposition) - 1 # previousposition is previous position
                baseposition = vl[previousposition]
                say(baseposition[1]+baseposition[0])



def forward(event=None):
        global baseposition
        currentposition = vl.index(baseposition)
        if currentposition == (len(vl) - 1):
                say(baseposition[1]+baseposition[0])

        else:
                nextposition = int(currentposition) + 1 # nextposition is next position
                baseposition = vl[nextposition]
                say(baseposition[1]+baseposition[0])


def readnumber(int):
           for item in vl:
            global baseposition
            currentposition = vl.index(baseposition)
            if int == item[0]:
                      baseposition = vl[vl.index(item)]
                      say(baseposition[1]+baseposition[0])


def help():
           say("Welcome to Vendy log! Use the plus and minus keys to go through the list of snacks or push a number to hear its contents!")







root = Tk()
prompt = '      VendyLog      '
label1 = Label(root, text=prompt, width=len(prompt))
label1.pack()

#keys buffer
keybuf = []

def test_after():
           if keybuf:
                      num = ''.join(keybuf)
                      keybuf.clear()




def get_key(event):
           keybuf.append(event.char)
           event.char = ''.join(keybuf)
           root.after(500,test_after)
           if event.char == '-':
                      back()
           elif event.char == '+':
                      forward()
           elif event.char == '.':
                      current()
           elif event.char in vl2:
                      readnumber(event.char)
           elif event.char == '00':
                      help()




root.bind_all('<Key>', get_key)

root.mainloop()

jjang
  • 45
  • 6
  • 1
    Have a timer (e.g. https://stackoverflow.com/a/2401181/51685) re-read the file every, say, minute? – AKX Mar 23 '20 at 16:42

2 Answers2

1

Here's a refactoring of your code with refresh every 60 seconds added, via load_list, refresh_list, and refresh_list_and_enqueue_next_refresh.

I took the liberty of fixing some other things too, namely...

  • Since I'm not on a Raspberry Pi, I don't have Pygame and gTTS immediately available. Those are now optional but if not available, will warn the user.
  • Only the Python 3 tkinter import is supported. Python 2 is deprecated.
  • We're now only using a list index variable to look through items, not copying items to and from the list.
  • A re-entrancy bug that could clear the key buffer while the user was still entering test was fixed, by keeping track of the last instant of time when the user hit a button.
import time
from io import BytesIO
import csv
import tkinter

try:
    import pygame
    from gtts import gTTS
except ImportError:
    pygame = None
    print("Warning: no sound output.")


if pygame:
    pygame.init()
    pygame.mixer.init()


def say(text):
    print("Speaking:", text)
    if pygame:
        tts = gTTS(text=text, slow=False, lang="en-us", lang_check=False)
        fp = BytesIO()
        tts.write_to_fp(fp)
        fp.seek(0)
        pygame.mixer.music.load(fp)
        pygame.mixer.music.play()


def load_list():
    with open(r"./vendylist.csv", mode="r") as infile:
        return sorted(list(csv.reader(infile)))


items = []
list_index = 0


def refresh_list():
    global items, list_index
    new_items = load_list()
    if items != new_items:
        print("Loaded new items (count: %d)." % len(new_items))
        # TODO: here's hoping resetting the list isn't interrupting an user!
        items = new_items
        list_index = 0
    else:
        print("No new items loaded...")


def speak_current():
    speak_index(list_index)


def speak_index(index):
    item = items[index]
    say("%s %s" % (item[1], item[0]))


def clamp_list_index(new_index):
    return min(len(items) - 1, max(new_index, 0))


def back():
    global list_index
    list_index = clamp_list_index(list_index - 1)
    speak_current()


def forward():
    global list_index
    list_index = clamp_list_index(list_index + 1)
    speak_current()


def readnumber(entered_number):
    global list_index
    for index, (number, text) in enumerate(items):
        if str(entered_number) == str(number):
            speak_index(index)
            # EDIT: Update the global list index
            #       so +/- start from the item just spoken.
            list_index = index
            break


def help():
    say("Welcome to Vendy log!")


root = tkinter.Tk()
prompt = "      VendyLog      "
label1 = tkinter.Label(root, text=prompt)
label1.pack()

last_key_time = time.time()
keybuf = []


def maybe_clear_buffer():
    if keybuf and time.time() - last_key_time > 1:
        print("Clearing key buffer (%s)." % keybuf)
        keybuf.clear()


def get_key(event):
    global keybuf, last_key_time
    last_key_time = time.time()
    keybuf.append(event.char)
    keybuf_str = "".join(keybuf)
    root.after(1000, maybe_clear_buffer)

    if event.char == "-":
        back()
    elif event.char == "+":
        forward()
    elif event.char == ".":
        speak_current()
    elif keybuf_str == "00":
        help()
    elif keybuf_str.isnumeric():
        readnumber(keybuf_str)


def refresh_list_and_enqueue_next_refresh():
    refresh_list()
    root.after(60000, refresh_list_and_enqueue_next_refresh)


refresh_list_and_enqueue_next_refresh()
root.bind_all("<Key>", get_key)
root.mainloop()
AKX
  • 152,115
  • 15
  • 115
  • 172
  • Wow, thank you so much. I opened the program and while running changed an entry in the csv. It changed it instantly. My only issue now is that if someone presses + to go through the list, and gets to say, number 12, then presses number 45, and then presses + to go to the next item in the list, the list positions moves to 13 instead of 46. Also, what should I delete to get rid of the console output? I dont want it to print anything like the numbers entered or buffer clearing. :) – jjang Mar 23 '20 at 18:34
  • For the console output, they're the `print()` statements, and they're there for debugging ease. I'd honestly keep them there - after all, erm, your target audience won't see them... As for the navigation bug you describe, it's an one-line fix, let me add it. – AKX Mar 23 '20 at 18:36
  • hmm, when I put in the number 66 for cupcakes or anything else, I get: for index, (number, text) in enumerate(items): ValueError: not enough values to unpack (expected 2, got 0) now it has suddenly stopped working. Sorry to bug you with this! – jjang Mar 23 '20 at 18:47
  • You're not bothering! That sounds like your data isn't exactly correct - maybe try adding `print(items)` before that line and see what's up... – AKX Mar 23 '20 at 18:49
  • After I tested it by adding 70,cheese to the csv I pushed return and made a new line by mistake. Deleting that fixed it! Thank you so much! – jjang Mar 23 '20 at 19:15
  • Glad I could help! Please mark the answer as correct if you can. – AKX Mar 23 '20 at 19:18
  • Ps. it will be easy to make `load_list()` resilient against that sort of error :) – AKX Mar 23 '20 at 19:18
0

Currently the file is never closed, but you can re-read it on any event you want to implement with the following (open follows csv module doc recommendations for Python 2 and 3):

if sys.version_info[0] == 3:
    with open(r'/home/pi/VendyLogProject/vendylist.csv',newline='') as infile:
        vl = sorted(list(reader(infile)))
else:
    with open(r'/home/pi/VendyLogProject/vendylist.csv','rb') as infile:
        vl = sorted(list(reader(infile)))
Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
  • Will this work if no new line is added, and just one already existing line in the csv is edited? Also as the program will be running constantly, I worry that ill end up with lots and lots of open files, when all I want to do is refresh one file. Im new to python so please excuse me. – jjang Mar 23 '20 at 16:50
  • @jjang This code reads the file and closes it. If you run the same lines again it will reload the data and close it. `with` closes the file when execution leaves the `with` block. – Mark Tolonen Mar 23 '20 at 17:09