2015. 12. 21. 18:58

LEGO Wall-E Motorized


LEGO Wall-E Motorized using Arduino

controlling by iPhone


기말고사 기간에 레고 월E 출시 발견. 이어서, 마인드스톰 추가 구성해서 움직이게 한거 보고 넋이 나감. 

그래서 겨울 방학때 아래의 미션하에 프로젝트를 진행하였다:


1. 레고 월E 굴러가게 하기.

2. 무조건 내 핸드폰 앱으로 컨트롤 가능해야 함. 적외선 컨트롤러 이딴거 난 안쓴다.

3. 블루투스 구성 위한 마인드스톰 오지게 비쌈. 아두이노 사용해서 경비절감.


그리하여 아래와 같이 완성!


외국 친구들한테도 페북 공유하려다보니 못난 영작이 함께 들어갑니다. 리딩 흐름에 방해가 되더라도 양해 부탁드립니다.


During the last final's week, I found the LEGO's brand-new Wall E model. And soon after that, I found another Youtube video Wall-E motorized with LEGO mind storm. It was totally mind blowing work!! So, of course, I've done this project during my winter break. My missions were:

1. LEGO Wall-E Motorized

2. It has to be controlled by my iPhone. I don't wanna talk about the stupid infrared controller at all. :P

3. But, mind storm for bluetooth is freakin' expensive. Use Arduino.

At the end, voila.





Step 1. 부품을 준비한다. Prepare parts we need


Requirement parts:

  1. Lego Wall-E : $60.
  2. Motors : LEGO Power Functions M-Motor. (9v 65mA) $7 x 2 = $14.
  3. Lego Pick-a-brick : For connecting motor. 4pcs = $1
  4. Arduino UNO Rev3 : $25.
  5. Bluetooth module : HM-10. $9.
  6. 9v battery holder with on/off switch : $2
  7. 9v batteries : $3
  8. L293D Motor Drive Shield Board : $5
  9. Wires : Male to female 40pcs = $1


Total cost: $120



Step 2. 아뒤노 하드웨어 부품을 연결한다. Build Arduino hardware parts



나의 모터쉴드는 아두이노 위에 겹쳐서 꼽는 방식이었다. 그 후 모터 연결하고 전원 연결은 모터 쉴드에다 하면 되고, 블루투스 모듈은 와이어를 이용해서 그림과 같이 해당 핀에 분선하여 연결한다. 나의 경우 TX를 3번 핀, RX를 2번 핀에 연결 했다. (모터 컨트롤 핀으로 쓰이지 않는 것을 선택해야 한다. 참고로 0번과 1번, 즉 아두이노 보드의 TX와 RX는 USB와 연결된 프로그램 업로드 용이므로 사용을 피한다.)


My motor shield can be over-layered with the Arduino board. Motors and power (battery) are connected onto the motor shield, bluetooth module needs additional wires to branch from the Arduino. I used pin 3 as TX, and  pin 2 as RX. (You should choose the pins, which are not used for motor controlling. Besides, we should avoid to use pin 0 and 1 because these are for program uploading of Arduino board.)



Step 3. 아두이노에 모터 제어 프로그램을 업로딩한다. 

Upload a motor control program to Arduino.




아두이노를 컴퓨터에 연결하고, Arduino 공식 사이트에서 프로그램 업로드 소프트웨어를 다운받아 설치한다. 

Tools 메뉴에서 Board 를 Arduino Uno 로 선택한다. 코드를 작성 후 업로드 버튼을 눌러 아두이노 보드에 작성한 코드 프로그램을 업로딩한다. 참고로, 고맙게도 모터관련 라이브러리가 이미 제작되어 있다. 이를 이용하면 모터 제어를 쉽게 짤 수 있다. AFMotor임.

https://learn.adafruit.com/adafruit-motor-shield/library-install

위 URL로 들어가면 github 다운로드 링크가 나온다. 다운받은 폴더를 아뒤노 개발툴 라이브러리 폴더 안에 넣고 (e.g. ~/Arduino/libraries/AFMotor) AFMotor.h 파일을 아뒤노 코드에 인클루드 시키면 사용할 수 있다. 

내가 짠 아뒤노 코드는 이 글 최하단에 추가하였으니 참고바람.


참고로 HM-10의 커맨즈는 다음과 같다. 아뒤노 시리얼 모니터에서 입력해보고 정상적으로 응답이 오는지 확인하자. 또한 시리얼 하단에서 no line ending 옵션으로 되어 있는지도 확인한다. 

  • "AT" 전송 시 응답 "OK"
  • "AT+VERR?" 전송 시 응답은 현재 버젼 출력 
  • 이름변경: "AT+NAMEmyWallEBLT" 전송 시 응답 "OK+SET..."
  • 비번변경: "AT+PIN1234" 전송 시 응답 "OK+SET..." (디폴트:000000)
  • 스래이브/마스터변경: "AT+ROLE0" 전송 시 응답 "OK+SET:0" (디폴트: 0 슬래이브. 1은 마스터를 의미.)


Connect the Arduino to a laptop, open a developing software tool downloading from the official Arduino website.

Make sure whether choose 'Arduino Uno' on the Tools menu. Upload a code onto Arduino board. Fortunately, there is a DC motor control library for Arduino already. We can make a motor controlling code easily by using this library, named AFMotor.

