Much improved Pi Assistant message reader.

The most important improvement is that this program now works… 😎 

I just leave it running on the Pi Assistant, along with the Google provided assistant_library_with_button_demo.py program that I like to use, when I want to ask the Google Assistant something…

#!/usr/bin/python3
#
# Program to run on PiAssistant, to watch for newly arrived text files in
# /home/pi/Messages that it should speak, and delete them once it has.
import os
import paramiko
import time

path = “/home/pi/Messages/”

while True:
    with os.scandir(path) as entries:
        for entry in entries:
            f = open(entry, “r”)
            content = f.read()
            f.close()
            command = “python ~/AIY-projects-python/src/aiy/voice/tts.py “” +
                      content + “” –lang en-GB –volume 10 –pitch 60 –speed 90″
            os.system(command)
            os.remove(path+entry.name)      
            # Now wait for tts.py to finish
            command = “ps -ef | grep -v grep | grep “voice.tts” | wc -l”
            while True:
                ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command(command)
                answer = str(ssh_stdout.read())
                if answer[2] == “0”:
                break           
            time.sleep(0.5)
    time.sleep(0.5)

And here is a program to run on a cluster of Raspberry Pi computers, as say_who.py, which will each send a message to the Pi Assistant, from each core of the processor. The assistant will read them out, separately. It does seem still to lose some messages, and if I work out why, I will let you know! I have been searching for simple example programs for Raspberry Pi clusters on the internet for ages, and have not found very many. Here’s my little gift…

#!/usr/bin/python3
from mpi4py import MPI
import os
import time
import subprocess as sp
# Attach to the cluster and find out who I am and what my rank is
comm = MPI.COMM_WORLD
my_rank = comm.Get_rank()
cluster_size = comm.Get_size()
my_host = os.popen(‘cat /etc/hostname’).read().strip()
    
# Make something for the assistant to say
speech = “Host is  {} “.format(my_host)
speech = speech + “Rank {} “.format(my_rank)
# Make a unique filename and path
message = “/home/pi/” + my_host + str(my_rank)
          + time.strftime(“%H%M%S”) + “.txt”
# Put the speech in the file
fp = open(message,”x”)
fp.write(speech)
fp.close()
# Put a copy of the file on PiAssistant
cmd = “scp ” + message + ” pi@PiAssistant:/home/pi/Messages/”
sp.call(cmd,shell=True)
os.remove(message)
You would load that program on each of the Pi machines in your cluster, and then set it running using the command

mpiexec -n 16 -hostfile myhostfile python3 say_who.py

I’m assuming you have four Pi 3 machines in the cluster, hence the -n 16 parameter. 

#RaspberryPi #Cluster #Python

A new program for Google Voice AIY

My earlier post, about a function that could send things to the Google AIY Raspberry Pi Assistant, and supposedly prevent more than one message being spoken at a time, was errmmm, wrong. That’s a technical term, that we programmers use.

I wrote a test program for my Pi cluster, whereby each of the sixteen cores would announce its hostname and rank. It’s not all that often you get the opportunity to use the word cacophony, but…

Basically, several processes could all think the Assistant was not busy, and they all sent messages in the time it took for the first message to start being spoken.

I spent a while looking at how to program mutual exclusivity for a resource, and was impressed by how complex such an apparently simple thing can get.

I decided that what was needed was a simple program, running on the Assistant, that would watch a directory, notice when a file to be spoken arrived, and speak the text in that file. Python makes it easy to deal with more than one file in the directory. Here’s what I wrote…

#!/usr/bin/python3

#
# Program to run on PiAssistant, to watch for newly arrived text files in
# /home/pi/Messages that it should speak, and delete them once it has.
import os
import time
path = “/home/pi/Messages/”
while True:
    with os.scandir(path) as entries:
        for entry in entries:
            f = open(entry, “r”)
            content = f.read()
            f.close()
            command = “python ~/AIY-projects-python/src/aiy/voice/tts.py “” +
                      content + “” –lang en-GB –volume 10 –pitch 60 –speed 90″
            os.system(command)
            time.sleep(0.5)
            os.remove(path+entry.name)
    time.sleep(0.5)

When it spots one of more files in the Messages directory, it reads the text, and sends it out to be spoken. It can supposedly only do one file at a time, but… Still the cacophony!    

#RaspberryPi #GoogleAIY #Python            

Using Google AIY voice.

Using Google AIY voice, part 94.

I wrote a useful function, so that any of my Raspberry Pi machines could send a string to the Google Assistant as a message to be spoken aloud, and it worked very well. Here it is…
# A function to send a string to PiAssistant for output as speech
import paramiko

