Saturday, June 6, 2020

IR Transmitter and google home integration using ESP8266

Final result !






Why ?


I have ceiling speakers in the kitchen, which I love !

However, I had to either use the remote, or walk up to the wall unit very time I wanted to change the volume or power on/off the speakers.

This got even more annoying when somebody was cooking in the kitchen and the volume had to be increased , or if someone was on the phone, and it had to be muted.

So, I figured, I have the remote for it, Why not try to build a cheap device that can send the IR signals to the remote, and then integrate it with google home J

How  ?


So, here are the high level steps that I followed. I will go into more detail later.

1.     Figure out the IR codes for the remote using an ESP8266 and IR receiver
2.     Test sending the IR codes from the ESP8266
3.     Setup wifi on the ESP8266
4.     Do IoT integration for the ESP8266 with google home

Here are the parts that I used for this project :

1.     ESP8266 module (pack of 3) = $11.99

3.     Breadboard and wires

4.     330 ohm resistors and generic LEDs ( I reused what I had)
5.     Case LEDs ( I reused what I had)
6.     Multimeter ( I reused what I had)

So, here are the detailed steps

·      Familiarize yourself with the pin layouts of the ESP8266

·      Figure out the IR codes for the remote using an ESP8266 and IR receiver
o   Use the IRrecvDumpV2 example sketch that comes with the IRremoteESP8266 library to capture the IR codes for your remote
o   Double check your receiver diode type (as the pinouts may be different). I used the standalone diode, so the pinouts are as follows

·      Test sending the IR codes from the ESP8266
o   Use the IRsendDemo example sketch that comes with the IRremoteESP8266 library to test sending the IR codes to your device
o   You can see the details from my sketch as well.
·      Setup wifi on the ESP8266
o   You can see the details from my sketch as well.
·      Do IoT integration for the ESP8266 with google home
o   Setup device and variables on remoteme.org




o   Enable google assistant extension on iftt.com and then setup applets with triggers and responses on iftt.com

o   Test it out

·      Final sketch


#include <Arduino.h>
#include <IRsend.h>
#include <IRrecv.h>
#include <IRremoteESP8266.h>
#include <IRutils.h>
#include <RemoteMe.h>
#include <RemoteMeSocketConnector.h>
#include <RemoteMeDirectWebSocketConnector.h>
#include <ESP8266WiFi.h>
#include <Pinger.h>
#include <Ticker.h>

#define WIFI_NAME "YOUR-WIFI-SSID"
#define WIFI_PASSWORD "YOUR-WIFI-PASSW)RD"
#define DEVICE_ID 1
#define DEVICE_NAME "YOUR-ESP8266-DEVICE-NAME"
#define TOKEN "YOUR-API-TOKEN-FROM-REMOTEME.ORG"

RemoteMe& remoteMe = RemoteMe::getInstance(TOKEN, DEVICE_ID);

Ticker periodic_checker;

Pinger pinger;

// ==================== start of TUNEABLE PARAMETERS ====================

// GPIO to use to control the IR LED circuit. Recommended: 4 (D2).
// LED minus pin connected to GPIO 4 = D2
// LED plus pin connected to 3.3 V

const uint16_t kIrLedPin = 4;

//const IPAddress remote_ip(8, 8, 8, 8); // Remote host

// GPIO to use to control the hearbeat green LED circuit. Recommended: 15 (D8).
// LED minus pin connected to GPIO 15 = D8 via 470 ohm resistor
// LED plus pin connected to 3.3 V

// GPIO to use to control the error status red LED circuit. Recommended: 13 (D7).
// LED minus pin connected to GPIO 13 = D7 via 470 ohm resistor
// LED plus pin connected to 3.3 V

const uint16_t HearbeatLedPin = 15;
const uint16_t ErrorLedPin = 13;

// kFrequency is the modulation frequency all UNKNOWN messages will be sent at.
const uint16_t kFrequency = 38;  // in KHz. e.g. 38kHz.

// The IR transmitter.
IRsend irsend(kIrLedPin);

// Somewhere to store the captured message.
decode_results results;

// First code is with the uno, second code is with the ESP2866
// OnQ remote
//689FA729 = Power toggle  = 1E536C0D
//7B8AD561 = Volume up = 91C28805
//2035FA5E = Volume down = F621F31E
//71603D32 = Mute = D678818A
//EBA6D9DE = next source = 1A6C3CDE
//B980E06F = previous source = B7A32C1B

// REPLACE WITH YOUR IR CODES ....

