Mateus De Camargo Jonas
Published © GPL3+

Smart Light Switch (UIUC - SE 423 - TI LaunchPad Project)

Google Assistant integration with openHAB on Orange Pi Zero, telling Texas Instruments MSP430G2553 Launchpad to drive servos on light switch

IntermediateFull instructions provided15 hours845
Smart Light Switch (UIUC - SE 423 - TI LaunchPad Project)

Things used in this project

Hardware components

Texas Instruments MSP430G2553 LaunchPad
×1
Orange Pi Zero
×1
SparkFun 5v regulator
×1
SparkFun Jumper Wires Kit
×1

Software apps and online services

openHAB
Code Composer Studio
Texas Instruments Code Composer Studio
Google Assistant
Maker service
IFTTT Maker service

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Multitool, Screwdriver
Multitool, Screwdriver

Story

Read more

Custom parts and enclosures

Servo Switch Plate for HS311 Servo

This is the switch plate for a HS311 Servo which fits around a standard light switch.

Servo Arm for servo

This is the servo arm attachment for both the 9gram and HS311 servos.

Servo Switch Plate for 9gram servo

This is the switch plate for a 9gram servo which fits around a standard wall light switch.

Schematics

Wiring Schematic

The power for the MSP430G2553 is provided by the Mirco-USB port plugged into a wall outlet.
The power for the 5V Regulator is provided by a 6V 2A wall plug.

Code

openHAB Items file setup

Java
This is the file in which the declarations for the items in openHAB are made in. It must sit within the " /openhab/items " folder and must be file type " .items ".
In openHAB, items refer to objects which can have several different types, depending on what you need for your home automation, be it a string, a switch, a slider, color wheel, etc.
In our case, we use it for a string which will hold the output of when our switch for the light is run. As well as including a couple switches and strings which link to an command line executable shell file which is what allows us to manage the GPIO ports on the Orange Pi Zero.
String YourText "[%s]" //String used in PaperUI //[%s] sets item type to string for use in PaperUI
Switch TestLight <light> //Switch item used to receive command from Google Assistant //<light> determines icon in PaperUI

//channel setting allows specific commands based on binding, in this case shell execute binding
Switch LightOn {channel="exec:command:TurnLightOn:run"} //Switch in charge of running thing TurnLightOn
Switch LightOff {channel="exec:command:TurnLightOff:run"} //Switch in charge of running thing TurnLightOff

String LONlastexec {channel="exec:command:TurnLightOn:lastexecution"} //String for logging last execution on TurnLightOn
String LOFFlastexec {channel="exec:command:TurnLightOff:lastexecution"} //String for logging last execution on TurnLightOff

String LON_out {channel="exec:command:TurnLightOn:output"} //String for logging output of TurnLightOn
String LOFF_out {channel="exec:command:TurnLightOff:output"} //String for logging output of TurnLightOff

Switch StartupGPIO {channel="exec:command:StartupGPIO:run"} //Swithc in charge of running thing StartupGPIO

openHAB Things file setup

Java
This is the file in which the declarations for the things in openHAB are made in. It must sit within the " /openhab/things" folder and must be file type " .things ".
Things are objects which link items to services, plugins, bindings, etc.
In our case, we will be linking some command line executable shell scripts which manage the GPIO pins on the Orange Pi Zero, to specific Commands.
When these commands are called, the shell scripts they are linked to are executed.
//Things are the link between shell scripts and openhab. 
//set interval to 0 to does not auto-repeat, set timeout = 5 seconds in case something goes wrong
//set autorun to false so only triggers when state of linked item chages
Thing exec:command:TurnLightOn [command="/etc/openhab2/scripts/LightOn.sh", interval=0; timeout=5, autorun = false]
Thing exec:command:TurnLightOff [command="/etc/openhab2/scripts/LightOff.sh", interval=0; timeout=5, autorun = false]

//set autorun to true so triggers when initiated
Thing exec:command:StartupGPIO [command="/etc/openhab2/scripts/start.sh", interval=0; timeout=5, autorun = true]

openHAB Rules file setup

Java
This is the file in which the declarations for the rules used in openHAB are made in. This file must reside in " /openhab/rules " and must be of file type " .rules "
Rules are a set of If Statements constantly running in the background.
In this case, we want to check whenever the state of our Switch "Test Light" changed to either ON or OFF.
When this happens, their respective shell scripts are called to either tell the LaunchPad to turn on or off the light switch via the servo. I have implemented a little pause to ensure the MSP430G2553 has sufficient time to receive the data and execute the command to the servo.
rule "Light Update to ON" //Rule that triggers when item TestLight changes to ON via Google Assistant. 
when
        Item TestLight changed to ON
