These last few days I have been improving my home automation assistant based on experience gained from using the Google Home Mini. While not at all anticipated, the latter's ability to play radio stations has proved useful. It has also been helpful to get weather information with Google Home. Accordingly I wanted to add similar capabilities to my DIY project.
The project is currently running on an Orange Pi Zero on which I have installed the full Armbian distribution of Debian 9 (Stretch). Internet radio will be played through the Music Player Daemon. The local weather forecast will be obtained from our national weather service and read out using the pico engine that performs text to speech synthesis.
Table of Contents
- Audio Input and Output
For some reason the Orange Pi Zero would no longer boot. I had been
playing around with CUPS the previous day but nothing terribly wrong had
occurred although in the end I did remove the package. Had I been too
enthusiastic in cleaning up the system? This glitch provided a good excuse to
move on to Debian 9 (Stretch) which is the currently
offered distribution at
No matter what I did, I could not enable the WiFi with
dietpi-config. Thinking it could be a problem with the micro SD
card, which could have been the reason for the boot failure, I reinstalled an
older DietPi image of Debian 8 (Jessie) on the same
SD card. Because WiFi worked, it seemed the culprit was a bug in the
So I decided to go back to the Armbian which is a considerably bulkier distribution.
but the system did boot, albeit, complain that my SD card was very
slow and things seemed a bit wonky. There are lots of queries on the Armbian forum about this and invariably the reply to
these is to check the power supply and SD card. Perhaps the little Belkin wall wart I had been using lately was not good enough. So
I went back to the Asus power supply which was
working without problems before. The
bad CRC warning did not go
It was time to look at the micro SD card. There is a good blog on Testing SD Cards with Linux by Chris Collins. Instead I
used F3 - an
alternative to h2testw for Linux by Michel Machado (AltaMayor) at Digrati. I compiled the command line utilities,
f3read, f3write as instructed and just copied them to my local binary
directory rather than install them. As a consequence, I had to run the
utilities as root to access the SD card
The good news, of course, is that the SD card is OK. The bad news was the very low write speed for a supposedly class 10 card. As far as I know, class 10 means minimum read and write speeds of 10 MB/s. The only way the card could be construed to be class 10 is to average the read and write speeds. With an average read speed of 25 MB/s, one would think that Armbian would not have complained. However, the 25 MB/s rate is for sequential reads and perhaps the random read speed is much lower.
For those that may be interested, the SD card is a Verbatim PREMIUM 16GB micoSHC Card (part number #44082). On the front of the packaging it says "up to 45BM/s 300X Read", on the back it says "Read Speed Up To 45MB/s (300X), Write speed lower. X=0.15MB/s" (novel punctuation). I am not sure what that means because 300*0.15MB/s = 45MB/s, which is not slower. Or is it 45*0.15 = 6.75MB/s which is nearer to the observed read speed?
Doing these test meant that I had to burn the distribution image onto the micro SD card and go through the boot sequence once again. I connected to the OPiZ serial port using Kermit (see Serial Connection with the Orange Pi Zero) before powering up the OPiZ.
As can be seen, the message
*** Warning - bad CRC, using default
environment is still visible, but the system did boot correctly.
After creating my account, I rebooted and logged in under my account.
After the usual setup with
armbian-config (change of the
time zone to
America/Moncton and hostname to
I was disappointed to discover that I could not get WiFi going. Furthermore
systemctl status reported a
which meant that something had not been installed correctly.
I played with
much progress. This is where the serial link shines because I could
disable the Ethernet interface when trying various combinations.
During one of the numerous reboots, there was a suggestion to upgrade to
version 4.14.15 of sunxi which I did immediately. I also removed the
interfaces file in
/etc/network because of a
recommendation I read on the
Armbianforum. Lo and behold,
no longer reported its state as degraded and I could get the WiFi working
by toggling it down and up.
Unfortunately, the interface did not come up when I rebooted, but
toggling down and up again worked. This was definite progress. I decided to
call it quits for the night. Yesterday morning, I was amazed to note that
both the Ethernet and WiFi interfaces were available on booting the Orange
Pi Zero. Later the WiFi interface came up without problem on a reboot without
an Ethernet connection. This morning, I could not reach the OPiZ from
the local area network even though
ifconfig showed that
wlan0 did have its usual IP address. I toggle the interface down
and up using the serial connection and that did fix the problem. But strangely,
Internet radio would not function because no stream could be decoded. Rebooting
fixed everything. Why?
Patience is touted as a virtue. It is a necessity with the Orange Pi Zero.
In all probability, the DietPi image will contain the newest corrections to Armbian and it will be possible to get WiFil to work on it sometime in the near future.
This is a good opportunity express my thanks to each and everyone of those very knowledgeable persons that provide these distributions to mere mortals like me. Without their precious and unselfish work, many single board computers would be nothing more than paper weights.
Since this small system is being tested as a voice-activated home
automation assistant, the sound hardware must be running. It turns out that
alsa-utils is installed by default in the
When I tried to record and play back a sound file with the
aplay utilities, nothing worked. To be
fair I hadn't expected it to work and, indeed, listing playback devices with
aplay -l and
aplay -L showed nothing was available.
Similarly, no capture devices were listed with
arecord -l or
arecord -L. Clearly the analogue audio input and output were not
For those picking up this saga mid-stream, I have an expansion board with a built in microphone and a 3.5mm audio jack to which are connected powered speakers.
As one would expect, the audio hardware can be enabled with
and then click on.
In the next screen, select,
and then again click on.
Then enable. If I remember correctly, use the up and down arrow keys to select it and then press the space bar to toggle the enabled check mark on or off. At the bottom of the list, two usb hosts were already enabled.
Click onand then back out of the utility clicking on or as needed. In the end the system has to be rebooted.
I think all this could have been done by modifying the
overlays line of the u-boot environment file as
That's enough to enable the hardware, now it must be configured. That involves creating a sound configuration file and then setting default recording and playback volumes. For the configuration file, I used the same one I had when using DietPi.
Many seem to prefer to set up a global configuration file. So the
same content could be saved to
/etc/asound.conf, but this must
be done as a super user as the owner of the file must be
The two screen captures below show how I set up the volume with
alsamixer following the instructions in step 1 of
Configuring Orange PI PC for analogue Line-Out jack audio output
(and Simultaneous HDMI output with Software Mixing) by AnonymousPi.
First ensure that the
Card shown is
H3 Audio Codec.
If that (or something similiar) is not what is shown in the top left corner,
press F6 to select the sound card. Then press
F3 to see only the playback devices.
Use the right and left cursor keys to select
and press the M key if a
stands for mute is displayed. What should be showing is
which I guess stands for Open. The use the up and down arrow keys to
Line out gain. I chose the maximum value before
seeing red. Then go to the
DAC input and do the same.
Moving on to the microphone, press F4.
As before, use the right and left cursor keys to select
Mic1, but here press the space bar to toggle it on. The
CAPTUREcaption will be shown and
[Off, Off]will no longer be displayed beside
Item: Mic1in the upper left corner. Then adjust
ADC Gainlevels using the up and down cursor keys. I chose to show one red bar in each case.
alsamixer is exited by pressing the Esc
key, the settings can be saved in the file
/var/lib/alsa/asound.state with the following command.
It is probably best to reboot and test that everything works. That will be very simple to do.
Arriving at these settings is pretty much a trial and error procedure. There is a way to make things easier which I mentioned in the previous post. Here are my suggestions:
- Download a 30 second or so
- Open a second
sshsession to run
alsamixerconcurrently while playing the
aplayin the other
aplayto work first, it is really hard to see if
arecordis working when the output is not working.
- Remember that, by default, both the audio input and output are muted.
Using a Google Home Mini has opened up my eyes to potential uses of a single board computer such as the Orange Pi Zero or Raspberry Pi. One possibility is as a server for Internet radio. I have been testing this possibility using the Music Player Daemon. There is a lot of information on the Web for this type of thing. Turns out that it is deceptively easy to get the daemon up on Orange Pi Zero running the latest version of Armbian.
Ouch!, that is big. Strictly speaking the command line interface
mpc is not needed as I will be using my own Python based
client. Furthermore, the daemon can be controlled directly over
telnet but the latter is not installed by default. It is just
easier to include
mpc to test
mpd. Amazingly, it
works out of the box for the most part.
However adjusting the volume does not work. The
file has to be tweaked.
Cards on the table, I did not come up with these settings on my own. I merely "borrowed" them from a configuration file created by the DietPi installation of mpd.
I noticed that among the many libraries installed there was the
libwavpack1. Testing showed that Media Player Daemon can play
wav encoded files.
I think this will prove quite useful, but I am getting ahead of myself.
After some searching on the Web and testing out a few text to speech engines, I settled on Pico TTS which is available as a Debian package. It has reasonably good voice synthesis in English, French, Italian, etc. Installation could not be simpler.
Check that the binary file has been installed.
The short usage message shows how words will be translated to sound.
The program creates a
wav sound file from the words appended
to the command line. Then
aplay can be used to play the sound.
The original Debian image from Armbian is much heavier than the pared-down version from
DietPi. For example, both versions 2.7 and 3.5 of
Python are installed. However
python3-env are not.
Note the suggested
sudo update-command-not-found command
that will henceforth mean that when an application is not found, a suggestion
of how to install it may be displayed. The next step is to install
the two missing Python 3 packages. Since I am avoiding using Python 2,
the equivalent packages are not installed for that older version.
As explained in my post Python 3 virtual environments,
pip will not
be installed in the system but it will be automatically added in virtual
environments. Since this newer version of Armbian
comes with Python 3.5.3, I followed the instructions for a
Ubuntu installation of the virtual environment tools
in the post. (I will soon be updating that post to remove the reference to
the different distributions and replacing it with the more meaningful
Python 3 version numbers).
Now, I will show how to use the
with Python wrappers. The test for
picotts will be reading
of local weather forecasts obtained with HTML requests. The test for
mpd will be to play a radio stream. This will be done in
a Python 3 virtual environment.
First step is to create a project directory and then to create a Python virtual environment in the directory.
If you prefer not using my
then you can replace them with
python3 -m venv pvenv,
source pvenv/bin/activate and
pip install -U pip setuptools wheel.
The next step is to the
Environment and Climate Change Canada a department of the Canadian Federal Government publishes weather reports, advisories and forecasts in many forms such as the box on the right and what it calls Public Text Bulletins. These are HTML pages, one for each province and territory. The political region is divided into forecast areas where the weather will presumably be homogeneous. Each area forecast is preceded by the list of geographic regions contained in the area. Don't reread that sentence, just have a look at part of tonight's short-term forecast for New-Brusnwick and things will be clear.
FPCN14 CWHX 022000 Forecasts for New Brunswick issued by Environment Canada at 4:00 p.m. AST Friday 2 February 2018 for tonight Saturday and Saturday night. The next scheduled forecast will be issued at 5:00 a.m. AST Saturday. Fredericton and Southern York County Oromocto and Sunbury County Stanley - Doaktown - Blackville Area Woodstock and Carleton County. Tonight..Snow ending early this evening then clearing. Blowing snow over exposed areas this evening. Wind northwest 20 km/h gusting to 40. Low minus 20. Cold wind chill minus 30 overnight. Saturday..Mainly sunny. Wind west 20 km/h. High minus 12. Cold wind chill minus 30. Saturday night..A few clouds. Increasing cloudiness near midnight then light snow. Wind west 20 km/h becoming light in the evening. Low minus 16 except minus 21 in low lying areas. Moncton and Southeast New Brunswick Fundy National Park. Flash freeze warning in effect. Tonight..Snow ending this evening then clearing. Amount 5 cm. Blowing snow over exposed areas this evening. Wind northwest 30 km/h gusting to 50 becoming west 20 overnight. Low minus 19. Cold wind chill minus 28. Saturday..Mainly sunny. Wind west 20 km/h gusting to 40. High minus 13. Cold wind chill minus 29 in the morning. Saturday night..A few clouds. Increasing cloudiness after midnight then 60 percent chance of flurries overnight. Wind west 20 km/h becoming southeast 20 near midnight. Low minus 16 with temperature rising to minus 10 by morning. Kent County Kouchibouguac National Park. Tonight..Snow ending this evening then clearing. Amount 5 cm. Blowing snow over exposed areas early this evening. Wind northwest 30 km/h gusting to 50 becoming west 20 overnight. Low minus 20. Cold wind chill minus 28. Saturday..Mainly sunny. Wind west 20 km/h gusting to 40. High minus 13. Cold wind chill minus 30 in the morning. Saturday night..Partly cloudy. 30 percent chance of flurries overnight. Wind west 20 km/h becoming light in the evening. Low minus 18. Cold wind chill minus 25.
I live in Southeast New Brunswick about 20 minutes away from Moncton. Tonight our forecast is the same as that of the Fundy National Park Area. Sometimes we are lumped in with Kent County and Kouchibouguac National Park.
The complete forecast is contained in the only <pre></pre> HTML tags in the page. There is a preamble with dates and times and then each forecast area begins with a blank line, a list of concerned geographic areas that terminates with a period "." and then the weather forecast itself. I wrote the following Python function to extract the weather to request the HTML page containing the forecast and then to extract the preamble and the forecast for a specified geographical region.
Before discussing the function, it is worthwhile to repeat that I am learning Python and in no way am I suggesting that this is the optimal or even correct way of doing things. Indeed, I welcome all suggested improvements.
The obvious first step is to make the HTML request. This is
simple given the excellent
requests library that was
installed in the previous section. The HTML source code will be available as
The second step is to get the forecast. I used the string
function to get the index of the <pre> and </pre> tags. Everything
between these indices is extracted, split into lines and assigned to the
The idea is then to go through all these lines, one by one, copying
only those that are needed into a variable called
Three flags are set up, to keep track of where we are in the file. There
inheader which will be true from the second line in the
file until an empty line is reached. From then on, when a blank line
inregions is set to true and will remain so
until a period "." is found. While
inregions is true, a
test for the desired region is done. When it is found
will be set true. Whenever an empty line is found, then
which is a Python wrapper around
pico2wave is invoked to
say the content of
inpredict is true.
There is some substitution done before converting the text to speech. In the header, "AST" and "ADT" which stand for Atlantic Standard and Daylight Time are removed. Pico TTS does not know what to make of those acronyms. In the forecast itself, there are sequences of two periods ".." after each day or part of day. Pico TTS has problems with that, but replacing them with a comma to represent a rest seems to be effective.
picotts.py, I just adapted some code found on the
Web. There are three interesting parts to it. First the
file is stored in a directory on a temporary file system in RAM which
cuts down on the wear and tear of the SD card and improves speed.
Second, it turns out that in addition to the command line option to set the output language, the volume, pitch and speed of the output voice can be changed using tags similar to HTML markup tags in the text stream.
Third, I find that the first word to be said by
lost. This is the first word overall. First words in subsequent calls to
picotts.say are uttered. It's almost as if it takes
time to "connect" to the engine. In the
_main routine of
forecast.py, I added an initial call to
with just a space and then delayed before continuing. It is a stop-gap
measure which does not work very well.
forecast.py: Python script to obtain and play weather forecast picotts.py: Python wrapper for
pico2wavetext to speech engine configuration.py: Python script to read configuration file configuration.json: Configuration file for
To test, set up the Python virtual environment and then
- for short and long term predictions for Moncton in French:
- for long term predictions only for Saint John in English:
The most complicated thing about playing Internet radio is managing stations and URLs. I will show here part of the solution I came up with. It remains work in progress. I have yet to complete an updating utility, but it is not necessary at this point.
Here is a look at part of the file,
radio.json, in which
information about Internet radio stations is stored.
As can be seen, any number of URLS and categories can be saved for each station along with the station name and a geographical location. I am thinking of adding a language field.
Multiple URLs are of interest because some stations have numerous streams corresponding to different sampling rates. Also, some stations appear to be rebroadcast by other providers and so on. And it is common to find URLs that are out of date on the Web.
Some stations have long names that are difficult to remember. It is unreasonable to expect that users will specify the completely correct name when giving oral commands. Of course, the speech to text conversion in not foolproof either. It quickly became apparent that Google Home, for one, uses some sort of "nearest match" algorithm to identify the station it will play. So I had to devise my own and it remains to see if it will prove acceptable when my list of radio stations is fully populated.
The idea behind this mess is simple. Given a requested radio station
name, the function goes through the list of all known stations calculating
inCount which is the number of words in requested name
not contained in a known station name and
outCount which is
the number of words in the station name not found in the requested name.
requested name: "Ici Radio Canada Moncton" station name: "ICI Radio-Canada Première Nouveau-Brunswick" inCount: 1 ("Moncton") outCount: 3 ("Première", "Nouveau", "Brunswick")When both counts are 0, a perfect match has been found. Otherwise, the nearest match will have the lowest
outCount. I tried to use the sum of these counts but got disappointing results. So currently, I am keeping track of the station with the lowest
inCountin the variable
leftSourceand of the station with the lowest
outCountin the variable
rightSource. At the end, if no perfect match has been found, then the better match of the two stations is returned along with the corresponding
outCount. Any suggestion on how to improve that last bit of decision-making in choosing between the
rightSourceor indeed about going about the whole matching names would be welcome. In the meantime, I might look into using the
soundextype algorithms which I have used with success in another context.
The routine that plays an Internet radio station is not too complicated. It can be invoked with the (approximate) name of a radio station or with an already specified station. The function goes through the list of URLs until it finds one that works. If that URL is not the first one in the list, it is moved to the top and the configuration file is marked as changed so that it will be saved.
iradio.py Python script contains other methods, including
resume whose meaning
is pretty clear.
I noticed that France Musique Concerts Radio France was choppy while there was no problem playing the same station on the desktop. Perhaps the AllWinner H2+ cannot quite keep up with the 192 kbps rate or the problem has to do with the network interface. The OPiZ could play Radio BBC 3 at 128 kbps without stuttering. Or maybe it has to do with the encoding method used.
This is all preliminary as I have said before. I am thinking of changing the radio configuration file. The play method could stand to be improved in order to optionally select a particular URL, especially in light of the preceding paragraph. There are lots of debugging print statements that need to be removed and better integration with the voice synthesis system is needed.
Another possibility that needs investigation is the use of another media player. VLC is often mentioned and I have had success using it for video feeds on my Ubuntu desktop and Android devices.
radio.json: JSON file with information for a few Internet radio stations iradio.py: Python script to play Internet radio stations with
To test, set up the Python virtual environment and then
- to listen to BBC Radio 3:
- to test the pause and resume functions:
- to test using mpd and pico tts at the same time:
- to obtain complete mpd status:
picotts.pymust be in the same directory as
iradio.pyfor this to work.