ssh = paramiko.SSHClient()
ssh.load_host_keys(filename=’/home/pi/.ssh/known_hosts’)
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

server, username, password = (‘PiAssistant’, ‘pi’, ‘your_password-goes_here’)
def say_this_with_assistant(say):
    ssh.connect(server, username=username, password=password)
    command = “python ~/AIY-projects-python/src/aiy/voice/tts.py “” + say + “” –lang en-GB –volume 10 –pitch 60″
    ssh.exec_command(command)
    ssh.close()
    return 

print(“Starting…n”)
say_this_with_assistant(“Normal service will be resumed as soon as possible. “)
print(“Finished.n”)

The snag occurs when two, or more, machines want to say something at the same time. The PiAssistant calmly multitasks, and says the strings at the same time, which sounds interesting, but tends to be incomprehensible. I hoped that voice/tts.py would have some way of letting other programs know whether it was busy, but it doesn’t. However, the low level Linux ps command can tell when voice/tts.py is running, so I added a check for that as a Paramiko command, like this…

# A function to send a string to PiAssistant for output as speech when it’s free
import paramiko
import time
ssh = paramiko.SSHClient()
ssh.load_host_keys(filename=’/home/pi/.ssh/known_hosts’)
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
def say_when_free(say):
    server, username, password = (‘PiAssistant’, ‘pi’, ‘your_password-goes_here‘)
    ssh.connect(server, username=username, password=password)    
    command = “ps -ef | grep -v grep | grep “voice.tts” | wc -l”
    while True:
        ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command(command)
        answer = str(ssh_stdout.read())
        if answer[2] == “0”:
            break
        
    command = “python ~/AIY-projects-python/src/aiy/voice/tts.py “” + say + “” –lang en-GB –volume 10 –pitch 60″
    ssh.exec_command(command)
    ssh.close()
    return 
print(“Starting…n”)
say_when_free(“I waited until I was allowed to say this. “)
print(“Finished.n”)
I’m not going to claim  any of this as properly written Python, the best way to do what is required, or even particularly original, but it does currently seem to be the only description of how to do this that’s on the internet.

Google Assistant

Google Assistant updates. 

Once I had my Google Assistant running Raspbian Buster, and verified that it could not only answer spoken questions, but also speak text sent to it by other computers, I made a bad mistake.


As a rule, it’s a good idea to keep a Raspberry Pi’s operating system up to date. Not on this system, it turns out!

I found that doing so stops the microphone working. Other people have also found this, according to the internet, dating back to 2018.

It was simple enough to fix, once I knew that the update was the cause of the trouble. I just flashed the micro SD card again, and copied my configuration files back onto it. Just like that, it was all working again. The files you need to copy back on are assistant.json client_secrets.json and credentials.json, if you were wondering.

In order not to let the same unpleasantness happen again, I disabled the update programs apt and apt-get, using the commands

sudo chmod a-x /usr/bin/apt
sudo chmod a-x /usr/bin/apt-get


Huge struggle with Google Assistant

 Google Assistant

I really like this thing, it’s a Raspberry Pi 3B+, with a Google AIY Voice HAT, and a nifty acrylic case instead of the original cardboard one.


I’ve had it for a while though, and it was still running Raspbian Jessie. Nothing wrong with that, but there are new versions of the AIY Voice software, that can do more things than the original software. I’d rather like my other Raspberry Pi systems to be able to use it as a voice input and output, for example. To do that requires Raspbian Buster…

So that I wouldn’t lose all the setup information on the Micro SD card, I decided to upgrade in place. Bad idea!


The upgrade, two steps, via Raspbian Stretch, takes hours. Stretch booted OK, though, so I continued. Buster wouldn’t boot up…

All that took over half a day, and it didn’t work.

After a bit of head scratching, and a certain amount of exercising my extensive vocabulary of rude words, I did what anyone sensible would have done in the first place, and downloaded a ready-made image, from GitHub and flashed the micro SD card with Balena Etcher.

I had saved copies of the setup information files handy, which I copied in with WinSCP (freeware that’s so great I paid for it!), rebooted the Pi, and suddenly it worked!

The Nebra Anybeam HAT

 Is the Nebra AnyBeam “HAT” a secret?

I ask, because I have still seen no reviews at all of this fine thing in any of the magazines, or online. Here’s mine. The paint on the study wall is not an ideal screen, and there’s too much ambient light, but still..
 