https://learn.adafruit.com/adafruit-motor-shield/library-install

You can see github download link when you get into the above URL. Put the library folder into a library folder located under the Arduino developer tool folder, we can use AFMotor after include AFMotor.h file in the code.

My code is at the bottom of this article. 


Note that, HM-10 commands are following: 

  • Send "AT", receive "OK"
  • Send "AT+VERR?", receive a version of bluetooth firmware.
  • Set name: send "AT+NAMEmyWallEBLT", receive "OK+SET..."
  • Set pin: send "AT+PIN1234", receive "OK+SET..." (Default: 000000)
  • Set role: send "AT+ROLE0", receive "OK+SET:0" (Default: 0, means slave. 1 is master mode)
Send these commend to bluetooth through Arduino tool's serial monitor, check whether an answer comes correctly. Make sure the 'no line ending' option is selected at the bottom of the serial monitor.




Step 4. 아이폰 블루투스 컨트롤러 앱을 제작한다. Develop iPhone bluetooth controller app.




그냥 뭐... 잘 짜면 된다...;; 역시 하단에 필요 코드를 첨부하였다. 프로젝트는 기본 Single View 프로젝트로 생성하면 되고, Swift가 아닌 Object C 코드이다. 컴파일 후 아이폰에 올리고 실행하면 위 사진 왼쪽 화면이 나온다. (블루투스가 켜진 상태여야 함.) 내 월E 블루투스를 선택하면, 커넥션 성공 시 오른쪽 화면으로 이동하게 된다. 이것저것 눌러봐서 모터가 잘 도는지 확인한다. 


Well.. good luck. :P. It's hard to explain. I attached essential codes I wrote at the bottom of this article as well.  You need to create a project as a single view project using Xcode. My codes are written by objective C, not Swift. After compile and upload your app onto your iPhone, you can see a screen like the above left picture when you start the app. (It has to be a bluetooth on.) After choose your wall-e bluetooth device, it would move to a screen like the above right picture if it makes the connection successful. Click some buttons to see motor working well.  



Step 5. 월E를 조립한 후 조립 해 둔 하드웨어 부품을 부착한다.

Assemble the Arduino hardware parts onto the Wall-E.



일단 바퀴에 모터 두개 부착. First, attach the two motors with the wheels.



배터리는 배안에 넣어주고 전선은 목 옆쪽 공간을 통해 밖으로 빼준다.

Put a battery into the belly space of Wall-E, wires need to be out through the gap of the neck.



요런식으로 나는 등뒤에 전기테잎으로 고정하였다... 전테말고 다른 방법을 강구하고 싶지만..

(누가 우리 월E 배낭 좀 만들어주시오. ㅎㅎㅎ)


I used an electrical tape to attach the parts at the backside like above. 

I wanna use other things like a backpack or something tho..





Complete!!!








Appendix


* 모터의 선택 : 월E에 실리콘 떡질해서 모터 고정시키고 싶지 않아서 레고 모터 사용. 추가적으로 이때문에 3번 항목 구입 발생.. 모터랑 월E 바퀴 연결부품인데 필요한 브릭만 레고사에서 주문 가능.

* Selection of motors : I used LEGO motors because I don't want to use a sticky messy silicon glue on my Wall-E. That's why my 3rd purchase, pick-a-bricks, was occurred. You can make order from LEGO for picking some little amount of parts what you exactly need for connecting wheels and motors.  


* 라즈베리 파이에 대해 : 이왕이면 더 가지고 놀게 많은 PI 를 사려고 했으나, 이게 컴터 메인보드 같은거라 배터리로 안되고 파워 지속공급 되야 함. 즉, 유선 월E가 된다는 황당한 시츄에이션이... -_- (그거슨 있어서는 안되는 일이야..)

* About the Rasberry PI: We all know the PI is more versatile, however, it is kind of a mainboard, so it needs continuous power supply. It means, my Wall-E will have a stupid wire from a laptop. ABSOLUTELY NOT ALLOWED.


* 블루투스 모듈: HC-05 나 HC-06 많이들 쓰는데, 안드로이드는 이거 써도 됨. 허나 아이폰 6 부터는 블루투스 4.0 이라서 안잡힘. 이거 몰랐어서 블루투스 4.0 쓰는 HM-10으로 다시 샀음. (내 돈...)

* Bluetooth module: HC-05 or HC-06 are mostly common to use for Android application. But iPhone 6 or later have bluetooth 4.0, it cannot scan those modules. I bought HM-10 again after HC-05 because I didn't know that. 


* 모터 드라이버 쉴드: 걍 젤 싼거, 모터 두개 이상 돌릴 수 있는거로 사면 됨. 모터 쉴드는 모터 연결 편리성과 파워를 안전정으로 공급하기 위해 씀. 좀 더 자세히 말하면, 첫째는, 일반적으로 모터는 높은 전류 필요로 함. (레고 모터도 65mA임) 허나 아두이노는 핀당 20mA 출력함. 드라이버가 이를 충분한 전압과 전력으로 출력할 수 있게 해 줌. 둘째는, 모터 회전 방향 변경을 하려면 + - 바꿔줘야 하는데, 이를 위한 복잡한 전선 연결을 해결해 줘서 편리하게 방향 변경할 수 있게 해줌. (그때가 납땜에 영혼을 불사를 수 있다면 직접 해도 될듯함...) 더 자세한 내용은 네이버 아두이노 카페 글 참고 http://cafe.naver.com/arduinostory/30555

