Introduction: Shake Gesture Controlled Lamp Using Arduino

Intro
Ever wanted your own lamp that can be controlled by a shake gesture using your smartphone? Here is a simple guide on how to create your own Shake Gesture Controlled Lamp an Arduino.


Required materials
- Arduino (Uno used in this example)
- Relay
- HM-10 C2541 Bluetooth 4.0 Module (If using iOS, for Android: HC-05 Bluetooth Module works as well)
- Breadboard (Optional)
- 8x Jumper wires (5x male-male and 3x male-female used in this tutorial)
- Light bulb socket
- Light bulb
- Power plug cable

Tip: Soldering is not necessary when using 3 male-female wires.

Step 1: Step 1: Connect the Bluetooth Module to the Arduino

Connect the pins of the Bluetooth module to the Arduino. On the bluetooth module, you can find 4 pins: Power/VCC (Red wire), Ground/GND (Black wire), TXD (Green wire) and RXD (Yellow wire). Connect the red wire to the 3.3V port on your Arduino. Connect the black wire to the GND port on your Arduino. Connect the green wire to the Digital 0-port on your Arduino and the yellow wire to the Digital 1-port on your Arduino.

Step 2: Step 2: Connect the Relay to the Arduino

The relay has three pins: Power, Ground and Signal. The power and ground pins are powering the relay module, and the signal pin is used to control the state (open or closed) I am using the three male-to-female jumper wires.

Connect the red wire (power) to the 5V port on the Arduino. Connect the black wire (ground) to the GND port on your Arduino. Finally connect the white wire (signal) to a digital output port (port 8 used in this example).

Step 3: Step 3: Connect the Light Bulb Socket to the Relay Module

Now comes the tricky part: Connecting the light bulb socket to the relay module. At the end of your power plug cable you'll have (most of the times) three cables: blue, brown and yellow/green (or 2 for power and one for ground). You'll only use the two cables for power. Connect one of the two cables to the relay (center port), and the other directly to the terminal block on the light bulb socket. You'll have to cut a small part of the power plug cable because you'll need an extra small cable that connects to the NO (Normally Open) port on the relay and to the terminal block of the light bulb socket.

Step 4: Step 4: Arduino Coding!

There's not much code though. What we are going to do basically is using the SoftwareSerial framework to communicate with our bluetooth module.

#include

SoftwareSerial mySerial(10, 11); // RX, TX

void setup() { Serial.begin(9600); pinMode(8,OUTPUT);

mySerial.begin(38400); Serial.setTimeout(25); }

void loop() {

delay(25); if (Serial.available()) { String str = Serial.readString(); if(str == "1") { Serial.println("RETURN:ON"); digitalWrite(8, HIGH); } if(str == "0") { Serial.println("RETURN:OFF"); digitalWrite(8, LOW); } } }

Our app is sending a message in the form of a string, which the Arduino can read by Serial.readString(). If the string is equal to '1', we are sending a return message: "RETURN:ON" and we set the 8 pin output to HIGH. Same goes for '0'. The return messages are used in the app for two-way-communication, so we can verify that the light is turned on or off.

Step 5: Step 5: Writing the IOS App

The iOS App is pretty complicated though. We are using the default iOS CoreBluetooth framework to connect to the Arduino. Also, we import the CoreLocation and CoreMotion frameworks (for detecting the location heading in degrees and the shake gesture motion events).

The app automatically connects to any bluetooth device with the name: HMSoft, so the user doesn't has to select the bluetooth device from a list of devices.

ViewController.h

//
// ViewController.h

// ShakeControlTest

/

/ Created by Max Kievits on 15-12-17.

// Copyright © 2017 MaxMedia-Apps. All rights reserved.

//

#import <UIKit/UIKit.h>

#import <CoreMotion/CoreMotion.h>

#import <CoreLocation/CoreLocation.h>

#import <CoreBluetooth/CoreBluetooth.h>

@interface ViewController : UIViewController {

CLLocationManager *locationManager;

IBOutlet UILabel *orientationLabel;

float degrees;

NSMutableArray *devicesArray;

bool blockSearching;

bool lightOn;

IBOutlet UIProgressView *progressView;

bool connected;

IBOutlet UIImageView *ovalImageView;

NSInteger calibratedLampDegrees;

}

@property (strong, nonatomic) CBCentralManager *centralManager;