then
        LightOn.sendCommand(ON) //switch item LightOn to ON until its done performing script. 

        while(LightOn.state != OFF) { //sleep until script is done
                Thread::sleep(100)
        }

        Thread::sleep(400) //give some buffer time to make sure everything runs properly.
        YourText.postUpdate(TestLight.state.toString) //send state of TestLight to string in PaperUI
end

rule "Light Update to OFF" //Rule that triggers when item TestLight changes to OFF via Google Assistant. 
when
        Item TestLight changed to OFF
then
        LightOff.sendCommand(ON) //switch item LightOn to ON until its done performing script. 
		
		while (LightOn.state != OFF) { //sleep until script is done
                Thread::sleep(100)
        }

        Thread::sleep(400) //give some buffer time to make sure everything runs properly.
		YourText.postUpdate(TestLight.state.toString) //send state of TestLight to string in PaperUI
end

openHAB Sitemap file setup

Java
This is the file in which the declarations for the sitemap used in the openHAB browser PaperUI are made in. It must sit within the " /openhab/sitemaps" folder and must be file type " .sitemap".
The PaperUI is a web interface you can use to display your items previously declared. To do this you must install the binding Cloud Service, which allows you to connect remotely to your openHAB server without needing to setup a local server on your device.
sitemap default label="Light Switch" //PaperUI settings
{
        Frame label="Lights"{ //new section called Lights
                Switch item=TestLight label="Room Light Switch" //Displaying Item TestLight with label "Room Light Switch"
                Text item=YourText label="State:" //Displaying string of state of item TestLight
        }
}

openHAB Startup Rules file setup

Java
This is the file in which the declarations for the startup rules in openHAB are made in. It must sit within the " /openhab/rules" folder and must be file type " .rules ".
Rules are a set of If Statements which are constantly running in the background.
In this case we want to run the startup script whenever the system turns on. This script will declare the GPIO pins to their proper state for communication with the MSP430G2553 LaunchPad.
rule "Startup GPIO" //Rule file that is triggered when the system starts up, switches StartupGPIO to ON until its done running, then resets to OFF
 when
        System started
 then
        StartupGPIO.sendCommand(ON)
end

MSP430G2553 Source Code

C/C++
This is the file deployed to the Launchpad which determines the run state of the servo, depending on the inputs from the orange pi zero on GPIO pins 2.6 and 2.7.
The servo pulse width is determined by the Time Compare in pin 2.4.
#include "msp430g2553.h"
#include "UART.h"

void print_every(int rate);

char newprint = 0;
long NumOn = 0;
long NumOff = 0;
int statevar = 1;
int timecheck = 0;
int angle = 90;
int PulseWidth = 3025;
char IntakeChar = 0;
int offPos = 3800; //servo position to turn On Light
int onPos = 2800; //servo position to turn Off Light
int restPos = 3300; //neutral servo position

void main(void) {

    WDTCTL = WDTPW + WDTHOLD;                 // Stop WDT

    if (CALBC1_16MHZ ==0xFF || CALDCO_16MHZ == 0xFF) while(1);

    DCOCTL = CALDCO_16MHZ;    // Set uC to run at approximately 16 Mhz
    BCSCTL1 = CALBC1_16MHZ;

    // Timer A Config
    TACCTL0 = CCIE;             // Enable Periodic interrupt
    TACCR0 = 16000;                // period = 1ms
    TACTL = TASSEL_2 + MC_1; // source SMCLK, up mode

    //P2.6 and P2.7 inputs from OrangePi
    P2DIR &= ~0xC0; // Set Direction for 2.6 and 2.7 to input
    P2REN |= 0xC0; // Enable on-board resistors to act as switches
    P2OUT |= 0xC0; // Set resistor to pulldown
    P2SEL &= ~0xC0; // Set pins to I/O
    P2SEL2 &= ~0xC0; // ^^^

    //PWM Configs for servo on p2.4
    P2DIR |= 0x10; //P2.4 output direction
    P2SEL |= 0x10; //P2.4 config
    P2SEL2 &= ~0x10; //P2.4 config
    TA1CCR0 = 40000; //PWM Period of 20ms
    TA1CCTL2 = OUTMOD_7; //outpout mode: reset/set for servo2
    TA1CTL = TASSEL_2 + ID_3 + MC_1; // clock source select SMCLK, Input divider 8 mode, control: up mode

    Init_UART(115200,1);    // Initialize UART for 115200 baud serial communication
    _BIS_SR(GIE);       // Enable global interrupt

    while(1) {  // Low priority Slow computation items go inside this while loop.  Very few (if anyt) items in the HWs will go inside this while loop
        // The newprint variable is set to 1 inside the function "print_every(rate)" at the given rate
        if ( (newprint == 1) && (senddone == 1) )  { // senddone is set to 1 after UART transmission is complete
            // only one UART_printf can be called every 15ms
            UART_printf("PW: %d \n\r", PulseWidth);
            newprint = 0;
        }
    }
}