* Motor driver shield: Just bought a cheapest one, which can manage more than two motors. Motor shield is for convenience of connecting motors and provides sufficient power. In detail, first, motors require high current value mostly. (LEGO M motor is also 65mA.) But Arduino has only 20mA per pin. Driver makes it can output enough voltage and current for motors. Second, it should change the direction of + and - to change the direction of motor rotating, wire connecting is quite annoying and messy job. Driver handle this issue, it provides a pleasant environment to use multiple motors changing direction.


* 기타 제작 과정에서 도움 받은 참고 자료 및 사이트 Additional useful references. 

LEGO Wall-E 21303 Motorized with Power Functions https://youtu.be/3_9q6RjB5nM

Controlling LEGO DC motor with an Arduino https://youtu.be/PtinpaVpHeo

IPhone to Arduino using Bluetooth 4.0 http://www.instructables.com/id/IPhone-to-Arduino-using-Bluetooth-40-/

Core Bluetooth Programming Guide https://developer.apple.com/library/...

Bluetooth 4.0 datasheet http://www.pridopia.co.uk/pi-doc/BT4.0-HM-10-Serial_Port_BLE_Module_Master_Slave.pdf


* 아두이노 모터 제어 코드 Arduino motor control code.


Note 1:  모든 DC 모터는 조금씩 베이스 속도가 다르다. 이를 동일하게 맞추려면 휴리스틱한 방법으로, 두 모터의 속도 스케일 차이를 파악한 후 속도를 조정해야 한다. 뭔 말이냐면, 1번과 2번 모터를 모두 속도 10으로 맞추었는데, 육안으로 봤을때 2번 모터가 1번보다 3배 빠르게 돌아가고 있다면, 구동전에 1번 모터 속도를 30으로 2번 모터 속도를 10으로 맞추고 구동을 한다는 말이다. 


Note 2: 레고 모터는 AFMotor 라이브러리를 이용한 속도 세팅 시, 90 이하의 속도 세팅에서는 아예 구동하지 않는다.


#include <SoftwareSerial.h>

#include <AFMotor.h>

#define SPD1  100                    // Motor1 base speed.

#define SPD2  165                    // Motor2 base speed.


AF_DCMotor motor1(1);            // Motor1 is connected to slot 1.

AF_DCMotor motor2(4);          // Moter2 is connected to slot 4. 

SoftwareSerial BTSerial(3, 2);  // Pin3 is for TX, and Pin 2 is for RX.

int cmd1 = RELEASE;

int cmd2 = RELEASE;

int unavailableCount = 0;

String cmds = "";


void setup()

{

  Serial.begin(9600);

  BTSerial.begin(9600);

  Serial.println("Jisun's Wall-E world!!");

  motor1.setSpeed(SPD1);

  motor2.setSpeed(SPD2);

}


void loop()

{

  if(BTSerial.available())

  {

    char cmd = BTSerial.read();

    cmds = cmds + cmd;

    unavailableCount = 0;

  }

  else

  {

    unavailableCount = unavailableCount + 1;

  }

  

  if ( unavailableCount > 10 && cmds.length() > 0 )

  {

    int spd1 = SPD1;

    int spd2 = SPD2;

    String dbgstr = "";

    

    if ( cmds.indexOf("speed") >=0 )

    {

      int spdlv = 1;

      if ( cmds == "speed1") spdlv = 1;

      else if ( cmds == "speed2") spdlv = 2;

      else if ( cmds == "speed3") spdlv = 3;

      

      spd1 = spd1 + spdlv * 25;

      spd2 = spd2 + spdlv * 20;

      dbgstr = "Set speed level: " + String(spdlv);

      motor1.setSpeed(spd1);

      motor2.setSpeed(spd2);

    }

    else if ( cmds == "forward" )

    {

      dbgstr = "Move forward";

      cmd1 = FORWARD; cmd2 = FORWARD;

    }

    else if ( cmds == "backward" )

    {

      dbgstr = "Move backward";

      cmd1 = BACKWARD; cmd2 = BACKWARD;

    }

    else if ( cmds == "left")

    {

      dbgstr = "Turn left";

      cmd1 = RELEASE; cmd2 = FORWARD;

    }

    else if ( cmds == "right")

    {

      dbgstr = "Turn right";

      cmd1 = FORWARD; cmd2 = RELEASE;

    }

    else if ( cmds == "leftback")

    {

      dbgstr = "Turn left backward";

      cmd1 = RELEASE; cmd2 = BACKWARD;

    }

    else if ( cmds == "rightback")

    {

      dbgstr = "Turn right backward";

      cmd1 = BACKWARD; cmd2 = RELEASE;

    }

    else if ( cmds == "release" )

    {

      dbgstr = "Release";

      cmd1 = RELEASE; cmd2 = RELEASE;

    }


    if ( dbgstr.length() > 0 )

    {

      dbgstr += "(" + String(spd1) + "/" + String(spd2) + ")";

      Serial.println(dbgstr);

      motor1.run(cmd1);

      motor2.run(cmd2);

    }

    else

    {

      Serial.println(cmds);

    }

    cmds = "";

    unavailableCount = 0;

  }

  

  if(Serial.available())

  {

    char cmd = Serial.read();

    BTSerial.write(cmd);

  }

}