uint16_t power_toggle_button_array[167] = {5962, 1218,  1218, 1168,  1218, 1168,  618, 574,  620, 572,  620, 1170,  618, 574,  620, 1168,  620, 1170,  1214, 572,  1216, 550,  642, 1168,  620, 570,  620, 8958,  3010, 1166,  1216, 1168,  1216, 1168,  620, 574,  618, 574,  618, 1170,  620, 572,  620, 1170,  618, 1170,  1216, 572,  1218, 572,  620, 1170,  618, 572,  618, 8958,  3010, 1166,  1216, 1168,  1218, 1168,  620, 572,  620, 572,  620, 1170,  618, 572,  620, 1168,  620, 1170,  1216, 572,  1216, 572,  620, 1168,  620, 570,  620, 8958,  3010, 1164,  1218, 1168,  1216, 1168,  620, 572,  620, 572,  620, 1168,  620, 572,  620, 1168,  620, 1170,  1216, 572,  1218, 572,  618, 1168,  620, 570,  620, 8958,  3008, 1166,  1216, 1170,  1218, 1168,  618, 574,  618, 574,  618, 1170,  620, 574,  618, 1170,  620, 1168,  1218, 572,  1218, 572,  620, 1170,  620, 570,  620, 8956,  3012, 1164,  1218, 1168,  1216, 1168,  620, 574,  618, 574,  618, 1170,  618, 574,  620, 1170,  618, 1170,  1216, 572,  1216, 572,  620, 1170,  618, 570,  620};  // UNKNOWN A6E38D8B
uint16_t volume_up_button_array[167] = {5960, 1216,  1218, 1168,  1218, 1168,  620, 572,  620, 572,  620, 1168,  620, 574,  620, 1168,  620, 1168,  1218, 1168,  620, 1168,  620, 1168,  1218, 570,  618, 8958,  3010, 1164,  1218, 1166,  1218, 1168,  620, 574,  620, 570,  622, 1168,  622, 572,  622, 1168,  620, 1168,  1220, 1168,  620, 1168,  620, 1168,  1218, 568,  620, 8958,  3012, 1164,  1218, 1166,  1218, 1168,  620, 570,  620, 572,  622, 1168,  620, 572,  620, 1168,  620, 1168,  1218, 1168,  620, 1170,  618, 1168,  1218, 570,  620, 8956,  3012, 1164,  1218, 1168,  1218, 1168,  620, 570,  622, 572,  620, 1168,  620, 572,  620, 1168,  620, 1170,  1218, 1168,  620, 1168,  620, 1168,  1218, 570,  620, 8956,  3012, 1164,  1218, 1168,  1218, 1166,  620, 572,  620, 572,  620, 1168,  620, 572,  620, 1168,  622, 1168,  1218, 1168,  620, 1168,  622, 1168,  1218, 568,  622, 8954,  3012, 1164,  1218, 1168,  1216, 1168,  620, 572,  620, 572,  620, 1170,  618, 570,  622, 1168,  622, 1168,  1218, 1168,  620, 1168,  620, 1170,  1216, 570,  620};  // UNKNOWN 85D38CB
uint16_t volume_down_button_array[167] = {5984, 1192,  1216, 1168,  1218, 1168,  618, 574,  620, 572,  620, 1170,  618, 574,  618, 1170,  1216, 572,  620, 572,  620, 572,  618, 600,  592, 1194,  594, 8956,  3010, 1166,  1218, 1166,  1218, 1168,  620, 600,  594, 572,  618, 1170,  618, 574,  620, 1168,  1218, 572,  620, 572,  620, 572,  620, 572,  618, 1168,  620, 8958,  3010, 1166,  1216, 1168,  1216, 1168,  620, 572,  620, 572,  620, 1170,  618, 598,  594, 1168,  1218, 572,  618, 574,  620, 572,  620, 572,  620, 1166,  620, 8956,  3012, 1166,  1216, 1168,  1218, 1168,  620, 572,  620, 572,  620, 1168,  620, 572,  620, 1168,  1216, 572,  620, 572,  620, 572,  620, 570,  620, 1166,  620, 8958,  3010, 1164,  1218, 1168,  1218, 1168,  620, 572,  620, 572,  620, 1168,  620, 572,  622, 1168,  1218, 570,  620, 572,  620, 572,  620, 572,  620, 1166,  620, 8956,  3010, 1166,  1216, 1168,  1216, 1168,  620, 572,  620, 572,  620, 1170,  620, 572,  620, 1170,  1216, 572,  618, 574,  620, 574,  618, 574,  618, 1168,  620};  // UNKNOWN 75FFD1AF
uint16_t volume_mute_button_array[111] = {5984, 1192,  1216, 1166,  1220, 1168,  620, 600,  594, 572,  618, 1170,  620, 598,  594, 1170,  618, 1170,  1216, 598,  1190, 1168,  618, 1170,  618, 1194,  592, 8958,  3010, 1166,  1216, 1194,  1192, 1194,  594, 598,  594, 574,  618, 1196,  592, 572,  620, 1170,  620, 1168,  1218, 570,  1272, 1114,  618, 1170,  620, 1166,  620, 8958,  3010, 1166,  1216, 1168,  1218, 1168,  620, 574,  618, 598,  594, 1196,  594, 600,  592, 1170,  618, 1170,  1216, 572,  1216, 1168,  618, 1170,  620, 1168,  618, 8958,  3010, 1192,  1192, 1168,  1216, 1170,  618, 600,  592, 574,  618, 1168,  620, 572,  620, 1194,  594, 1170,  1216, 598,  1190, 1194,  594, 1170,  616, 1168,  620};  // UNKNOWN 5F72B567


// ==================== end of TUNEABLE PARAMETERS ====================

//*************** CODE FOR COMFORTABLE VARIABLE SET *********************

