Wednesday, March 28, 2018

Raspberry Pi Compliment/Joke Generator

Background

During my day job I spend much of my time implementing Microsoft IT software. My company has a dedicated onsite TAM (Technical account manager) tasked with guiding us through the Microsoft support organization whenever there are issues. The unfortunate part for him is that his job is to basically act as the on site Microsoft whipping boy.

After a particularly brutal couple weeks of issues we decided to give him a break and throw him a little event to show our appreciation for all he does (and put a few of us on his good side in case he ever cracks and goes postal). His last name is Deeter so the first annual "Deeter Day" was born.

Having a pile of $3 Google AIY voice kits and pi zeros from the recent Microcenter pi day sale I built a gag compliment box for the event which when pressed would speak/play a random compliment directed at our guest of honor.  In my head I pictured Stuart Smalley doing his daily affirmation from classic SNL so I labeled the box "Deeter's Daily Affirmations"

If you want to build something similar to either play random audio clips, songs or speak one of various statements here is how to build it:

Items needed:


  • Raspberry Pi - 2/3/Zero/ZeroW
  • If using a zero - 2x20 header needs to be soldered on
  • Google AIY Kit to provide the button, speaker and amplifier board
If you don't have the Google AIY kit you can make it work with another form of audio out (bluetooth/headphone jack or  I2S Amplifier board) you will also need a push button

Step 1 - Collect audio files or statements to convert into audio files

If you already have MP3 or WAV files you can skip to step 3. I couldn't find prerecorded compliments so decided to create my own audio files via text to speech.
Googling around I found a starter list of compliments here
https://www.happier.com/blog/nice-things-to-say-100-compliments/

To add a bit of humor I found some Chuck Norris 'facts' and copy/paste and search and replace generated a list of personalized Deeter facts.
Some here http://codesqueeze.com/the-ultimate-top-25-chuck-norris-the-programmer-jokes/
Another source https://www.telegraph.co.uk/films/2016/04/19/the-20-best-chuck-norris-facts/

Compile all your statements/jokes/compliments into a single file (one statement per line.)

Step 2: Convert any text to Audio Files

The Pi has a few speech libraries info here but in quick testing the speech wasn't great on a zero so I decided to preprocess them all into spoken audio files on my PC using Powershell and the Windows SAPI components

Use this powershell to convert to wav files:

#Based on https://gist.github.com/lazywinadmin/51619a0e47f4c8e7a8b7
Add-Type -AssemblyName System.speech
mkdir C:\users\$env:username\Desktop\Compliments
$Statements=Get-Content "C:\users\$env:username\Desktop\Compliments.txt"
$FileName= 1
foreach ($Statement in $Statements)
{

$speak = New-Object System.Speech.Synthesis.SpeechSynthesizer
$speak.SelectVoice('Microsoft Zira Desktop')
$speak.rate = 0
$speak.Volume = 100
$speak.SetOutputToWaveFile("C:\users\$env:username\Desktop\Compliments\" + $FileName + "-Compliment.wav")
$speak.Speak($Statement)
$speak.Dispose() # this stop and save the wav file
++$FileName
}

Step 3: Setup your Pi/Google AIY voice board

Since I'm using the Google AIY audio board I downloaded the Google AIY Raspian image since it has integrated audio drivers.
  1. Download the AIY image to an SD Card 
  2. Connect your pi to a monitor, keyboard and mouse (make sure hdmi is connected before boot)
  3. Boot into the image - after a minute or two you should have a google desktop
  4. Connect to your network and note the assigned IP (you can hover over the wireless icon)
  5. To make the following steps easier Enable VNC under Preferences ->Raspberry Pi Configuration->Interfaces and EnablingVNC.
  6. Set a fixed resolution to use via VNC under the System tab (1280 x 720 works well)
  7. If using MP3 files - install mpg123  (the image has integrated wav file support)
  8. sudo apt-get install mpg123
  9.  I used Node-red to run the project so make sure to upgrade the included node-red by opening the console and running: update-nodejs-and-nodered Note: Don't update any other components as I had something break the audio driver.
  10. Also configure Node-red to run as a service. Reboot when complete with sudo reboot
  11. sudo systemctl enable nodered.service
Attach the audio board to your pi (if using a zero you need to solder in headers). Test audio by running the Check Audio Script on the desktop and ensure audio plays.

Step 4: Copy audio files to the Pi

Get the files onto the Pi using your tool of choice. I find it easiest to use the file transfer built into VNC viewer. You can find info on it here: https://www.realvnc.com/en/connect/docs/file-transfer.html
The node-red flow you will import is configured to look for them under Documents\Audio but you can place them anywhere as long as you edit the code.


Step 5: Create a Node-Red flow to play a file/compliment upon press of the Google AIY Kit button

  1. Fire up node-red. On a different system connect to the webpage it hosts (http://IPAddress:1880). You will be presented with the node red flow designer.
  2. In the upper right there is an icon with 3 lines (I will call the hamburger), select that and go to Manage Palette
  3. Select the install tab and install the nodes:
    1. node-red-contrib-fs
    2. node-red-dashboard
  4. Open the flow file found here and copy the entire contents to the clipboard: https://github.com/lawrence-jeff/PiComplimentGenerator 
  5. Open the Hamburger -> Select Import from Clipboard. Paste in the flow code.

Step 6: Enjoy

The simple usage is to just use the integrated button and get a random file, you can also go to the webpage on the device (http://IPAddress:1880/ui) and select a compliment from the dropdown which will play it remotely.

The flow also supports attaching a USB keyboard and if you press a key it will look for a file containing the keycode of the pressed button.


Wednesday, November 15, 2017

IR to Network Bridge with an ESP8266

A year or so ago we cut the cord (cable) and started relying on a combination of OTA tuning via a Windows media center, the OTA tuner in our TV and a Roku stick. The original thought was the limited programming selection would push us to spend less time watching TV to focus on more productive things. (Spoiler - It never happened, somehow network programming filled the void.)

This setup has a pretty high WAF (wife approval factor) mainly since the media center handles IR and is controlled along with the TV via a single Harmony universal remote - but that damn Roku remote presents a number of issues:
  1. It doesn't use IR (some sort of proprietary RF) and therefore can't be setup in the Harmony or other IR based universal remote.
  2. It's also particularly small so seems inexplicably drawn into every nook and cranny in the furniture (or hidden by the kids)

One idea that was quickly dismissed (<WAF)
Attaching the small remote to the Harmony seemed a poor choice but something had to be done, the constant volume change within Roku apps had to be addressed!

One day I saw mention of a network API for the Roku and an idea was born... what if I setup a fake device in the Harmony and then used an ESP8266 to pickup the device IR and convert this to a corresponding network based Roku API call? The ESP was certainly capable of decoding IR (using one of the common Arduino libraries) and with it's on board WiFi it should be able to  make the REST API calls to the Roku.

A few hours later the IRtoRokuBridge was born. In a surprising twist this ends up even more capable than the original remote since you can program arbitrary shortcut buttons on the Harmony screen that take you directly into the requested app.


The code is pretty basic but it has been running well for 6 months or so. 

Two libraries/authors I leveraged and want to thank:

You can find the code/details on my Github page (https://github.com/lawrence-jeff/IRtoRokuBridge) a few things I learned:

  • Cheap off brand IR modules can give you issues with range and decode - switching my setup to use a name brand module (whatever Radio Shack sold 10 years ago) worked much better.
  • The Arduino code isn't as resilient as what you will find on a TV or other device, you really need to aim.. I kept meaning to implement a less exact match in the code but it works well enough that I never got around to it
  • If you use NEC codes holding down a button generates a unique repeat code which you need to handle. I found the Harmony sometimes sent these when not intended so via trial and error I filter out the first two repeat codes which makes it act better (this is for instances when you are holding down an arrow for instance to browse through the app menu)

This example uses the Roku API - but anything you can do on your home network you can now do via IR and a spare remote. Drop a comment if you do any other interesting things with IR control

Saturday, February 7, 2015

ESP8266 and OpenHAB (Part 2)

ESP8266 and OpenHAB (Part 2)

Improving MQTT Auto-connect 

In the last blog one of the issues I mentioned was the LUA code being 'finicky' about reconnects, I made some updates based on code I found on www.esp8266.com to address this as well as move some of the configurable aspects to variables.

Updated mqtt.lua


 --Init  
 DeviceID="Light_FF_Bath_Ceiling"  
 Broker="192.168.0.32"  
 --GPIO0 is connected to switch with internal pullup enabled  
 gpio.mode(3,gpio.INPUT,gpio.PULLUP)  
 --GPIO2 is connected to LED via resistor, initially off  
 gpio.mode(4,gpio.OUTPUT)  
 gpio.write(4,gpio.LOW)  
 m = mqtt.Client("ESP8266", 180, "user", "password")  
 m:lwt("/lwt", "ESP8266", 0, 0)  
 m:on("offline", function(con)   
    print ("Mqtt Reconnecting...")   
    tmr.alarm(1, 10000, 0, function()  
      m:connect(Broker, 1883, 0, function(conn)   
        print("Mqtt Connected to:" .. Broker)  
        mqtt_sub() --run the subscription function  
      end)  
    end)  
 end)  
 gpio.trig(3, "down",function (level)  
    local PinValue=gpio.read(4)  
    --Change the state  
    if (PinValue==1) then  
      --The read resets the output to 0, put it back  
      gpio.write(4,0)  
      print("Light was on, turn off")    
      m:publish("/openHAB/in/" .. DeviceID .. "/state","OFF",0,0)  
    else  
      gpio.write(4,1)  
      print("Light was off, turn on")    
      m:publish("/openHAB/in/" .. DeviceID .. "/state","ON",0,0)  
     end  
 end)  
 -- on publish message receive event  
 m:on("message", function(conn, topic, data)   
    print("Recieved:" .. topic .. ":" .. data)   
      if (data=="ON") then  
      print("Enabling LED")   
      gpio.write(4,gpio.HIGH)  
      m:publish("/openHAB/in/" .. DeviceID .. "/state","ON",0,0)  
    elseif (data=="OFF") then  
      print("Disabling LED")   
      gpio.write(4,gpio.LOW)  
      m:publish("/openHAB/in/" .. DeviceID .. "/state","OFF",0,0)  
    else  
      print("Invalid - Ignoring")   
    end   
 end)  
 function mqtt_sub()  
    m:subscribe("home/openHAB/out/" .. DeviceID .. "/command",0, function(conn)   
      print("Mqtt Subscribed to OpenHAB feed for device " .. DeviceID)  
    end)  
 end  
 tmr.alarm(0, 1000, 1, function()  
  if wifi.sta.status() == 5 and wifi.sta.getip() ~= nil then  
    tmr.stop(0)  
    m:connect(Broker, 1883, 0, function(conn)   
      print("Mqtt Connected to:" .. Broker)  
      mqtt_sub() --run the subscription function  
    end)  
  end  
 end)  

Improving the feedback of the OpenHAB item graphic


Another issue I had mentioned was my concern that the state of the switch changed in OpenHAB independent of whether the ESP8266 actually processed the request and made the update. One way to improve this is to disable the autoupdate property of the switch item. This will wait until the ESP8266 responds before updating the graphic indicating the light is on or off (unfortunately the toggle switch still changes). So the light bulb picture on the left doesn't change until the ESP publishes the state update but the toggle graphic on the right still updates immediately. So if your broker is off the switch shows on but the light shows off

For reference here is a screenshot when working








To fix this, open the items file that defines the switch
C:\openhab\configurations\items\demo.items

Find the entry for the Light_FF_Bath_Ceiling and add {autoupdate="false"} to the end as so:

 Switch Light_FF_Bath_Ceiling           "Ceiling"           (FF_Bath, Lights)     {autoupdate="false"}  

I find you need to refreh the browser before it works correctly


Open Issues

At this point the basics work pretty well, some major open issues though as far as resiliency:

  • The LUA mqtt code will crash if it tries to send or receive more than one message at the same time. This can happen if the button is pressed at the same time as the every 3 minute status update occurs.
  • Not very resilient to wifi outage (LUA Panic crash)
  • Rebooting the module sometimes doesn't start the code correctly
  • The bootloader outputs to GPIO2 upon boot so you will see the light flicker on reboot.


ESP8266 and OpenHAB

ESP8266, MQTT and OpenHAB


Here is a quick little demo on how to use the ESP8266 as an ultra cheap ($3) sensor/control node for OpenHAB.

Setup

ESP8266 (ESP-01) with nodemcu LUA firmware (1/24/2015 build or later)
Mosquitto (or equivalent) mqtt broker running on your network (stock config)
OpenHAB installed and running the demo site (add all MQTT modules)

Circuit

This represents an independently controlled device that you also want to also be able to view/control in OpenHAB. The ESP-01 only has 2 available GPIO pins so this setup connects a button to GPIO0 (must be high on boot) and a LED on GPIO2. Standard power, enable and serial connections not displayed.
Basic circuit - Switch on GPIO0, LED on GPIO2

Procedure


Load the following code on the ESP8266

(See updated post for latest code)

This code is pretty basic and is hard coded to control one of the OpenHAB demo items (FF bathroom ceiling light). Before uploading replace the two instances of the mqtt broker IP with your value. This assumes you have a working LUA instance (1/24/2015 build or greater) and it is already configured to connect to your wireless network.

I used ESPlorer to load the files 

mqtt.lua

gpio.mode(3,gpio.INPUT,gpio.PULLUP)
gpio.mode(4,gpio.OUTPUT)
gpio.write(4,gpio.LOW)

m = mqtt.Client("ESP8266", 180, "user", "password")
m:lwt("/lwt", "ESP8266", 0, 0)

m:on("offline", function(con) 
     print ("reconnecting...") 
     print(node.heap())
     tmr.alarm(1, 10000, 0, function()
          m:connect("192.168.0.32", 1883, 0)
     end)
end)

gpio.trig(3, "down",function (level)
      local PinValue=gpio.read(4)
      --Change the state
      if (PinValue==1) then
         --The read resets the output to 0, put it back
          gpio.write(4,0)
          print("Light was on, turn off")    
          m:publish("/openHAB/in/Light_FF_Bath_Ceiling/state","OFF",0,0)
      else
          gpio.write(4,1)
          print("Light was off, turn on")    
          m:publish("/openHAB/in/Light_FF_Bath_Ceiling/state","ON",0,0)
       end
end)

-- on publish message receive event
m:on("message", function(conn, topic, data) 
     print("Recieved:" .. topic .. ":" .. data) 
if (data=="ON") then
          print("Enabling LED") 
          gpio.write(4,gpio.HIGH)
          m:publish("/openHAB/in/Light_FF_Bath_Ceiling/state","ON",0,0)
     elseif (data=="OFF") then
          print("Disabling LED") 
          gpio.write(4,gpio.LOW)
          m:publish("/openHAB/in/Light_FF_Bath_Ceiling/state","OFF",0,0)
     else
          print("Invalid - Ignoring") 
     end 
end)

tmr.alarm(0, 1000, 1, function()
 if wifi.sta.status() == 5 then
     tmr.stop(0)
     m:connect("192.168.0.32", 1883, 0, function(conn) 
          print("connected")
          m:subscribe("home/openHAB/out/Light_FF_Bath_Ceiling/command",0, function(conn) 
               m:publish("Debug","Starting",0,0, function(conn) print("sent") end)
          end)
     end)
 end
end)

init.lua

dofile('mqtt.lua')


Test the circuit and mqtt

If you just uploaded the code you need to restart the device and ensure it reports Connected and Sent to the serial out. (LUA is finicky and you may need to reboot a couple times).
At this point it would be good to verify the circuit and MQTT components work. The switch should turn the light on and off and you should see your broker receive messages for each change. (If using Mosquitto run it in verbose mode to see the messages coming in/out. (use -v)).



Configure OpenHAB demo to connect to MQTT and process the ESP messages


Add these lines to the mqtt config section in  
C:\openhab\configurations\openhab.cfg (if under windows)
Replace the IP and port with your MQTT broker details

mqtt:broker.url=tcp://192.168.0.32:1883
mqtt:broker.clientId=openhab
mqtt-eventbus:broker=broker
mqtt-eventbus:commandPublishTopic=home/openHAB/out/${item}/command
mqtt-eventbus:statePublishTopic=home/openHAB/state/${item}/state
mqtt-eventbus:stateSubscribeTopic=/openHAB/in/${item}/state


The lines above create an eventbus which allows you to control all OpenHAB items via mqtt. You can also skip the bus and individually connect a single item to mqtt but this is an easier way to start.


Test it all

Open the demo OpenHAB site (replace localhost with your OpenHAB values)

http://localhost:8080/openhab.app?sitemap=demo

Expand FirstFloor ->Bathroom
If you toggle the Ceiling light it should toggle the state of your led, if you press the button it should control the led and update the page.

Next Steps

OpenHAB seems to assume things work, for instance when you change the light state it actually changes the website state before it receives confirmation from the ESP, this seems less than reliable. I wonder if its possible for it to be in a pending state until the ESP process the change and sends back updated state details. (See Part 2 for details on how this can be fixed)