* 아이폰 블루투스 컨트롤러 코드 iPhone bluetooth controller code


ViewController.h


//

//  ViewController.h

//  WallEController

//

//  Created by Jisun Kang on 1/10/16.

//  Copyright © 2016 ___JSK___. All rights reserved.

//


#import <UIKit/UIKit.h>

#import <CoreBluetooth/CoreBluetooth.h>


@interface ViewController : UIViewController <CBCentralManagerDelegate>

@property (nonatomic, strong) CBCentralManager *centralManager;

@property (nonatomic, strong) CBPeripheral *peripheral;

@property (nonatomic, strong) CBCharacteristic *currentcharacteristic;


-(IBAction) goToSecondView:(id)sender;

-(IBAction) cancelConnection:(id)sender;

@end


@interface SecondViewController : UIViewController

@property (nonatomic, strong) CBCentralManager *centralManager;

@property (nonatomic, strong) CBPeripheral *peripheral;

@property (nonatomic, strong) CBCharacteristic *currentcharacteristic;


-(IBAction) pressButtons:(UIButton *)sender;

@end




ViewController.m


//

//  ViewController.m

//  WallEController

//

//  Created by Jisun Kang on 1/10/16.

//  Copyright © 2016 ___JSK___. All rights reserved.

//


#import "ViewController.h"


// this is for various device screen size.

#define SCREEN_WIDTH [[UIScreen mainScreen] bounds].size.width

#define SCREEN_HEIGHT [[UIScreen mainScreen] bounds].size.height


@interface ViewController () <UITableViewDataSource, UITableViewDelegate>

{

    NSMutableArray* cbArray;

}

@property (strong, nonatomic) IBOutlet UITableView *mainTableView;

@property (strong, nonatomic) IBOutlet UILabel *mainLabel;

@end


@implementation ViewController


- (void)viewDidLoad {

    [super viewDidLoad];


    // initializing UITableView

    cbArray = [NSMutableArray array];

    _mainTableView.delegate = self;

    _mainTableView.dataSource = self;

    _mainTableView.backgroundColor = [UIColor colorWithRed:236.0/255.0 green:236/255.0 blue:236.0/255.0 alpha:0.7];

    _mainTableView.backgroundView = nil;

    _mainTableView.allowsMultipleSelectionDuringEditing = NO;

    _mainTableView.separatorStyle = UITableViewCellSeparatorStyleSingleLineEtched;

    _mainTableView.autoresizingMask = UIViewAutoresizingNone;

    

    self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];

    self.peripheral.delegate = self;

}


- (void)viewDidAppear:(BOOL)animated {

    [super viewDidAppear: true];

    _mainLabel.text = @"";

}


- (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

}


- (void)dealloc

{

    [self.centralManager stopScan];

}


- (IBAction)goToSecondView:(id)sender {

    UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName: @"Main" bundle:nil];

    SecondViewController* secVC = [mainStoryboard instantiateViewControllerWithIdentifier:@"SecondView"];

    secVC.centralManager = self.centralManager;

    secVC.peripheral = self.peripheral;

    secVC.currentcharacteristic = self.currentcharacteristic;

    [self presentViewController:secVC animated:YES completion:nil];

}


- (IBAction)cancelConnection:(id)sender {

    if ( self.peripheral == nil )

        return;

    _mainLabel.text = @"Cancel connection.";

    [self.centralManager cancelPeripheralConnection:self.peripheral];

    self.peripheral = nil;

    [self.centralManager scanForPeripheralsWithServices:nil options:nil];

}


// =========================================

// CB Central Manager

// =========================================


// Invoked when the central manager’s state is updated.

- (void)centralManagerDidUpdateState:(CBCentralManager *)central

{

    switch (central.state) {

        case CBCentralManagerStatePoweredOn:

            NSLog(@"CoreBluetooth BLE hardware is powered on and ready");

            _mainLabel.text = @"Scanning...";

            [self.centralManager scanForPeripheralsWithServices:nil options:nil]; //@{CBCentralManagerScanOptionAllowDuplicatesKey:@YES}];

            break;

        case CBCentralManagerStatePoweredOff:

        case CBCentralManagerStateResetting:

        case CBCentralManagerStateUnauthorized:

        case CBCentralManagerStateUnknown:

        case CBCentralManagerStateUnsupported:

        default:

            NSLog(@"CoreBluetooth BLE hardware is powered off or not initialized.");

            break;

    }

}


// add the device to our array and reload the table view.

- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI

{

    CBPeripheral* currentPer = peripheral;

    

    if(![cbArray containsObject:currentPer])

    {

        [cbArray addObject:currentPer];

    }

    [_mainTableView reloadData];

}


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

{

    NSLog(@"Success to connect.: %@",peripheral);

    _mainLabel.text = @"Success to connect.";

    peripheral.delegate = self;

    

    if (peripheral.services) {

        [self peripheral:peripheral didDiscoverServices:nil];

    } else {

        [peripheral discoverServices:nil];

    }

    self.peripheral = peripheral;

}


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

    

    NSLog(@"Disconnected with peripheral: %@",peripheral);

    _mainLabel.text = @"Disconnected and scanning...";

    self.peripheral = nil;

    [self.centralManager scanForPeripheralsWithServices:nil options:nil];

    

    NSLog(@"The error is: %@", error.localizedDescription);

}


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