inline void setDECREASE_VOLUME(boolean b) {remoteMe.getVariables()->setBoolean("DECREASE_VOLUME", b); }
inline void setINCREASE_VOLUME(boolean b) {remoteMe.getVariables()->setBoolean("INCREASE_VOLUME", b); }
inline void setTURN_OFF(boolean b) {remoteMe.getVariables()->setBoolean("TURN_OFF", b); }
inline void setTURN_ON(boolean b) {remoteMe.getVariables()->setBoolean("TURN_ON", b); }
inline void setMUTE_VOLUME(boolean b) {remoteMe.getVariables()->setBoolean("MUTE_VOLUME", b); }

void heartbeat_check();

//*************** IMPLEMENT FUNCTIONS BELOW *********************

void onDECREASE_VOLUMEChange(boolean b) {
  Serial.printf("onDECREASE_VOLUMEChange: b: %d\n",b);
    if (b)
    {
      digitalWrite(LED_BUILTIN, HIGH);  // Turn the LED off by making the voltage HIGH
      irsend.sendRaw(volume_down_button_array, 167, kFrequency);
      delay(2000);
      digitalWrite(LED_BUILTIN, LOW);  // Turn the LED off by making the voltage LOW
      Serial.println("Sent IR code for volume down");
      setDECREASE_VOLUME (false);
    }
}

void onINCREASE_VOLUMEChange(boolean b) {
  Serial.printf("onINCREASE_VOLUMEChange: b: %d\n",b);
    if (b)
    {
      digitalWrite(LED_BUILTIN, HIGH);  // Turn the LED off by making the voltage HIGH
       irsend.sendRaw(volume_up_button_array, 167, kFrequency);
      delay(2000);
      digitalWrite(LED_BUILTIN, LOW);  // Turn the LED off by making the voltage LOW
     Serial.println("Sent IR code for volume up");
      setINCREASE_VOLUME (false);
    }
}

void onMUTE_VOLUMEChange(boolean b) {
  Serial.printf("onMUTE_VOLUMEChange: b: %d\n",b);
    if (b)
    {
      digitalWrite(LED_BUILTIN, HIGH);  // Turn the LED off by making the voltage HIGH
      irsend.sendRaw(volume_mute_button_array, 111, kFrequency);
      delay(2000);
      digitalWrite(LED_BUILTIN, LOW);  // Turn the LED off by making the voltage LOW
      Serial.println("Sent IR code for volume mute");
      setMUTE_VOLUME (false);
    }
}


void onTURN_OFFChange(boolean b) {
  Serial.printf("onTURN_OFFChange: b: %d\n",b);
    if (b)
    {
      digitalWrite(LED_BUILTIN, HIGH);  // Turn the LED off by making the voltage HIGH

      irsend.sendRaw(power_toggle_button_array, 167, kFrequency);
      delay(2000);
      digitalWrite(LED_BUILTIN, LOW);  // Turn the LED off by making the voltage LOW
      Serial.println("Sent IR code for power toggle");
      setTURN_OFF (false);
    }

}
void onTURN_ONChange(boolean b) {
  Serial.printf("onTURN_ONChange: b: %d\n",b);
  if (b)
  {
      digitalWrite(LED_BUILTIN, HIGH);  // Turn the LED off by making the voltage HIGH
      irsend.sendRaw(power_toggle_button_array, 167, kFrequency);
      delay(2000);
      digitalWrite(LED_BUILTIN, LOW);  // Turn the LED off by making the voltage LOW
      Serial.println("Sent IR code for power toggle");
      setTURN_ON (false );
  }
}