// Timer A0 interrupt service routine
#pragma vector=TIMER0_A0_VECTOR
__interrupt void Timer_A (void)
{
    print_every(250);

    if ((P2IN &= 0x40) == 0x40) { //p2.6 active // Light On
        PulseWidth = onPos;
    }else if ((P2IN &= 0x80) == 0x80) { //p2.7 active // Light Off
        PulseWidth = offPos;
    }else { //when not changing state, sit in neutral position in case google assistant brakes.
        PulseWidth = restPos;
    }
    TA1CCR2 = PulseWidth; //aka to rotate servo to specific angle

    timecheck++;
}


// ADC 10 ISR - Called when a sequence of conversions (A7-A0) have completed
#pragma vector=ADC10_VECTOR
__interrupt void ADC10_ISR(void) {
}


// USCI Transmit ISR - Called when TXBUF is empty (ready to accept another character)
#pragma vector=USCIAB0TX_VECTOR
__interrupt void USCI0TX_ISR(void) {

    if(IFG2&UCA0TXIFG) {        // USCI_A0 requested TX interrupt
        if(printf_flag) {
            if (currentindex == txcount) {
                senddone = 1;
                printf_flag = 0;
                IFG2 &= ~UCA0TXIFG;
            } else {
                UCA0TXBUF = printbuff[currentindex];
                currentindex++;
            }
        } else if(UART_flag) {
            if(!donesending) {
                UCA0TXBUF = txbuff[txindex];
                if(txbuff[txindex] == 255) {
                    donesending = 1;
                    txindex = 0;
                }
                else txindex++;
            }
        } else {  // interrupt after sendchar call so just set senddone flag since only one char is sent
            senddone = 1;
        }

        IFG2 &= ~UCA0TXIFG;
    }

    if(IFG2&UCB0TXIFG) {    // USCI_B0 requested TX interrupt (UCB0TXBUF is empty)
        IFG2 &= ~UCB0TXIFG;   // clear IFG
    }
}


// USCI Receive ISR - Called when shift register has been transferred to RXBUF
// Indicates completion of TX/RX operation
#pragma vector=USCIAB0RX_VECTOR
__interrupt void USCI0RX_ISR(void) {

    if(IFG2&UCB0RXIFG) {  // USCI_B0 requested RX interrupt (UCB0RXBUF is full)

        IFG2 &= ~UCB0RXIFG;   // clear IFG
    }

    if(IFG2&UCA0RXIFG) {  // USCI_A0 requested RX interrupt (UCA0RXBUF is full)
        IntakeChar = UCA0RXBUF; //read in value sent over UART from terminal port
        sendchar(IntakeChar); //echo value back to console
        //if statements to determine what angle value is desired to change servo's PW on the fly
        if (IntakeChar == 61){ //ASCII char equals to =
            angle = angle + 5;
        }else if (IntakeChar == 45){ //ASCII char equals to -
            angle = angle - 5;
        }else{ //any other ASCII char
            angle = 90;
        }
        IFG2 &= ~UCA0RXIFG;
    }

}

// This function takes care of all the timing for printing to UART
// Rate determined by how often the function is called in Timer ISR
int print_timecheck = 0;
void print_every(int rate) {
    if (rate < 15) {
        rate = 15;
    }
    if (rate > 10000) {
        rate = 10000;
    }
    print_timecheck++;
    if (print_timecheck == rate) {
        print_timecheck = 0;
        newprint = 1;
    }

}

Credits

Mateus De Camargo Jonas

Mateus De Camargo Jonas

1 project β€’ 1 follower

Comments