{

    NSLog(@"Connection failed to peripheral: %@",peripheral);

    _mainLabel.text = @"Fail to connect.";

}


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

{

    for (CBService *service in peripheral.services)

    {

        NSLog(@"Discovered service %@", service);

        _mainLabel.text = @"Discovered service.";

        

        [peripheral discoverCharacteristics:nil forService:service];

    }

}


- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error

{

    NSLog(@"Looking for Characteristic...");

    for (CBCharacteristic *characteristic in service.characteristics)

    {

        if ( characteristic.properties & CBCharacteristicPropertyWriteWithoutResponse )

        {

            NSLog(@"Find Characteristic!!");

            self.currentcharacteristic = characteristic;

            [peripheral setNotifyValue:YES forCharacteristic:characteristic];

        

            [self goToSecondView:nil];

        }

    }

}


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

{

    if (error) {

        NSLog(@"Error writing characteristic value: %@",

              [error localizedDescription]);

    }

}


// =========================================

// TableView delegates

// =========================================


#pragma mark UITableView Delegate

// reload the table view when a new peripheral device is found and write their name list.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

{

    static NSString *cellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];

    if (cell == nil) {

        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];

    }

    

    cell.backgroundColor = [UIColor clearColor];

    cell.selectionStyle = UITableViewCellSelectionStyleDefault;

    

    CBPeripheral* currentPer = [cbArray objectAtIndex:indexPath.row];

    cell.textLabel.text = (currentPer.name ? currentPer.name : @"Not available");

    

    return cell;

}


- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

{

    [tableView deselectRowAtIndexPath:indexPath animated:YES];

    

    _mainLabel.text = @"Trying to connect...";

    CBPeripheral* currentPer = [cbArray objectAtIndex:indexPath.row];

    

    if ( self.peripheral != nil )

    {

        [self.centralManager cancelPeripheralConnection:self.peripheral];

    }

    currentPer.delegate = self;

    self.peripheral = currentPer;

    [self.centralManager connectPeripheral:currentPer options:nil];

    

    [self.centralManager stopScan];

}


- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {

    return [NSString stringWithFormat:@"Total count %lu",(unsigned long)cbArray.count];

}


- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section

{

    return 40;

}


#pragma mark UITableView Datasource

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

{

    return 50;

}


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)sectionIndex

{

    return cbArray.count;

}

@end



// =========================================

// SecondViewController

// =========================================


@interface SecondViewController ()

{

}

@property (strong, nonatomic) IBOutlet UILabel *secondLabel;

@end


@implementation SecondViewController


- (void)viewDidLoad {

    [super viewDidLoad];

}


- (void)viewDidAppear:(BOOL)animated {

    [super viewDidAppear: true];

    _secondLabel.text = @"";

}


- (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

}


- (IBAction)pressButtons:(UIButton *)sender {

    _secondLabel.text = @"";

    

    NSString *buttonTag = [NSString stringWithFormat:@"%d", (int)sender.tag];

    NSString *data = @"";

    

    switch (sender.tag) {

        case 1:

            _secondLabel.text = @"Turn left";

            data = @"left";

            break;

        case 2:

            _secondLabel.text = @"Go forward";

            data = @"forward";

            break;

        case 3:

            _secondLabel.text = @"Turn right";

            data = @"right";

            break;

        case 4:

            _secondLabel.text = @"Turn left backword";

            data = @"leftback";

            break;

        case 5:

            _secondLabel.text = @"Move backward";

            data = @"backward";

            break;

        case 6:

            _secondLabel.text = @"Turn right backword";

            data = @"rightback";

            break;

        case 0:

            _secondLabel.text = @"Stop";

            data = @"release";

            break;

        case 11:

            _secondLabel.text = @"Slow speed";

            data = @"speed1";

            break;

        case 12:

            _secondLabel.text = @"Normal speed";

            data = @"speed2";

            break;

        case 13:

            _secondLabel.text = @"fast speed";

            data = @"speed3";

            break;

        default:

            break;

    }

    NSLog(_secondLabel.text);

    

    if ( self.currentcharacteristic != nil )

    {

        NSLog(@"Write value to peripheral");

        NSData *encodingdata = [data dataUsingEncoding:NSUTF8StringEncoding];

        

        [self.peripheral writeValue:encodingdata forCharacteristic:self.currentcharacteristic type:CBCharacteristicWriteWithoutResponse];

    }

}

@end



2008. 11. 13. 13:42

Mintpad?! 디지털 포스트잇!

M신개념의 새로운 포터블 멀티미디어 기기가 나왔다. Mintpad.
오. 귀엽다. 처음 봤을때 딱 MIT MediaLab 에서 만든 Shiftable 생각나더라. (음.. shiftable 블로깅 했다고 생각했는데 블로깅 내역이 없네. 아래는 Shiftable 동영상이다.)



Shiftable 은 작은 블럭들간의 인터렉션을 즐기는 일종의 Digtal Toy 라고 할 수 있겠다. 보면서 우와 재미있다 나도 하나 가지고 싶어 - 하고 있었는데, MIT 대학생들의 프로젝트 성 물건이기에 상용화는 되어 있지 않아 그럴 방도는 없었다.