@property (strong, nonatomic) NSMutableDictionary *devices;

@property (strong, nonatomic) CBPeripheral *discoveredPeripheral;

@property (strong, nonatomic) CBPeripheral *selectedPeripheral;

@property (readonly, nonatomic) CFUUIDRef UUID;

@property (strong, nonatomic) CBCharacteristic *characteristics;

@property (strong, nonatomic) NSMutableData *data;

@end

ViewController.m

//
// ViewController.m

// ShakeControlTest

/

/ Created by Max Kievits on 15-12-17.

// Copyright © 2017 MaxMedia-Apps. All rights reserved.

//

#import "ViewController.h"

#define UIColorFromRGB(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0]

#define DEGREES_TO_RADIANS(angle) ((angle) / 180.0 * M_PI)

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {

[super viewDidLoad];

// Do any additional setup after loading the view, typically from a nib.

locationManager = [[CLLocationManager alloc] init];

locationManager.delegate = self;

[locationManager setDesiredAccuracy:kCLLocationAccuracyBest];

[locationManager requestWhenInUseAuthorization];

[locationManager startUpdatingHeading];

_centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];

devicesArray = [[NSMutableArray alloc] init];

blockSearching = false;

lightOn = false;

}

-(BOOL)becomeFirstResponder {

return true;

}

-(void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event {

UIImageView *bigOvalEffectView = [[UIImageView alloc] initWithFrame:CGRectMake(self.view.frame.size.width / 2, self.view.frame.size.height, 2044, 2044)];

bigOvalEffectView.image = [UIImage imageNamed:@"BigOval"];

bigOvalEffectView.center = CGPointMake(self.view.center.x, self.view.center.y + 800);

bigOvalEffectView.transform = CGAffineTransformMakeScale(0.4, 0.4);

[self.view addSubview:bigOvalEffectView];

[UIView animateWithDuration:1 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{

bigOvalEffectView.alpha = 0.0;

bigOvalEffectView.transform = CGAffineTransformMakeScale(2, 2);

} completion:^(BOOL finished) {

[bigOvalEffectView removeFromSuperview];

}];

}

-(void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {

if (motion == UIEventSubtypeMotionShake) {

[self switchLight];

}

if (connected == false) {

UIImageView *bigOvalEffectView = [[UIImageView alloc] initWithFrame:CGRectMake(self.view.frame.size.width / 2, self.view.frame.size.height, 2044, 2044)];

bigOvalEffectView.image = [UIImage imageNamed:@"BigOval"];

bigOvalEffectView.center = CGPointMake(self.view.center.x, -800);

bigOvalEffectView.transform = CGAffineTransformMakeScale(0.4, 0.4);

[self.view addSubview:bigOvalEffectView];

[UIView animateWithDuration:1 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{

bigOvalEffectView.alpha = 0.0;

bigOvalEffectView.transform = CGAffineTransformMakeScale(2, 2);

} completion:^(BOOL finished) {

[bigOvalEffectView removeFromSuperview];

}];

UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleHeavy];

[generator impactOccurred];

[NSTimer scheduledTimerWithTimeInterval:0.1 repeats:false block:^(NSTimer * _Nonnull timer) {

UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleHeavy];

[generator impactOccurred];

}];

[NSTimer scheduledTimerWithTimeInterval:0.2 repeats:false block:^(NSTimer * _Nonnull timer) {

UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium];

[generator impactOccurred];

}];

}

}

-(void)switchLight {

if (connected) {

if (degrees >= (calibratedLampDegrees - 25) && degrees <= (calibratedLampDegrees + 25)) {

if (lightOn == false) {

[self sendValue:@"1"];

} else {

[self sendValue:@"0"];

}

}

}

}