void setup() {

  irsend.begin();       // Start up the IR sender.

  pinMode(LED_BUILTIN, OUTPUT);     // Initialize the LED_BUILTIN pin as an output
  pinMode(HearbeatLedPin, OUTPUT);  // Initialize the hearbeat led pin as an output
  pinMode(ErrorLedPin, OUTPUT);  // Initialize the error led pin as an output


  digitalWrite(LED_BUILTIN, LOW);   // Turn the LED on (Note that LOW is the voltage level
  // but actually the LED is on; this is because
  // it is active low on the ESP-01)

  digitalWrite(HearbeatLedPin, HIGH);  // Turn the LED off by making the voltage HIGH
  digitalWrite(ErrorLedPin, LOW);   // Turn the LED on (Note that LOW is the voltage level


  Serial.begin(9600);

   Serial.print("Serial debug started...");
   Serial.println();

  // Station mode, where the NodeMCU device joins an existing network,
  WiFi.mode(WIFI_STA) ;


  WiFi.begin(WIFI_NAME, WIFI_PASSWORD);

  while (WiFi.status() != WL_CONNECTED) {
   digitalWrite(LED_BUILTIN, LOW);   // Turn the LED on (Note that LOW is the voltage level
   // but actually the LED is on; this is because
   // it is active low on the ESP-01)
    delay(500);
    Serial.print("Waiting for wifi connection...");
    Serial.println();
    digitalWrite(LED_BUILTIN, HIGH);  // Turn the LED off by making the voltage HIGH
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  digitalWrite(HearbeatLedPin, LOW);   // Turn the LED on (Note that LOW is the voltage level
  digitalWrite(ErrorLedPin, HIGH);  // Turn the LED off by making the voltage HIGH


  //Setup the listeners on state changes
  remoteMe.getVariables()->observeBoolean("DECREASE_VOLUME" ,onDECREASE_VOLUMEChange);
  remoteMe.getVariables()->observeBoolean("INCREASE_VOLUME" ,onINCREASE_VOLUMEChange);
  remoteMe.getVariables()->observeBoolean("MUTE_VOLUME" ,onMUTE_VOLUMEChange);
  remoteMe.getVariables()->observeBoolean("TURN_OFF" ,onTURN_OFFChange);
  remoteMe.getVariables()->observeBoolean("TURN_ON" ,onTURN_ONChange);

  remoteMe.setConnector(new RemoteMeSocketConnector());
  remoteMe.setDirectConnector(new RemoteMeDirectWebSocketConnector());
  remoteMe.sendRegisterDeviceMessage(DEVICE_NAME);

   //Initialize Ticker every 10 minutes
    periodic_checker.attach(600, heartbeat_check);
}


void loop() {
  remoteMe.loop();
  //heartbeat_check();
}

void heartbeat_check()
{

// Ping
  if(pinger.Ping(IPAddress(8,8,8,8)) == true)
    {
       Serial.println("Success in pinging 8.8.8.8 !!");
       digitalWrite(HearbeatLedPin, LOW);   // Turn the LED on (Note that LOW is the voltage level
       digitalWrite(ErrorLedPin, HIGH);  // Turn the LED off by making the voltage HIGH

    } else
    {
       Serial.println("Error in pinging 8.8.8.8 :(");
       digitalWrite(HearbeatLedPin, HIGH);  // Turn the LED off by making the voltage HIGH
       digitalWrite(ErrorLedPin, LOW);   // Turn the LED on (Note that LOW is the voltage level
     }

}


Tuesday, May 12, 2020

Building a Roomba virtual wall with an Arduino nano


I recently bought a Roomba, and love it. I had the need for a couple of virtual wall sensors, and they were approximately $40-45 each, and I begin to think that it was a rip off…

Anyhow, after searching the web, I saw the Roomba vacuums had an open interface for integrations and  also that the virtual wall basically is an IR emitter that generates a 1ms ON, 1ms OFF signal continuously at 38 KHz.

So, that was the breakthrough….

First of all, here is the finished project :









Here is a quick list of parts that I bought for my project

·      Arduino nano (bought 3 for $13.99)

·      IR emitter (bought 10 for $5.78)

·      Case (bought 5 for $10.99)


So basically, I was able to build 3 virtual walls for ~ $30 (not counting the effort and labor, lol)

I already had the following parts (that you will also need in order to complete the project)
·      Resistors
·      Connecting wires
·      Soldering iron
·      Hot glue gun
·      Drill

Instructions are pretty simple, and I used this github project as a reference as well.

Basically, you connect the IR LED with it's positive leg connected via a current-limiting resistor(220 Ohm or similar) to the +5v, and it's negative leg to Pin3 aka D3 (hardcoded via the IRemote.h library)

I did make the following variations for my project though :

·      In some of the virtual walls, I made 2 perpendicular LEDs by connecting them in parallel
·      I tried powering with the 9v battery, and it works fine for upto 2-3 days before draining the battery out. So, if you must use a battery, use a portable USB charger which does not auto shutoff due to low current usage.

Saturday, March 21, 2020

How to make a jukebox with Raspberry PI





1.     Installing the OS on the raspberry pi
a.     Install Raspbian lite on the pi (I used etcher on a mac)


b.     Plugin a usb keyboard, connect via HDMI, and use a wired ethernet cable for initial setup
c.     Login to the pi locally (pi/raspberry)
d.     Set advanced settings by using sudo raspi-config

                                               i.     Increase GPU memory to (at least) 256 (Advanced Options -> Memory Split)
                                             ii.     Enable ssh (Advanced Options -> Interfacing options).
e.     Now, you can do the following operations on the pi remotely using ssh (you can get the IP address of the pi from the hdmi screen and login is still the same, i.e.  pi/raspberry)

2.     Software installation and basic configuration
a.     PS : I have followed the basic steps from this link, and customized them for my own use. It might be useful reference, so now you have it

chmod +x ./install.sh
source ./install.sh

3.     Music and skin configuration
a.     Configure /home/pi/rpi-fruitbox-master/Music to point to your mp3 source folder (I have chosen to point this to an smb share on my home nas, but you could point it to a local usb stick as well)
b.     Test it first by manual commands, e.g.

mkdir /home/pi/Music
sudo mount -t cifs -o vers=1.0,_netdev,username=****,password=****,uid=1000,gid=1000 //192.168.1.8/public/Media/fruitbox/Music /home/pi/Music

c.     Now, make the change persistent across reboots by adding the following entry to /etc/fstab as follows :

//192.168.1.8/public/Media/fruitbox/Music  /home/pi/Music  cifs  defaults,vers=1.0,_netdev,username=****,password=****,uid=1000,gid=1000 0 0

4.     Controller configuration
a.     Plugin the controller and verify operation

I bought this generic xbox controller from amazon for $16.99, but you can use other ones too.

b.     Configure fruitbox to accept and map the controller buttons


                                               i.     First install the xboxdrv package (sudo apt install xboxdrv)

                                             ii.     Run the following command to monitor inputs (sudo xboxdrv)

                                            iii.     Press a button (e.g. A) and see the registered change as noted below (the output scrolls by fast, but you can see it in terminal history)
X1:     0 Y1:  -256  X2:     0 Y2:  -256  du:0 dd:0 dl:0 dr:0  back:0 guide:0 start:0  TL:0 TR:0  A:0 B:0 X:0 Y:0  LB:0 RB:0  LT:  0 RT:  0

X1:     0 Y1:  -256  X2:     0 Y2:  -256  du:0 dd:0 dl:0 dr:0  back:0 guide:0 start:0  TL:0 TR:0  A:1 B:0 X:0 Y:0  LB:0 RB:0  LT:  0 RT:  0

                                            iv.     Now, you are ready to map the buttons in fruitbox to your newly recognized controller (see how have chosen the button configuration for my xbox controller)

cd rpi-fruitbox-master/

sudo ./fruitbox --config-buttons

Follow instructions on screen and press the controller keys for the controls. Press the ESC key for the buttons you want to skip.

                                              v.     Now edit the fruitbox.btn (sudo vi /home/pi/rpi-fruitbox-master/fruitbox.btn) and remove the duplicates for the buttons you have skipped (you will see the escape key repeated multiple times, just comment the duplicates out and only keep the one for ButtonQuit)

Here is how my fruitbox.btn file looks like

# fruitbox v1.16 button mapping file
# Missing buttons will assume their default values (see user guide)

ButtonQuit       = JoyButton 0 316
#ButtonCoin1      = JoyButton 0 316
#ButtonCoin2      = JoyButton 0 316
#ButtonCoin3      = JoyButton 0 316
#ButtonCoin4      = JoyButton 0 316
#ButtonVol+       = JoyButton 0 316
#ButtonVol-       = JoyButton 0 316
#ButtonRandom     = JoyButton 0 316
ButtonSelect     = JoyButton 0 314
ButtonSkip       = JoyButton 0 315
#ButtonPause      = JoyButton 0 316
ButtonUp         = JoyStick 0 4 1
ButtonDown       = JoyStick 0 4 0
ButtonLeft       = JoyButton 0 310
ButtonRight      = JoyButton 0 311
#ButtonLeftJump   = JoyButton 0 316
#ButtonRightJump  = JoyButton 0 316
#ButtonLeftAlpha  = JoyButton 0 316
#ButtonRightAlpha = JoyButton 0 316
#ButtonAuto       = JoyButton 0 316
#ButtonLoop       = JoyButton 0 316
#ButtonFree       = JoyButton 0 316
#ButtonClear      = JoyButton 0 316
#ButtonMute       = JoyButton 0 316
ButtonPowerOff   = JoyStick 0 2 0
#Button0          = JoyButton 0 316
Button1          = JoyStick 0 17 0
Button2          = JoyStick 0 16 0
Button3          = JoyStick 0 17 1
Button4          = JoyStick 0 16 1
#Button5          = JoyButton 0 316
#Button6          = JoyButton 0 316
#Button7          = JoyButton 0 316
#Button8          = JoyButton 0 316
#Button9          = JoyButton 0 316
ButtonA          = JoyButton 0 304
ButtonB          = JoyButton 0 305
ButtonC          = JoyButton 0 308
ButtonD          = JoyButton 0 307
#ButtonF          = JoyButton 0 316
#ButtonG          = JoyButton 0 316
#ButtonH          = JoyButton 0 316
#ButtonI          = JoyButton 0 316
#ButtonJ          = JoyButton 0 316
#ButtonK          = JoyButton 0 316
#ButtonFlag1      = JoyButton 0 316
#ButtonFlag2      = JoyButton 0 316
#ButtonFlag3      = JoyButton 0 316
#ButtonFlag4      = JoyButton 0 316


5.     Test out the jukebox with your skin real quick

cd rpi-fruitbox-master/
sudo ./fruitbox --cfg skins/WallJuke/fruitbox.cfg

You will see a database building message for the files in your Music folder that we configured earlier

If all was done correctly, you should be able to select tracks using the A-D, 1-4 buttons, skip them, and then exit as well

6.     Some final changes to the raspbian OS to start things just right on bootup
a.     First we need to set the automatic login to the user pi

sudo raspi-config
·  select: 3 Boot Options Configure options for start-up
·  Then: B1 Desktop/CLI Choose whether to boot into the desktop environment or the command line

And finally:

·      B2 Console Autologin Text console, automatically logged in as 'pi' user
·      Exit by selecting <Finish>

And to the question: Would you like to reboot now?
·      Reply<Yes>

Go ahead and change the password for the user pi as well.

Here are some screenshots of the steps mentioned above to guide you





At this point we verify that when Raspbian restarts, the password is not required to log in as user pi

b.     Now we have to automate the start and stop of the fruitbox program
                                               i.     Create the run script (/home/pi/runjb.sh)

#!/bin/bash

. /home/pi/jukebox.conf

ISRUNNING=`ps -ef | grep fruitbox | grep -v grep|wc -l`
TY=`ps ax | grep $$ | tail -n 1 | awk '{ print $2 }'`

if [ $ISRUNNING -lt 1 ] && [ "$TY" = "tty1" ]
 then

        cd /home/pi/rpi-fruitbox-master
        sudo ./fruitbox --cfg ./skins/$THEME/$CONFFILE
        echo "Found $TY. Should initiate a shutdown, as fruitbox has exited..."
        #sudo halt
        #sudo /sbin/init 0
else
        echo "Not the main console, nothing to do..."
fi
                                             ii.     Create the default config file and uncomment your skin (/home/pi/jukebox.conf)

#!/bin/bash


USER="pi"
HD="/home/"$USER
CONFFILE="fruitbox.cfg"
THEME=WallJuke
#THEME=Modern
#THEME=Granite
#THEME=MikeTV
#THEME=NumberOne
#THEME=Splat
#THEME=TouchOne
#THEME=WallSmall
#THEME=Wurly

                                            iii.     Set the correct permissions and add the run script to run automatically when pi starts and automatically logs you in

sudo chmod 770 /home/pi/runjb.sh
sudo chmod 770 /home/pi/jukebox.conf
sudo echo "sudo /home/pi/runjb.sh" >> /home/pi/.bashrc

                                            iv.     Now reboot (sudo reboot) and verify auto launch and proper operation

7.     Final end product !




8.     Other miscellaneous tips
a.     Audio configuration

Use sudo alsamixer to tune the audio. If you wanted to output to the audio out jack instead of hdmi, you can use sudo raspi-config to change that selection as well



b.     Using a local USB stick for audio
Here are basic instructions on how to mount a USB stick (e.g. I am using an FAT32 Cruze USB stick )

sudo lsblk -o UUID,NAME,FSTYPE,SIZE,MOUNTPOINT,LABEL,MODEL
UUID                                 NAME    FSTYPE  SIZE MOUNTPOINT LABEL MODEL
                                     sda            29.8G                  Cruze
5E62-D6E5                            └─sda1  exfat  29.8G            USB  
                                     mmcblk0         7.6G                 
00D2-4212                            ─mmcblk0p1
                                     │       vfat     63M            RECOVERY
                                     │                                    
                                     ─mmcblk0p2
                                     │                 1K                 
06604c8a-3da2-49e8-8007-3f14c26b363b ─mmcblk0p5
                                     │       ext4     32M            SETTINGS
                                     │                                     
5CB0-16FE                            ─mmcblk0p6
                                     │       vfat    256M /boot      boot 
6077dab4-00f2-4bf9-9e70-818da9fb1fed └─mmcblk0p7
                                             ext4    7.2G /          root 

sudo apt install exfat-fuse

sudo blkid
/dev/mmcblk0: PTUUID="64e82d74" PTTYPE="dos"
/dev/mmcblk0p1: SEC_TYPE="msdos" LABEL_FATBOOT="RECOVERY" LABEL="RECOVERY" UUID="00D2-4212" TYPE="vfat" PARTUUID="64e82d74-01"
/dev/mmcblk0p5: LABEL="SETTINGS" UUID="06604c8a-3da2-49e8-8007-3f14c26b363b" TYPE="ext4" PARTUUID="64e82d74-05"
/dev/mmcblk0p6: LABEL_FATBOOT="boot" LABEL="boot" UUID="5CB0-16FE" TYPE="vfat" PARTUUID="64e82d74-06"
/dev/mmcblk0p7: LABEL="root" UUID="6077dab4-00f2-4bf9-9e70-818da9fb1fed" TYPE="ext4" PARTUUID="64e82d74-07"
/dev/sda1: LABEL="USB" UUID="5E62-D6E5" TYPE="exfat"

Add the following line to /etc/fstab
UUID=5E62-D6E5 /mnt/usb exfat defaults,auto,users,rw,nofail 0 0

ls -l /mnt/usb/Music/
total 160
drwxrwxrwx 1 root root 32768 Apr  2  2016 'Led Zeppelin Remasters [Disc 1]'
drwxrwxrwx 1 root root 32768 Feb 27  2016 'Led Zeppelin Remasters [Disc 2]'
drwxrwxrwx 1 root root 32768 Apr  2  2016 'Led Zeppelin Remasters [Disc 3]'
drwxrwxrwx 1 root root 32768 Mar  7 04:12 'Ozzy Osbourne'
drwxrwxrwx 1 root root 32768 Apr  2  2016 'Peter Frampton'


Of course, change the above instructions to point to /home/pi/Music


c.     Wifi configuration
sudo raspi-config  (Network options, Wifi, Follow instructions to set country, SSID, password)




Reboot and check if both connections are active

ifconfig -a
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.1.211  netmask 255.255.255.0  broadcast 192.168.1.255
        inet6 fe80::24e3:a7a5:5e0e:5156  prefixlen 64  scopeid 0x20<link>
        ether b8:27:eb:df:72:56  txqueuelen 1000  (Ethernet)
        RX packets 652  bytes 205280 (200.4 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 150  bytes 23053 (22.5 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

wlan0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.1.212  netmask 255.255.255.0  broadcast 192.168.1.255
        inet6 fe80::46c:c76c:16ad:26b  prefixlen 64  scopeid 0x20<link>
        ether 00:13:ef:50:0e:f7  txqueuelen 1000  (Ethernet)
        RX packets 421  bytes 143549 (140.1 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 28  bytes 4449 (4.3 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

d.     How to use itunes to convert your owned music CDs to MP3s
                                               i.     Here is a good link from apple support that you can use to convert your own music CDs to mp3
                                             ii.     Couple of pointers though… Make sure you select mp3 encoder in the import settings,  and the itunes folder as organized, select import CD and eject.
                                            iii.     With the right settings above, you could just start inserting your CDs and let itunes convert it to mp3, and store it in a nicely organized folder and then eject the cd, so that you could just make a weekend project of converting your music CDs to mp3s.
e.     MP3 tagging
                                               i.     Fruitbox looks for the mp3 tags in the file and creates a database in /home/pi/fruitbox.db
                                             ii.     /home/pi/fuitbox.db is a text file, and you could manually edit it make corrections.
                                            iii.     However, you should use some mp3 tagging software to make the corrections.
                                            iv.     If you make mp3 tag changes, just remove the fruitbox.db file, and it will get recreated on next launch of fruitbox


9.     Made some more changes to skin and buttons and final configuration to look as follows 



  • After converting all my CDs to mp3's , I realized that I prefer the CD album style layout, so I started with the Granite skin and made the following minor tweaks
    • Added a picture of my dogs on top left and top right :)
    • Tweaked the "now playing" to show the album art of the CD thats playing
    • Moved the "coming up next to next tile"
    • Re did my button selections to just have the following key things
      • 0 , 1, 2, 3 , Quit, Skip, Up, Down, Left, Right , Clear, Pause, Select, Random
    • Providing my skins/Granite/fruitbox.cfg below 
    • Providing my fruitbox.btn below
    • Also, as you might be aware, MacOS creates the ._ files on NAS drives, so I was getting hidden mp3 files etc. which was causing havoc in the media library, so anytime I copy / update the files on my NAS drive now, I use the following two commands on the mac to find and clean the ._ files
      • find . -type f -name "._*" -exec ls "{}" \;
      • find . -type f -name "._*" -exec rm "{}" \;

skins/Granite/fruitbox.cfg 
[general]
SkinName = Granite
SkinDescription = An Albums based 70s skin
SkinSize = 1920 1080
Database = ../fruitbox.db
MusicPath = ../Music/
PageMode = Albums
SortSongsBy Album
SelectButtons = 0
SelectButtons = 0123
#SelectButtons = A
#SelectButtons = 1234
AutoSelect = Yes
SelectTimeout = 150
SelectHoldTimeout = 150
AutoPageTurnTime = 3000
MaxPlaylistLength = 100
AlbumPageMissingArtwork = xgiphy11.jpg
NowPlayingMissingArtwork = splat.png
AlbumPageArtworkMode = Folder
NowPlayingArtworkMode = Folder
AutoPlay = yes
AutoPlayGap = 3000
PlaysPerCoin1 = 0
SongsPerPage = 25
PageSize = 380 720
PairSongs = no
#PageMoveStyle = 20 4 no
PageMoveStyle = 0 4 no
#PageMoveStyle = 12 4 yes
AlbumText = 0 255 255 255 255 centre false true false 0 4 360
ArtistText = 1 128 32 32 255 centre true true false 0 44 360
SongText = 2 0 0 0 255 centre false true false 0 264 260
#SongText = 2 0 0 0 255 centre false true false 0 264 140
PairedSongDescription = AlbumTitle
AlbumArtSize = 160 160
AlbumArtOffset = 120 84
AlbumArtAngle = 0
AlbumPageLineSpacing = 0

[sounds]
LoadSong   = 100 recpop.wav
UnloadSong   = 100 recpop.wav
InsertCoin = 70  coindrop.wav
PageMove   = 70  pagemove.wav

[font]
#File = bauh93.ttf
File = TravellingFixed.ttf
Height = 40

[font]
#File = bauh93.ttf
#File = atwriter.ttf
#Height = 26
#File = hatten.ttf
#Height = 25
File = typewriter.ttf
Height = 36

[font]
#File = bauh93.ttf
#File = dotmatrx.ttf
File = atwriter.ttf
Height = 28

[font]
File = typewriter.ttf
Height = 26

[status]
Contents background_mix.txt
Bitmap = mesh01.jpg
Bitmap = mesh02.jpg
Bitmap = mesh03.jpg
Bitmap = mesh04.jpg
Bitmap = mesh05.jpg
Bitmap = mesh06.jpg
Bitmap = mesh07.jpg
Bitmap = mesh08.jpg
Bitmap = mesh09.jpg
Bitmap = mesh10.jpg
Bitmap = mesh11.jpg
Bitmap = mesh12.jpg
Bitmap = mesh13.jpg
Bitmap = mesh14.jpg
Bitmap = mesh15.jpg
Bitmap = mesh16.jpg
Bitmap = mesh17.jpg
Bitmap = mesh18.jpg
Bitmap = mesh19.jpg
Bitmap = mesh20.jpg
Bitmap = mesh21.jpg
Bitmap = mesh22.jpg
Bitmap = mesh23.jpg
Bitmap = mesh24.jpg
Bitmap = mesh25.jpg
Bitmap = mesh26.jpg
Bitmap = mesh27.jpg
Bitmap = mesh28.jpg
Bitmap = mesh29.jpg
Bitmap = mesh30.jpg
Bitmap = mesh31.jpg
Bitmap = mesh32.jpg
Bitmap = mesh33.jpg
Bitmap = mesh34.jpg
Bitmap = mesh35.jpg
Bitmap = mesh36.jpg
Bitmap = mesh37.jpg
Bitmap = mesh38.jpg
Bitmap = mesh39.jpg
Bitmap = mesh40.jpg
Bitmap = mesh41.jpg
Bitmap = mesh42.jpg
Bitmap = mesh43.jpg
Bitmap = mesh44.jpg
Bitmap = mesh45.jpg
Bitmap = mesh46.jpg
Bitmap = mesh47.jpg
Bitmap = mesh48.jpg
Bitmap = mesh49.jpg
Bitmap = mesh50.jpg
#SkinSize = 1920 1080
#Size width height (in pixels)
#Position x y (in pixels)
#Size = 1904 320
#Position = 8 8
Size = 400 320
Position = 1450 8
TimerTickPeriod = 2

[page]
Background = glass-red.png
Position = 80 320

[page]
Background = glass-green.png
Position = 540 320

[page]
Background = glass-blue.png
Position = 1000 320

[page]
Background = glass-yellow.png
Position = 1460 320

[bitmap]
File = foreground.png
Position = 0 0
Size = 1920 1080

#[status]
#Contents background_mix.txt
#Bitmap = ozzy-01.jpg
#Bitmap = mojo-01.jpg
##Bitmap = ozzy-01.png
##Bitmap = mojo-01.png
##Bitmap = hmv-logo-01.png
##Position = 920 130
#Position = 0 0
#Size = 75 75
#TimerTickPeriod = 40

[status]
Position = 80 80
Size = 840 160
Contents = now_playing.txt
Text = 3 255 255 255 255 left false false false 0 0
TimerTickPeriod = 100

[status]
Position = 790 90
Size = 120 120
Contents = now_playing_artwork.txt

[status]
Position = 1000 80
Size = 840 160
Contents = coming_up.txt
Text = 3 255 255 255 255 left false false false 0 0

[bitmap]
File = elapsed_bar.png
Position = 160 220
#Position = 80 220
Size = 680 8
HorzScale = NowPlayingElapsedTime
HorzScaleMode = Stretched

#[joystick]
#Bitmap = arrow.png
#Offset = -60 140
#Size 240 400

[bitmap]
File = mojo-01.jpg
Position = 0 0
Size = 75 75

[bitmap]
File = ozzy-01.jpg
Position = 1845 0
Size = 75 75

fruitbox.btn 
# fruitbox v1.16 button mapping file
# Missing buttons will assume their default values (see user guide)

ButtonQuit       = JoyStick 0 5 0
#ButtonCoin1      = JoyStick 0 5 0
#ButtonCoin2      = JoyStick 0 5 0
#ButtonCoin3      = JoyStick 0 5 0
#ButtonCoin4      = JoyStick 0 5 0
#ButtonVol+       = JoyStick 0 5 0
#ButtonVol-       = JoyStick 0 5 0
ButtonRandom     = JoyButton 0 307
ButtonSelect     = JoyButton 0 314
ButtonSkip       = JoyButton 0 305
ButtonPause      = JoyButton 0 304
ButtonUp         = JoyStick 0 4 1
ButtonDown       = JoyStick 0 4 0
ButtonLeft       = JoyButton 0 310
ButtonRight      = JoyButton 0 311
#ButtonLeftJump   = JoyStick 0 5 0
#ButtonRightJump  = JoyStick 0 5 0
#ButtonLeftAlpha  = JoyStick 0 5 1
#ButtonRightAlpha = JoyStick 0 5 1
#ButtonAuto       = JoyStick 0 5 1
#ButtonLoop       = JoyStick 0 5 1
#ButtonFree       = JoyStick 0 5 1
ButtonClear      = JoyButton 0 308
#ButtonMute       = JoyStick 0 5 1
#ButtonPowerOff   = JoyStick 0 5 1
Button0          = JoyStick 0 17 0
Button1          = JoyStick 0 16 0
Button2          = JoyStick 0 17 1
Button3          = JoyStick 0 16 1
#Button4          = JoyStick 0 5 1
#Button5          = JoyStick 0 5 1
#Button6          = JoyStick 0 5 1
#Button7          = JoyStick 0 5 1
#Button8          = JoyStick 0 5 1
#Button9          = JoyStick 0 5 1
#ButtonA          = JoyStick 0 5 1
#ButtonB          = JoyStick 0 5 1
#ButtonC          = JoyStick 0 5 1
#ButtonD          = JoyStick 0 5 1
#ButtonE          = JoyStick 0 5 1
#ButtonF          = JoyStick 0 5 1
#ButtonG          = JoyStick 0 5 1
#ButtonH          = JoyStick 0 5 1
#ButtonI          = JoyStick 0 5 1
#ButtonJ          = JoyStick 0 5 1
#ButtonK          = JoyStick 0 5 1
#ButtonFlag1      = JoyStick 0 5 1
#ButtonFlag2      = JoyStick 0 5 1
#ButtonFlag3      = JoyStick 0 5 1
#ButtonFlag4      = JoyStick 0 5 1