헌데 지나가다가 Mintpad 라는 제품을 발견. 물론, Shiftable 의 주요 컨셉인, 블럭간 물리적 인터렉션 기능(맞대고 있으면 두 블럭간 색상이 동기화 된다든지 하는)은 없지만,  한손에 잡는 작은 크기라는점, 매우 단순한 하드웨어 인터페이스 라는점, 그리고 그 기능들이 말그대로 '전문기기' 가 아닌 '보조문구' 수준의 단순하다는 점, tangible interface를 일부 '흉내내고' 있다는 점... 등등. mintpad 가 내밀고 있는 많은 컨셉들이 shiftable 과 상당부 오버랩 된다.




기능을 요약하면 아래와 같다.
  • 메모 
    • 포스트잇과 같은 컨셉으로 터치패드 스케칭. mintpad 끼리 메모 전송이 가능하기 때문에 1:1 챗도 가능하다는 이야기가 된다. 무선랜 없이 30m 이내의 교실이면 주고 받을 수 있다하니.... 이젠 간편한 치팅을. (-_-응?)
  • 인터넷/블로깅
    • 와이파이가 연결된 어디서든 인터넷 풀브라우징 지원!!! 오!!!!! 간단한 블로깅 기능도 있어서 블로그에 글 등록하고 보는것도 가능하단다.  
  • 멀티미디어
    • 뭐 음악 감상 동영상 감상 되고, 사진 되고.. e-book 되고.. 녹음되고..  이정도야 뭐 왠만한 포터블 기기의 기본기능이지 이제는.
  • 카메라
    • 130만 화소짜리 카메라가 달려잇덴다 ;ㅂ; 아놔... 예전에 zire71 쓰던때의 추억이 떠오르는군.. ㅠ ㅠ 화소는 떨어져도 진정한 똑딱이!!! 개인적으로 무척 마음에 드는 기능이다.
  • 스케쥴링 및 명함관리
    • 스케쥴 입력 가능 하고 명함 입력 가능. 간단하 pda 수준의 기능을 제공해 주는건가? 스케쥴링 기능은 정말 대 환영이지만. ㅎㅎㅎ.
  • 사파이어
    • PC에 별다른 설치 없이도 꼽기만 하면 어느 컴퓨터에서든 실행된다는 매우 마음에 드는 매니징 프로그램!! 사실 디지털 카메라니, mp3니, pmp 니.. 기타등등 pc 에는 온갖 씽크로 관련 매니징 소프트웨어깔리는데 정말 짜증. -_- 개인적 취향으로는 사파이어가 매우 마음에 든다.
더 자세한 것을 알고 싶다면 우측 배너 클릭! (무슨 나 홍보요원?-,.-)
가격은 대략 \199,000 (이정도면 아주 적당하다고 생각한다.)


하지만 아직 사용기나 기타등등을 읽어보면, 회의적인 시각도 있고.. (물론 그렇겠지. ㅎㅎ) 기능상의 의문을 제기하는 부분도 없잖아 있으므로 좀 더 신중할 필요가 있다......... (라고 쓰고 있지만 마음속은 이미 결재중이다 OTL)

요즘은 펌웨어 업그레이드 시대이므로 현재 미흡해보이는 많은 부분은 충분히 개선의 여지가 있다고 생각한다. ㅎㅎㅎㅎㅎ. 예를 들면 쉬프터블의 경우 흔들만 이미지가 흐려지거나하는 리액션이 있다. 민트패드의 경우 작성중이던 데이터가 저장된다고 한다. 뭐, '흔든다' 라는 tangible interface 가 기능과 전혀 의미적 유사성이 없다고 볼수 있다. -_- (흔들면 저장되다니.;; ) 하지만 Fun 컨셉에 추후의 펌웨어 업그레이드를 강조하는 걸로 봐서는 해당 기능을 사용한 추가적인 Funware 를 만들겠다는 생각이 엿보인다. (쉬프터블처럼 기울이면 화면이 넘어가거나 하면 좋을텐데 ㅋ)
 
참고로 이건 아이리버 신화를 만든 양덕준(누군지 멀라 -ㅂ-)~) 사장이 따로 나와서 만든거래. 오마갓. 난 그런 줄 몰랐어요 예찬론 펼쳤다고 아이리버 빠순이로 몰고가는건 곤란해. ㅋㅋ. 하지만 아이리버의 감성디자인을 건드리는 컨셉은 박수 쳐 주고 싶네요.  /박수
 

2008. 11. 10. 10:24

3D CUBE GAME - Level Head


원래 작년 이맘때 멤버십에서 처음 봤던 프로젝트이다.
봤을때 조금 충격이었지. 별다른 기술이 쓰인것은 없다. 프로젝터와, 단순한 영상처리. 그리고 간단한 3D 게임 프로그래밍 수준. 하지만 '누구나' 하는 기술로 '아무도' 생각지 못하는 창조물을 만든다는것은 언제봐도 부럽고 대단해.
교육중에 할일 없어 여기 저기 링크 타다가 다시 발견. 한단계 업그레이드된 레벨헤드. 일단은 꽤 귀엽잖은가? ㅎㅎ


levelHead is a spatial memory game by Julian Oliver.