I’ve put HAT in quotes, because this is technically not a HAT, as it doesn’t have the usual ROM for the Pi to recognise it by.
 

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
I used a Pibow case from Pimoroni to house it, with some extension pieces to raise the top high enough, and a Lego compatible base, in case I want to build it into anything made from Technic pieces at some point. The case made the Pi 3B run hot, so I added a few spacers and a heat sink, and it currently runs at about 63°C when nothing is happening.
 
Still trying to think of a list of things it can do…

Scrolling text on a UnicornHatHD

Scrolling text on UnicornHatHD


I wanted scrolling text on the UnicornHatHD which has a 16×16 LED array, and could only find a program that would do it on the old UnicornHat with its 8×8 array. 

https://github.com/topshed/UnicornHatScroll

If you change a couple of lines in UHScroll.py, it works on the UnicornHatHD.

Change the definition of flip to:-

flip = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]

and change the start of show_letter() to:-

def show_letter(letter,colour,brightness): 

    UH.rotation(0)        

    for i in range(16):

        for j in range(8):

That’s all you need to scroll text on the top 8 rows of LEDs. It should be fairly easy to use the lower 8 rows as well…

Useful? 

#RaspberryPi #Pimoroni #UnicornHatHD

Raspberry Pi motion detection and notification program

PiDoorCam updated

This is the amended, latest version of my Raspberry Pi program to watch the street outside, detect change, and send a picture to another computer for storage, and to my phone. I’m often in the back garden, and can’t hear the doorbell; missing deliveries is rather annoying!

It no longer tries to take pictures when it’s dark, not just because I don’t have an infra-red camera and infra-red floodlights, but mainly because the street light outside flashes on and off all night, and I don’t want hundreds of pictures of that!

You’ll need to set up a Pushover account, which is free provided you don’t send too many notifications, and a Ramdisk with a directory called /var/tmp on the Pi.

Program listing

# Program for PiDoorCam

# Detects motion, and when it spots some, takes a high resolution
# picture, and sends the picture to another computer, also
# a notification via Pushover, to my phone.

import io
import os
import picamera
import ftplib
import time
import datetime
from PIL import Image
import requests
import json
import schedule

camera = picamera.PiCamera()
picamera.PiCamera.CAPTURE_TIMEOUT = 30

# If we detect 100 pixels that changed by 30, we have seen movement.
pixels = 100
difference = 30

# Use the maximum resolution of the camera.
# This is for V1. V2 is 3280 x 2464.
# It’s also correct for the ZeroCam.
width = 2592
height = 1944

# Internet lookup of sunrise and sunset at our location
def sunrise_sunset():
    global sunrise, sunset
    
    # Location of greenhouse is lat = yyyyy lon = xxxxx
    url = ‘https://api.sunrise-sunset.org/json?lat=yyyyy&lng=xxxxx’
    response = requests.get(url)
    dict = response.json()
    res = dict.get(‘results’)

    curtim = datetime.datetime.now()
    hm = res.get(‘sunrise’).split(“:”)
    sunrise = curtim.replace(hour=int(hm[0])-1, minute=int(hm[1]))
    print(“An hour before sunrise “,sunrise)
    
    hm = res.get(‘sunset’).split(“:”)
    sunset = curtim.replace(hour=int(hm[0])+13, minute=int(hm[1]))
    print(“An hour after sunset   “,sunset)

# I copied this voodoo motion detection from somewhere. Changed the timeout
#  setting above to prevent the occasional failures to complete captures.
# Only alter this if you know what you are doing!
def compare():
   camera.resolution = (100, 75)
   stream = io.BytesIO()
   format = ‘bmp’
   camera.capture(stream, format)
   stream.seek(0)
   im = Image.open(stream)
   buffer = im.load()
   stream.close()
   return im, buffer

# Function to take a new high resolution picture, send it to another computer,
# send it to my phone, and then delete it.
def newimage(width, height):
    when = datetime.datetime.now()
    filename = “door-%04d%02d%02d-%02d%02d%02d.jpg”
               % (when.year, when.month, when.day, when.hour, when.minute, when.second)
    camera.resolution = (width, height)
    camera.capture(“/var/tmp/”+filename)

    connected = True
    ftp = ftplib.FTP()
    ftp.connect(“computer-name”)
    
    try:
        ftp.login(“user-name”,”password”)
    except ftplib.all_errors:
        connected = False
        print (“Failed to login to server.”)
        ftp.quit()
        
    if connected:
        ftp.storbinary(‘STOR ‘+filename, open(“/var/tmp/”+filename, “rb”))
        print (“Sent to server “, filename)

    ftp.quit()