-(void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event{

NSLog(@"Cancelled?");

if (motion == UIEventSubtypeMotionShake) {

[self switchLight];

}

if (connected == false) {

UIImageView *bigOvalEffectView = [[UIImageView alloc] initWithFrame:CGRectMake(self.view.frame.size.width / 2, self.view.frame.size.height, 2044, 2044)];

bigOvalEffectView.image = [UIImage imageNamed:@"BigOval"];

bigOvalEffectView.center = CGPointMake(self.view.center.x, -800);

bigOvalEffectView.transform = CGAffineTransformMakeScale(0.4, 0.4);

[self.view addSubview:bigOvalEffectView];

[UIView animateWithDuration:1 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{

bigOvalEffectView.alpha = 0.0;

bigOvalEffectView.transform = CGAffineTransformMakeScale(2, 2);

} completion:^(BOOL finished) {

[bigOvalEffectView removeFromSuperview];

}];

UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleHeavy];

[generator impactOccurred];

[NSTimer scheduledTimerWithTimeInterval:0.1 repeats:false block:^(NSTimer * _Nonnull timer) {

UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleHeavy];

[generator impactOccurred];

}];

[NSTimer scheduledTimerWithTimeInterval:0.2 repeats:false block:^(NSTimer * _Nonnull timer) {

UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium];

[generator impactOccurred];

}];

}

}

- (void) locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading {

degrees = newHeading.magneticHeading;

orientationLabel.text = [NSString stringWithFormat:@"%.0f˚",newHeading.magneticHeading];

ovalImageView.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(-degrees + calibratedLampDegrees));

}

-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {

NSString *receivedString = [[NSString alloc] initWithData:[characteristic value] encoding:NSUTF8StringEncoding];

NSString *str = [[receivedString stringByReplacingOccurrencesOfString:@"\n" withString:@""] stringByReplacingOccurrencesOfString:@"\r" withString:@""];

NSLog(@"//%@//",str);

if ([str isEqualToString:@"RETURN:OFF"]) {

orientationLabel.textColor = [UIColor whiteColor];

UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleHeavy];

[generator impactOccurred];

[NSTimer scheduledTimerWithTimeInterval:0.1 repeats:false block:^(NSTimer * _Nonnull timer) {

UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium];

[generator impactOccurred];

}];

lightOn = false;

}

if ([str isEqualToString:@"RETURN:ON"]) {

orientationLabel.textColor = [UIColor blackColor];

UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium];

[generator impactOccurred];

[NSTimer scheduledTimerWithTimeInterval:0.1 repeats:false block:^(NSTimer * _Nonnull timer) {

UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleHeavy];

[generator impactOccurred];

}];

lightOn = true;

}

if ([str containsString:@"PROGRESS:"]) {

float progress = [[str stringByReplacingOccurrencesOfString:@"PROGRESS:" withString:@""] floatValue];

progressView.progress = (progress/100);

}

}

- (void)didReceiveMemoryWarning {

[super didReceiveMemoryWarning];

// Dispose of any resources that can be recreated.

}

-(void)connectionAnimationWithState:(NSInteger)state {

if (connected == false) {

UIView *connectedView = [[UIView alloc] initWithFrame:CGRectMake(0, -50, self.view.frame.size.width, 50)];

UILabel *connectedLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 50)];

[[[[UIApplication sharedApplication] delegate] window] setWindowLevel:UIWindowLevelAlert];

if (state == 0) { //Disconnected

connectedView.backgroundColor = UIColorFromRGB(0xE6311E);

connectedLabel.text = @"Verbinding verbroken";

}

if (state == 1) {

connectedView.backgroundColor = UIColorFromRGB(0x2ecc71);

connectedLabel.text = @"Verbonden";

}

connectedLabel.textColor = [UIColor whiteColor];

connectedLabel.textAlignment = NSTextAlignmentCenter;

[connectedView addSubview:connectedLabel];

[self.view addSubview:connectedView];

[UIView animateWithDuration:0.3 animations:^{

connectedView.center = CGPointMake(connectedView.center.x, connectedView.center.y + 50);

} completion:^(BOOL finished) {

[UIView animateWithDuration:0.3 delay:3 options:UIViewAnimationOptionTransitionNone animations:^{

connectedView.center = CGPointMake(connectedView.center.x, connectedView.center.y - 50);

} completion:^(BOOL finished) {

[[[[UIApplication sharedApplication] delegate] window] setWindowLevel:UIWindowLevelNormal];

}];

}];

connected = true;

}

}

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

calibratedLampDegrees = degrees;

NSLog(@"%ld",calibratedLampDegrees);

ovalImageView.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(-degrees + calibratedLampDegrees));

//[self switchLight];

}

#pragma mark Alle Bluetooth Code

// Make sure iOS BT is on. Then start scanning.