levelHead uses a hand-held solid-plastic cube as its only interface. On-screen it appears each face of the cube contains a little room, each of which are logically connected by doors.

In one of these rooms is a character. By tilting the cube the player directs this character from room to room in an effort to find the exit.

Some doors lead nowhere and will send the character back to the room they started in, a trick designed to challenge the player's spatial memory. Which doors belong to which rooms?

There are three cubes (levels) in total, each of which are connected by a single door. Players have the goal of moving the character from room to room, cube to cube in an attempt to find the final exit door of all three cubes. If this door is found the character will appear to leave the cube, walk across the table surface and vanish.. The game then begins again.

Someone once said levelHead may have something to do with a story from Borges.. For a description of the conceptual basis of this project, see below.

Demo videos:

  • YouTube (low quality, BETA demo)
  • Vimeo (good quality, final demo) *NEW*
  • OGG/Theora 25M (plays in VLC, BETA demo)
  • OGG/Theora 65M (plays in VLC, final demo) *NEW*

    Installation configuration:



    Status
  • The game is currently considered stable, having been played by thousands of humans with vastly different brains and ways of handling the cubes.

    There is a source-code release intended for those willing to try to compile it and/or submit patches. As yet there is no binary executable available. In the meantime, levelHead is playable as an installation, appearing in several electronic arts events in 2008.

    Once levelHead is more easily installable, i intend to release all levels as paper cut-outs so people can print the levels onto stiff paper, cut and fold them up to play.

    It's perhaps worth mentioning that I'm currently talking with various parties about the possibility of publishing a larger and more sophisticated version of this project.

  • 2008. 3. 25. 21:53

    시맨틱 검색엔진 Qrobo

    우오와. -_-
    정말 필요하다....
    사실 네이버 지식인이 뜰 수 있었던 것은, 내가 알고 싶은 정보를 빠르고 좀 더 구체적인 데이터로 검색 가능 하다는 것 아니었을까? 지식인 처음 나왔을때 완전 쇼킹했는데... '이런 네이버 천재녀석들' 이라고. -_- (기술력보다는 그 기획력이...)
    하지만 Qrobo는 것은 기술력의 승리가 되려나?
    가끔, 난생 처음 듣는 이니셜의 단어를, 무슨 의미인지 검색하다보면 너무 뜻이 다른 (예 : AI 는 artificial intelligence 이기도 하지만 Adobe Illustrator 이기도 하다.-_-) 것들이 검색되고는 한다. 두개의 단어가 얽히고 섥혀져 검색되는 통에, 서로 다른 두 단어를 동의어로 인식하는 오류를 범하기도 한다.
    (예: adobe illustrator 는 인공지능으로 돌아가는구나...-_-; '극단적인 예'임을 밝힘.)



    글쎄, 하지만 문제시 되는 것은 역시.. '결과' 의 신뢰도와 정확성 이랄까. ㅎㅎ.
    그럴싸하게 체계적이고 분류적으로 잘 검색된 듯 하지만, 내가 진정 원한 결과가 엉뚱한 카테고리에서 발견될때의 배신감은 -_- 성질급하고 판단 빠른 네티즌에겐...회복할 수 없는 것이 될 지도 모르겠다.
    하지만 어디까지나 그냥 주관적인 걱정일 뿐이고, 아직 개발중에 있다고 하니까 ^^ 심히 기대해 볼 만하다고 생각한다.

    참조 Future of the Internet Economy Conference 2008 후기
    2008. 3. 20. 10:02

    MIT Media Lab. audiopad.

    Audiopad

    Audiopad is a composition and performance instrument for electronic music which tracks the positions of objects on a tabletop surface and converts their motion into music. One can pull sounds from a giant set of samples, juxtapose archived recordings against warm synthetic melodies, cut between drum loops to create new beats, and apply digital processing all at the same time on the same table. Audiopad not only allows for spontaneous reinterpretation of musical compositions, but also creates a visual and tactile dialogue between itself, the performer, and the audience.

    Audiopad has a matrix of antenna elements which track the positions of electronically tagged objects on a tabletop surface. Software translates the position information into music and graphical feedback on the tabletop. Each object represents either a musical track or a microphone.

    오디오패드는 전자음악의 작곡과 연주를 할수 있는 일종의 악기이다. 테이블 위에 오브젝트들을 위치시키면 이것을 트래킹하여 이 모션 정보를 음악으로 바꾸는 것이다. 이 오브젝트를 통해 방대한 사운드 샘플중에 특정 사운드를 골라내거나, 이리저리 위치시켜 멜로디를 합성시키거나, 드럼소리에 맞춰 새로운 비트를 넣을 수 있고, 이런 디지털 프로세싱은 동시에 한 테이블에서 이루어진다. 오디오패드는 자발적으로 음악을 재해석 할 수 있게 해줄 뿐 아니라, 연주자와 청중자 사이의 시각적이고 촉각적인 대화를 가능케 해주는 매개체가 된다.

    오디오패드는 전자태깅되는 테이블 위의 오브젝트들을 트래킹 하는 안테나적 요소를 가진다. 소프트웨어는 오브젝트의 위치 정보를 분석하여 음악과 그래픽적인 피드백으로 바꿔준다. 각 오브젝트들은 각각 하나의 음악트랙이거나 마이크의 역할을 한다.



    이상이 오디오패드 요약설명의 번역이다. 원문은 요기.

    멤버십에서 이 과제를 처음 접했을때가 떠오른다.
    얼마나 신기했던가! 얼마나 감탄했던가! -_- 하물며 나는 이것을 어떻게 만들어야 할지 감조차 잡을 수 없었다. 하지만 멤버십 경험을 내가 중히 할 수 있는 중에 하나이지만, 내 주변의 누군가는 어떤 식으로 만들어졌을거라는 감을 잡고 있었고 구체적으로 구현하는 방안에 대해 질문을 던졌다.
    그것이 시작이다. "저거, 만들수 있을것 같지 않아?".
    그때 우리가 구해 보았던 논문을 첨부하도록 하겠다. 관심있으면 download.

    대충 기억을 더듬어 원리를 설명하면, 테이블 바로 위 천장에 프로젝터를 달고, 그래픽은 그 프로젝터를 통해 테이블에 쏴주게 된다. 테이블은 자체로 거대한 RFID 리더기이며, 테이블 위에 올라가는 저 오브젝트들 (논문에서는 '퍽'이라고 한다. 순간 응? 아이스하키? 했지만. 사실 닮았다.)에 RFID 칩이 들어가는 것이다. 그럼 뭐.. 게임 끝이라고나 할까. 더 설명할께 있겠어? ㅎㅎ.
    조금 더 상세히 하면 RFID 가 두개 들어가는 원리이다. 하나는 중심에 하나는 살짝 위에 두면, 퍽을 회전 시켰을때 그 회전량을 파악할 수 있다. 퍽의 위치 정보를 아니까, 퍽 주변으로 그래픽 뿌려주고, 퍽의 회전 정도에 따라서도 그 외 다른 컨트롤이 가능하다.
    이렇게 알고나니 너무 간단한 원리... 이것이 컬럼버스의 계란. 그리고 기술의 승리.
    참고로 RFID 의 위치변화인식도는 2mm 수준. 꽤나 정밀하다. 따라서 조금만 움직여도 컨트롤 가능.

    기능 적으로 보자면, 퍽은 크게 세 종류. 음악 연주 퍽과 이를 반영하는 일명 마이크 퍽, 그리고 셀렉트퍽이 있다. 마이크 퍽이 중심이 되어 놓이고 음악연주 퍽이 마이크 퍽의 옆에 놓여져야 비로소 음악을 들을 수 있다. 마이크퍽이 없다면?? 아무리 연주퍽을 늘어놓아도 소리는 들리지 않는다.
    마이크 퍽은 말그대로 마이크 같아서, 이 퍽과의 거리가 얼마나 떨어져 있느냐고 볼륨 조절이 가능하다. 각 음악 연주 퍽들은 하나당 하나의 선율을 담당하고 있다고 볼 수 있다. 예를들어 1번 퍽이 전자키타 연주이고 2번 퍽이 신나는 비트의 드럼 사운드라면, 1번 퍽 옆에 2번 퍽을 놓으면 두 연주가 하모니를 이루게 된다. 셀렉트 퍽을 음악퍽 근처에 위치시키면 음악퍽의 음악을 변경할 수 있다. 아 감동적. - 이때의 그래픽이 얼마나 멋진가를 보라! - 트리형태로 온갖 사운드 트리를 볼 수 있다.

    하지만 이것을 만들어보자 했던 우리의 취지는 - 비록 따라만드는 것이지만, 본래 2류들은 1류들을 모방하면서 어느순간 1류가 되는 것이라고 생각한다. ㅋㅋㅋㅋㅋ - RFID 리더기의 절망적인 가격에 좌절되고 말았다.... 가로세로 50cm 정도가 백만원을 훌쩍 넘는다하니, 장난질 하려고 들이기에는 너무 큰 돈이었다. 그래서 그럼 좀더 포터블한 사이즈로 30*30cm 를 생각했으나.. 이것이 80만원 정도 였던가. 헐.

    하지만 우리가 누구던가. 포기하지 않고 만들긴 했다. (완전히 다른 속성의 것이 되어버렸지만.;;) RFID 대신 영상처리를 이용해서 퍽대신 특정 무늬의 카드를 배열하는 방향으로 대체 하였다.
    하지만 급조에 어설프게 되어서 그래픽이라든가 하는 부분은 전혀 다른 양상으로 되었지만. -_- 영상 처리로 하다보니 테이블 위에 빛을 뿌리는 것은 할 수 없었고, 따라서 그냥 모니터로 증강현실을 바라보듯이 그래픽 처리를 봐야 했다. ㅎㅎㅎㅎ.
    하지만 우리들만의 파티였고 재미있었기 때문에 그쯤에서 만족했다.
    그날 나름 프로젝트 발표회랍시고 너댓명 모여서 샴페인 따고 급조 크레페 만들고 ㅋㅋ 재미있었던 기억이. ㅎ (참고로
    tommorrow SIG 랍니다.)

    여담으로.. 지금에와 생각하는 거지만, 프로젝터가 정확한 그래픽을 뿌리기 위한 해상도 설정이 꽤나 힘들었을것 같다. tablescape 과제를 하고 나니.;; 왠지 그냥 그랬을것 같아. 알수 있어. ㅋㅋㅋ