# Code to send the Pushover message. Make picture smaller first.
# Note this uses a Ramdisk you must set up elsewhere.
    im = Image.open(“/var/tmp/”+filename)
    im.resize((324,243),Image.ANTIALIAS)
    im.save(“/var/tmp/”+filename)
    
    r = requests.post(“https://api.pushover.net/1/messages.json”, data = {
        “token”: “you-need-to-get-a-token-from-pushover”,
        “user”: “you-need-to-get-a-user-name-from-pushover”,
        “device”: “your-device”,
        “sound”: “intermission”,
        “message”: filename
    },
    files = {
        “attachment”: (filename, open(“/var/tmp/”+filename, “rb”), “image/jpeg”)
    })
   # Check r for problems – maybe put a delay here?
    if r.status_code != 200:
        print(“Pushover message failed.”)
    else:
        print(“Pushover accepted the message.”)
         
# Now delete the file.
    os.remove(“/var/tmp/”+filename)
    # Delay to avoid being nasty to Pushover server.
    time.sleep(5)

# Main program.

camera.rotation = 0
print(“Running door.py”)
image1, buffer1 = compare()

# Find sunrise and sunset times at two in the morning, and once
# at startup.
schedule.every().day.at(“02:00”).do(sunrise_sunset)
sunrise_sunset()
while (True):
   # See if it’s time to get sunrise and sunset.
   schedule.run_pending()

   image2, buffer2 = compare()

   changedpixels = 0
   for x in range(0, 100):
      for y in range(0, 75):
         pixdiff = abs(buffer1[x,y][1] – buffer2[x,y][1])
         if pixdiff > difference:
            changedpixels += 1

   # See if we think something moved.
   if changedpixels > pixels:
   # See if it’s light enough to take a picture.
      now = datetime.datetime.now()
      if now > sunrise and now < sunset:
          newimage(width, height)
      else:
          print(“A bit dark at “,now)

   image1 = image2
   buffer1 = buffer2

Greenhouse computer ravings continued.

Fat man in the greenhouse.
In the end, I got fed up with the temperate readings being messed up by the Sense HAT being inside the case, and no cooling fan being able to keep its sensors cool enough. I dispensed with the Sense HAT, which I will find some other use for, and put a cheap temperature and humidity sensor on wires lead out of the case.
Having arranged a shade to keep direct sunlight off the sensor, I now get sensible readings. I missed being able to look at the greenhouse and see the temperature scroll past, so I had the computer report temperature and humidity to another Pi indoors. 
Simple web site

That computer runs the Apache web server, and uses the incoming readings to make a simple web page, which I can look at from my main computer…






Code…

textA = [“<!doctype html>n”,”<html lang=”en”>n”,”<head>n”,”<meta charset=”utf-8″>n”,
         “<title>Greenhouse</title>”,”<link rel=”stylesheet” href=”mystyle.css”>”,
         “<meta http-equiv=”refresh” content=”150″ >”,
         “</head>”,”<body>”,”<h1>Greenhouse</h1>”,”<p>”]
textZ = [“</body>n”,”</html>n”]
def update_greenhouse_website():
    global previous_time, greenhouse_temp_file_modified
    greenhouse_temp_file_modified = os.stat(‘/home/pi/ftp/files/gth.txt’).st_mtime
    if greenhouse_temp_file_modified == previous_time:
        #print(‘Same time’)
        textM = “No greenhouse data received.nCheck greenhouse computer!”
    else:
        #print(‘New time’)
        fp = open(‘/home/pi/ftp/files/gth.txt’, ‘r’)
        textM = fp.read()
        fp.close()
    
    fp = open(‘/var/www/html/greenhouse.html’, ‘w’)
    for line in textA:
        fp.write(line)
    fp.write(textM)
    fp.write(“n”)
    for line in textZ:
        fp.write(line)
    fp.close()
    
    previous_time = greenhouse_temp_file_modified  
    return

Weather Station woes….

If you were making a weather station, you would try to make it waterproof. But you have to try harder, Fine Offset of China. Your WH1040 dies when the rain gets in…




Rain may only fall vertically in China, perhaps, so the marvellously cheap weather station I got from Maplin, just before they mysteriously went bust, would do there. But this is Wales, and the wind blows up the hill, carrying rain upwards with it, and it gets into the electronics and stops it working. It should look like this on the computer, although those maximum wind speeds are just plain wrong, thanks to EasyWeather corrupting its database.





I have a spare control unit, found on eBay, and wanted to swap them round, but the clever cover that keeps rain from above off somehow welded itself to the control unit. I removed it with BF&I.





Data is coming through! And it has even uploaded to Weather Underground!

And, now, having typed that, I look again, and it has stopped working.


Oh, well… Hang on, it’s back! Electronics, don’t you just love them?