- (void)centralManagerDidUpdateState:(CBCentralManager *)central {

// You should test all scenarios

if (central.state != CBManagerStatePoweredOn) {

// In case Bluetooth is off.

return;

// Need to add code here stating unable to access Bluetooth.

}

if (central.state == CBManagerStatePoweredOn) {

//If it's on, scan for devices.

[_centralManager scanForPeripheralsWithServices:nil options:nil];

}

}

-(UIStatusBarStyle)preferredStatusBarStyle {

return UIStatusBarStyleLightContent;

}

// Report what devices have been found.

- (void)centralManager:(CBCentralManager *)central

didDiscoverPeripheral:(CBPeripheral *)peripheral

advertisementData:(NSDictionary *)advertisementData

RSSI:(NSNumber *)RSSI

{

// Set peripheral.

_discoveredPeripheral = peripheral;

// Create a string for the discovered peripheral.

NSString * uuid = [[peripheral identifier] UUIDString];

if (uuid) //Make sure we got the UUID.

{

//This sets the devices object.peripheral = uuid

if (peripheral.state == CBPeripheralStateConnected) {

blockSearching = true;

}

if (blockSearching == false) {

if (peripheral) {

[devicesArray addObject:peripheral];

}

if (peripheral.state == CBPeripheralStateDisconnected) {

if (blockSearching == false) {

for (CBPeripheral *object in devicesArray) {

if ([object.name isEqualToString:@"HMSoft"]) {

[_centralManager connectPeripheral:peripheral options:nil];

_selectedPeripheral = object;

}

}

}

}

}

}

}

- (NSMutableDictionary *)devices

{

// Make sure the device dictionary is empty.

if (_devices == nil)

{

// Let's get the top 6 devices.

_devices = [NSMutableDictionary dictionaryWithCapacity:6];

}

// Return a dictionary of devices.

return _devices;

}

// Run this whenever we have connected to a device.

- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {

// Set the peripheral delegate.

peripheral.delegate = self;

// Set the peripheral method's discoverServices to nil,

// this searches for all services, its slower but inclusive.

[peripheral discoverServices:nil];

//State 0: Disconnected, State 1: Connected, State 2: Connecting

[self connectionAnimationWithState:1];

}

-(void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {

[self connectionAnimationWithState:0];

}

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error

{

// Enumerate through all services on the connected peripheral.

for (CBService * service in [peripheral services])

{

// Discover all characteristics for this service.

[_selectedPeripheral discoverCharacteristics:nil forService:service];

}

}

- (void)peripheral:(CBPeripheral *)peripheral

didDiscoverCharacteristicsForService:(CBService *)service

error:(NSError *)error

{

// Enumerate through all services on the connected peripheral.

for (CBCharacteristic * character in [service characteristics])

{

// Discover all descriptors for each characteristic.

[_selectedPeripheral discoverDescriptorsForCharacteristic:character];

}

}

- (void)peripheral:(CBPeripheral *)peripheral

didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic

error:(NSError *)error

{

//Store data from the UUID in byte format, save in the bytes variable.

const char * bytes =[(NSData*)[[characteristic UUID] data] bytes];

//Check to see if it is two bytes long, and they are both FF and E1.

if (bytes && strlen(bytes) == 2 && bytes[0] == (char)255 && bytes[1] == (char)225)

{

// We set the connected peripheral data to the instance peripheral data.

_selectedPeripheral = peripheral;

for (CBService * service in [_selectedPeripheral services])

{

for (CBCharacteristic * characteristic in [service characteristics])

{

// For every characteristic on every service, on the connected peripheral

// set the setNotifyValue to true.

[_selectedPeripheral setNotifyValue:true forCharacteristic:characteristic];

}

}

}

}

- (void)sendValue:(NSString *) str

{

for (CBService * service in [_selectedPeripheral services])

{

for (CBCharacteristic * characteristic in [service characteristics])

{

// Write the str variable with all our movement data.

[_selectedPeripheral writeValue:[[NSString stringWithFormat:@"%@", str] dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:characteristic type:CBCharacteristicWriteWithoutResponse];

}

}

}

Step 6: Step 6: Test and Play!

Now comes the fun part: Testing! Go test the app and see if the light turns on or off when you shake your phone!

You need to tap the screen when looking at the light to calibrate though ;)


Thank you for reading this tutorial! If you have any questions, feel free to ask me!