SPOKA Night Light controlled from and Android Phone


You people must have been wondering “what the heck was going on” and why haven’t I posted anything for the last 3 months or so… keep reading and you’ll find out more !

Some of you must have already seen something like this:

Ikea Spoka Night Light

It’s an Ikea Spoka night light and my wife really loves them… to the point that a while back she bought several of them, “just in case”…

We obviously only use one, so I’ve gotten her permission a few months ago to open another one up and play with it.

This project started actually back at the beginning of October 2011, but after doing the hardest part of it (reverse engineering the simple circuit) I had to abandon it in favour of the Stanford Artificial Intelligence and Machine Learning courses, which took most of my week-ends !

Then my son was born at the end of November, and here I am, more than 3 months later, having decided that I need to do whatever it takes to finish the project and post it.

I’m trying to do this while waiting for some special “mega servos” that I plan to use to automate my baby’s bouncer (those of you that have babies, know how important it is to rock them while they try to fall asleep, and what is the phonic price to be paid if not careful… :) ).

So here we go…

Originally, the lamp has 2 modes, that you can select with a switch at the top (which can also turn it off):

  1. slowly change between the 3 available colours (blue, red and orange) while dimming the intensity
  2. keep the same colour and intensity

The “obvious” hack would be to be able to customize the patterns of light that it can display. Another one would be to make it able to synchronize to some random music.

However it occurred to me that both this “mods” and plenty of others could be easily achieved by making the lamp controllable remotely, and then “simply” sending whatever pattern we want from the controlling device. This way we offload the logic to some more powerful device.

It could also lead to having more than 1 lamp synchronised, but this would be for another project…

The first idea was to use these 2 simple wireless transceivers and control the Spoka from my PC.

Then it dawned on me that it would be much nicer to control it from my phone, and what better choice than the IOIO to interface Android with the serial transceiver… ?

And then, I can’t remember how, I found these ultra cheap serial bluetooth transceivers… There are plenty of them on various sites (I got mine from ebay for 5£) and they all seem more or less the same…

Not only it’s much cheaper than a IOIO + the RF transceivers, but it’s also quite small and especially, it doesn’t  require anything to be wired to the phone (since then the IOIO also supports bluetooth, but then it would mean to integrate one in the Spoka which is not an option…).

However, as you’ll see later in this post (and as Ytai told me from the beginning :) ), this advantages come at a price, as the module seems more fiddly and there’s hardly any documentation out there !

Anyhow, the thought of having this little thing integrated in the Spoka and then being able to remotely control it from my phone, felt worth the effort !

1. Reverse engineer the light

Original Circuits

Thank you Ikea for having easily hackable, through hole boards !

Spoka Schema

2. Replace the IC with a custom one

The original IC was a 8 pin one and I would have really liked to be able to just “drop” in my own ATtiny45 or 85. This however proved impossible due to a different pin layout.

Original MCU removed, wires soldered

There was also the need for serial communication with the bluetooth module and 3 independent PWM for the 3 colours. While I’m sure an experienced AVR engineer could fit all this on an ATtiny 45, the thought of implementing software serial and struggle with the conflicts with the PWM timers,  didn’t sound good and hence I opted for a 20pin ATtiny2313 that I had lying around and that provides hardware serial.

ATtiny2313 in place

ATtiny2313 in place 2

The job of the new MCU will be quite simple:

  • wait for incoming serial data containing the intensity of each colour (and a checksum)
  • provide some acknowledgement on the serial line
  • update the PWM values accordingly

And here’s the C code.

Notice that I have actually implemented 2 modes:

  1. if the first character is a ‘#’ then you simply provide the intensity for each colour
  2. if the first character is a ‘*’ then you provide a delay only, and the MCU does the transitioning from one colour to the other (similarly to what the original setup was doing, but with the extra feature of being able to change the speed of transition)
/*
 * uart.h
 *
 * Created: 09/10/2011 Author: trandi
 */

#ifndef UART_H_
#define UART_H_

//BRR = (F_CPU / 16 / BaudRate ) - 1
#define BAUD_RATE_38400 (F_CPU / 16 / 38400 ) - 1
#define BAUD_RATE_4800 (F_CPU / 16 / 4800 ) - 1
#define BAUD_RATE_2400 (F_CPU / 16 / 2400 ) - 1

void uart_init(unsigned char baudrate);
uint8_t uart_isCharAvailable();
unsigned char uart_getChar();
void uart_putChar(unsigned char data);
void uart_putStr(const char* str);
//void uart_putw_dec(uint16_t w);

#endif /* UART_H_ */

/*
 * uart.c
 *
 * Created: 02/10/2011 Author: trandi
 */

#include <avr/io.h>
#include <util/delay.h>

void uart_init(unsigned char baudrate) {
 UBRRL = baudrate; // Set the baud rate

 UCSRB = _BV(RXEN) | _BV(TXEN); // Enable UART receiver and transmitter
 UCSRC = _BV(UCSZ1) | _BV(UCSZ0); // set to 8 data bits, 1 stop bit

 DDRD |= _BV(PD1); // TZ is output
 DDRD &= ~_BV(PD0); // RX is input
}

uint8_t uart_isCharAvailable() {
 return (UCSRA & _BV(RXC));
}

unsigned char uart_getChar() {
 while(! uart_isCharAvailable()) _delay_ms(10);
 char result = UDR;
 //useful echo
 uart_putChar(result);
 return result;
}

void uart_putChar(unsigned char data){
 /* Wait for empty transmit buffer */
 while (!(UCSRA & _BV(UDRE)));

/* Start transmission */
 UDR = data;
}

void uart_putStr(const char* str) {
 while(*str){
 uart_putChar(*str++);
 }
}

/*
void uart_putw_dec(uint16_t w) {
 uint16_t num = 10000;
 uint8_t started = 0;

while(num > 0){
 uint8_t b = w / num;
 if(b > 0 || started || num == 1){
   uart_putChar('0' + b);
   started = 1;
 }
 w -= b * num;

num /= 10;
 }
}*/

/*
 * SpokaLight.cpp
 *
 * Created: 01/10/2011 Author: trandi
 */

#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>
#include "uart.h"

#define PWM1 OCR0A //PB2
#define PWM2 OCR1B //PB3
#define PWM3 OCR1A //PB4
#define BLUE 0
#define RED 1
#define ORANGE 2
#define BUTTON_PORT PORTB // PORTx - register for button output
#define BUTTON_PIN PINB // PINx - register for button input
#define BUTTON_BIT PB1 // bit for button input/output

/*
n = (f / prescaler) * t Where n is the the number of timer ticks (as written in ICR1, OCR1A, OCR1B above).
f is the frequency you run the AVR at.
t is the wanted time.
You can compute this with f in Hz and t in seconds or often better with f in MHz and t in microseconds.
Notice for an 8MHz AVR with /8 prescaler this becomes
n = 8/8*t -> n = t
*/
void init_pwm(){
// OC1A, OC1B, OC0A outputs
 DDRB |= (1<<PB4)|(1<<PB3)|(1<<PB2);
 // TIMER 0
 // Fast PWM mode 3, Clear on compare, clear at TOP
 // TOP set for 255
 TCCR0A = (1<<COM0A0)|(1<<COM0A1)|(1<<WGM01)|(1<<WGM00);
 TCCR0B = (1<<CS00);

// TIMER 1
 // TOP, set for 255Hz
 ICR1 = 255;
 // Fast PWM mode 14, Clear on compare, clear at TOP
 TCCR1A = (1<<COM1A0)|(1<<COM1A1)|(1<<COM1B0)|(1<<COM1B1)|(1<<WGM11);
 TCCR1B = (1<<WGM13)|(1<<WGM12)|(1<<CS10);
}

void set_pwm(uint8_t colour, uint8_t value){
 uint8_t mapped_value = 255 - value;
 if(colour == BLUE) PWM3 = mapped_value;
 else if(colour == RED) PWM1 = mapped_value;
 else if(colour == ORANGE) PWM2 = mapped_value;
}

uint8_t button_is_pressed() {
 // the button is pressed when BUTTON_BIT is clear
 if (bit_is_clear(BUTTON_PIN, BUTTON_BIT)) {
   _delay_ms(25); // debounce time
   if (bit_is_clear(BUTTON_PIN, BUTTON_BIT)) {
   // wait for it to unclear before releasing... !
   while(bit_is_clear(BUTTON_PIN, BUTTON_BIT)) _delay_ms(10);
   return 1;
   }
 }

return 0;
}

// mode 0 - off / 1 - static colours / 2 - dynamic colours
volatile uint8_t _mode = 1;
volatile uint8_t _blue = 50, _red = 50, _orange = 50;
volatile uint8_t _currCol = BLUE, _currVal = 1, _dirUp = 1;
volatile uint16_t _interval = 30, _count = 0;

void update_status(){
 if(_mode == 0 ){
 set_pwm(BLUE, 0);
 set_pwm(RED, 0);
 set_pwm(ORANGE, 0);
 }else if(_mode == 1){
 set_pwm(BLUE, _blue);
 set_pwm(RED, _red);
 set_pwm(ORANGE, _orange);
 }else if(_mode == 2){
 set_pwm(BLUE, 0);
 set_pwm(RED, 0);
 set_pwm(ORANGE, 0);
 }
}

void update_dynamics(){
 // after 100 the intensity of LEDs seems to level off
 if(_currVal == 100) _dirUp = 0;
 else if(_currVal == 0) {
   _dirUp = 1;
   _currCol = (_currCol + 1) % 3;
 }

 _count ++;
 if(_count >= _interval){

_count = 0;
 _currVal = _dirUp ? _currVal + 1 : _currVal - 1;

if(_mode == 2){
 set_pwm(_currCol, _currVal);
 }
 }
}

uint8_t percToByte(uint8_t perc){
 uint16_t temp = perc * 255;
 return temp / 100;
}

int main(void){
 init_pwm();
 uart_init(BAUD_RATE_38400);

while(1){
 // 1. check for instructions over serial line
 if(uart_isCharAvailable()){
 uint8_t temp = uart_getChar();
 if(temp == '#'){
 _mode = 1;
 uart_putStr("BRO ");
 uint8_t blue = uart_getChar();
 uint8_t red = uart_getChar();
 uint8_t orange = uart_getChar();
 uint8_t checksum = uart_getChar(); //very basic check sum, it simply has to be = to the SUM of prev values, modulo 100

if(checksum == ((uint16_t)blue + (uint16_t)red + (uint16_t)orange) % 100){
 uart_putStr("OK");
 _blue = percToByte(blue);
 _red = percToByte(red);
 _orange = percToByte(orange);
 update_status();
 }else{
 uart_putStr("Nok");
 }
 }else if(temp == '*'){
 // go in mode "dynamic"
 _mode = 2;
 uart_putStr("Interval ");
 _interval = ((uint16_t)uart_getChar()) * 50;
 _currCol = BLUE;
 _currVal = 1;
 update_status();
 }
 }

// 2. check for button pressed or not
 if(button_is_pressed()){
 _mode = (_mode + 1) % 3;
 update_status();
 }

// 3. do whatever periodic action is needed
 update_dynamics();

// 4. get some rest
 //_delay_ms(1);
 }
}

Works just fine when connected to a PC serial port

3. Connect the bluetooth transceiver

Cheap Serial Bluetooth Transceiver

Bluetooth Serial connected, first tests...

4. Program the Android phone

This was (and still is) quite frustrating…

I love Android and its ease of programming in Java ! They really make your life much easier by providing everything one needs … Unless something doesn’t work as expected that is !

In my case, it was a matter of hours before I put together a simple view, like a joystick with 3 axes. It’s a simple gray dot that the user can move relative to 3 coloured circles, each corresponding to a set of LEDs on the lamp, which is supposed to update its intensity accordingly.

All the “back end” needs to do, is to measure the distance between the gray circle and the other 3 “reference” ones, transform this into a percentage and send it across the serial bluetooth line to the MCU.

Everything works nicely, EXCEPT the bluetooth serial port profile connection.

First of all, when I test the connection from the PC, there are no issues what so ever.

On Android however, I get garbled characters most of the time and sometimes the right one. It really feels like a timing issue…

I’ve based my code on the example coming with the Android SDK. The sending of bytes is done in the main thread, but the receiving has its own dedicated one.

The really strange thing is that depending on how and when I transform the bytes into a String, the code works or not…

If I disable the receiving thread and I only send data from the phone to the MCU, then it works 95% of the time, which again makes me think that it’s a timing issue on the Android side.

That’s for now the status, I basically send the instructions from the phone, and do NOT wait for the acknowledgement from the ATtiny.

Here’s the code. Ytai, you must have spend quite a lot of time on this yourself when bluetooth enabling the IOIO, if you have any idea or suggestion, it would be hugely appreciated !

BTDeviceListActivity.java
-------------------------------------------------------
package trandi.spokalightbluetooth;

import java.util.Set;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;

/**
 * This Activity appears as a dialog. It lists any paired devices and
 * devices detected in the area after discovery. When a device is chosen
 * by the user, the MAC address of the device is sent back to the parent
 * Activity in the result Intent.
 */
public class BTDeviceListActivity extends Activity {
 // Debugging
 private static final String TAG = "BTDeviceListActivity";

 // Return Intent extra
 public static String EXTRA_DEVICE_ADDRESS = "device_address";

 // Member fields
 private BluetoothAdapter _btAdapter;
 private ArrayAdapter<String> _pairedDevicesArrayAdapter;
 private ArrayAdapter<String> _newDevicesArrayAdapter;

 // The BroadcastReceiver that listens for discovered devices and changes the title when discovery is finished
 private final BroadcastReceiver _receiver = new BroadcastReceiver() {
 @Override
 public void onReceive(Context context, Intent intent) {
 String action = intent.getAction();

// When discovery finds a device
 if (BluetoothDevice.ACTION_FOUND.equals(action)) {
 // Get the BluetoothDevice object from the Intent
 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
 // If it's already paired, skip it, because it's been listed already
 if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
 _newDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
 }
 // When discovery is finished, change the Activity title
 } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
 setProgressBarIndeterminateVisibility(false);
 setTitle(R.string.select_device);
 if (_newDevicesArrayAdapter.getCount() == 0) {
 _newDevicesArrayAdapter.add(getResources().getText(R.string.none_found).toString());
 }
 }
 }
 };

 // The on-click listener for all devices in the ListViews
 private OnItemClickListener _deviceClickListener = new OnItemClickListener() {
 @Override
 public void onItemClick(AdapterView<!--?--> av, View v, int arg2, long arg3) {
 // Cancel discovery because it's costly and we're about to connect
 _btAdapter.cancelDiscovery();

// Get the device MAC address, which is the last 17 chars in the View
 String info = ((TextView) v).getText().toString();
 String address = info.substring(info.length() - 17);

// Create the result Intent and include the MAC address
 Intent intent = new Intent();
 intent.putExtra(EXTRA_DEVICE_ADDRESS, address);

// Set result and finish this Activity
 setResult(Activity.RESULT_OK, intent);
 finish();
 }
 };

 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);

// Setup the window
 requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
 setContentView(R.layout.device_list);

// Set result CANCELED incase the user backs out
 setResult(Activity.RESULT_CANCELED);

// Initialize the button to perform device discovery
 Button scanButton = (Button) findViewById(R.id.button_scan);
 scanButton.setOnClickListener(new OnClickListener() {
 public void onClick(View v) {
 doDiscovery();
 v.setVisibility(View.GONE);
 }
 });

// Initialize array adapters. One for already paired devices and one for newly discovered devices
 _pairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
 _newDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);

// Find and set up the ListView for paired devices
 ListView pairedListView = (ListView) findViewById(R.id.paired_devices);
 pairedListView.setAdapter(_pairedDevicesArrayAdapter);
 pairedListView.setOnItemClickListener(_deviceClickListener);

// Find and set up the ListView for newly discovered devices
 ListView newDevicesListView = (ListView) findViewById(R.id.new_devices);
 newDevicesListView.setAdapter(_newDevicesArrayAdapter);
 newDevicesListView.setOnItemClickListener(_deviceClickListener);

// Register for broadcasts when a device is discovered & discovery has finished
 this.registerReceiver(_receiver, new IntentFilter(BluetoothDevice.ACTION_FOUND));
 this.registerReceiver(_receiver, new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED));

// Get the local Bluetooth adapter
 _btAdapter = BluetoothAdapter.getDefaultAdapter();

// Get a set of currently paired devices
 Set pairedDevices = _btAdapter.getBondedDevices();

// If there are paired devices, add each one to the ArrayAdapter
 if (pairedDevices.size() > 0) {
 findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE);
 for (BluetoothDevice device : pairedDevices) {
 _pairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
 }
 } else {
 _pairedDevicesArrayAdapter.add(getResources().getText(R.string.none_paired).toString());
 }
 }

 @Override
 protected void onDestroy() {
 super.onDestroy();

// Make sure we're not doing discovery anymore
 if (_btAdapter != null) {
 _btAdapter.cancelDiscovery();
 }

// Unregister broadcast listeners
 this.unregisterReceiver(_receiver);
 }

 /**
 * Start device discover with the BluetoothAdapter
 */
 private void doDiscovery() {
 Log.d(TAG, "doDiscovery()");

// Indicate scanning in the title
 setProgressBarIndeterminateVisibility(true);
 setTitle(R.string.scanning);

// Turn on sub-title for new devices
 findViewById(R.id.title_new_devices).setVisibility(View.VISIBLE);

// If we're already discovering, stop it
 if (_btAdapter.isDiscovering()) {
 _btAdapter.cancelDiscovery();
 }

// Request discover from BluetoothAdapter
 _btAdapter.startDiscovery();
 }
}

MainActivity.java
-------------------------------------------------------
package trandi.spokalightbluetooth;

import java.io.IOException;

import trandi.spokalightbluetooth.MainView.ColValues;
import trandi.spokalightbluetooth.MainView.JoystickListener;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.Window;
import android.widget.TextView;
import android.widget.Toast;

/**
 * Main activity to control the hacked Spoka light.
 *
 * @author trandi
 */
public class MainActivity extends Activity {
 // Debugging
 private static final String TAG = "SpokaLightBluetooth_MainActivity";

// Intent request codes
 private static final int REQUEST_CONNECT_DEVICE = 1;
 private static final int REQUEST_ENABLE_BT = 2;

 private TextView _title;
 private BluetoothAdapter _bluetoothAdapter;
 private Spoka _spoka;

 @Override
 public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 Log.e(TAG, "+++ ON CREATE +++");

// Set up the window layout
 requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
 setContentView(R.layout.main);
 getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title);
 // register this activity with the View, to listen to Joystick events
 ((MainView)findViewById(R.id.myMainView)).addJoystickListener(new JoystickListener() {
 @Override
 public void onMove(ColValues newColours) {
 if(_spoka != null){
 final String result = _spoka.updateColours(newColours.red, newColours.blue, newColours.orange) ? "OK" : "NOK";

 Log.d(TAG, "Update Colours " + result + " (" + newColours + ")");
 }
 }
 });

// Set up the custom title
 _title = (TextView) findViewById(R.id.title_left_text);
 _title.setText(R.string.app_name);
 _title = (TextView) findViewById(R.id.title_right_text);

// Get local Bluetooth adapter
 _bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

// If the adapter is null, then Bluetooth is not supported
 if (_bluetoothAdapter == null) {
 Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show();
 finish();
 return;
 }
 }

 @Override
 public void onStart() {
 super.onStart();
 Log.d(TAG, "++ ON START ++");

if (!_bluetoothAdapter.isEnabled()) {
 // If BT is not on, request that it be enabled.
 Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
 // Asynchronous, the onActivityResult will be called back when finished
 startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
 }else {
 // Bluetooth is already enabled
 // Launch the BTDeviceListActivity to see devices and do scan
 Intent serverIntent = new Intent(this, BTDeviceListActivity.class);
 startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);
 }
 }

@Override
 public void onDestroy() {
 super.onDestroy();
 Log.d(TAG, "++ ON DESTROY ++");

 if(_spoka != null) _spoka.disconnect();
 }

public void onActivityResult(int requestCode, int resultCode, Intent data) {
 Log.d(TAG, "onActivityResult " + resultCode);

 switch (requestCode) {
 case REQUEST_CONNECT_DEVICE:
 // When BTDeviceListActivity returns with a device to connect
 if (resultCode == Activity.RESULT_OK) {
 // Get the device MAC address
 String address = data.getExtras().getString(BTDeviceListActivity.EXTRA_DEVICE_ADDRESS);
 // Create a business object which attempts to create to the Spoka !
 //ensureDiscoverable(_bluetoothAdapter);
 try {
 _spoka = new Spoka(_bluetoothAdapter, address);
 } catch (Exception e) {
 Toast.makeText(this, "Can't connect to the SPOKA", Toast.LENGTH_SHORT).show();
 finish();
 }
 }
 break;
 case REQUEST_ENABLE_BT:
 // When the request to enable Bluetooth returns
 if (resultCode == Activity.RESULT_OK) {
 // Bluetooth is now enabled
 // Launch the BTDeviceListActivity to see devices and do scan
 Intent serverIntent = new Intent(this, BTDeviceListActivity.class);
 startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);
 } else {
 // User did not enable Bluetooth or an error occured
 Log.d(TAG, "BT not enabled");
 Toast.makeText(this, "User did not enable Bluetooth or an error occured", Toast.LENGTH_SHORT).show();
 finish();
 }
 }
 }

 private void ensureDiscoverable(BluetoothAdapter bluetoothAdapter) {
 if (bluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
 Log.d(TAG, "Force discoverable");
 Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
 discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
 startActivity(discoverableIntent);
 }
 }
}

MainView.java
-------------------------------------------------------
package trandi.spokalightbluetooth;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class MainView extends View {
 private static final Point _red = new Point(50, 100);
 private static final Point _blue = new Point(250, 100);
 private static final Point _yellow = new Point(150, 300);

 private final List<JoystickListener> _joystickListeners = new ArrayList();

 private Point _joystick = new Point(150, 200);
 private final int _r = 30;
 private boolean _move = false;
 private long _lastMove = 0;

 public MainView(Context context, AttributeSet attrs) {
 super(context, attrs);
 }

 public synchronized void addJoystickListener(JoystickListener listener) {
 _joystickListeners.add(listener);
 }

 @Override
 public boolean onTouchEvent(MotionEvent me) {
 // are we trying to move the "joystick"
 if(Math.abs(me.getX() - _joystick.x) <= _r && Math.abs(me.getY() - _joystick.y) <= _r){
 if(me.getAction() == MotionEvent.ACTION_DOWN){
 _move = true;
 }else if(me.getAction() == MotionEvent.ACTION_UP){
 _move = false;
 }else if(me.getAction() == MotionEvent.ACTION_MOVE
 && _move
 && (System.currentTimeMillis() - _lastMove > 50))
 {
 _lastMove = System.currentTimeMillis();
 // call all the listeners
 new Thread(){
 @Override
 public void run(){
 ColValues updatedColours = new ColValues();
 for(JoystickListener listener : _joystickListeners){
 listener.onMove(updatedColours);
 }
 }
 }.start();
 }
 }

 if(_move){
 _joystick.set(Math.round(me.getX()), Math.round(me.getY()));

 // force to redraw
 this.invalidate();
 }

 return true;
 }

 @Override
 protected void onDraw(Canvas canvas) {
 super.onDraw(canvas);

 // draw the fixed circles
 Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
 paint.setColor(Color.RED);
 canvas.drawCircle(_red.x, _red.y, 20, paint);
 paint.setColor(Color.BLUE);
 canvas.drawCircle(_blue.x, _blue.y, 20, paint);
 paint.setColor(Color.YELLOW);
 canvas.drawCircle(_yellow.x, _yellow.y, 20, paint);

 // draw the moving circle (the "joystick")
 paint.setColor(Color.GRAY);
 canvas.drawCircle(_joystick.x, _joystick.y, _r, paint);
 }

 public static interface JoystickListener {
 void onMove(ColValues newColours);
 }

 public class ColValues {
 public final int red;
 public final int blue;
 public final int orange;

 public ColValues(int red, int blue, int orange){
 this.red = red;
 this.blue = blue;
 this.orange = orange;
 }

 /**
 * Calculates the distances from the position of the joystick and base colour circles
 */
 public ColValues(){
 final double maxDist = getDist(_red, _blue);

this.red = 100 - (int)Math.round(getDist(_joystick, _red) * 100.0 / maxDist);
 this.blue = 100 - (int)Math.round(getDist(_joystick, _blue) * 100.0 / maxDist);
 this.orange = 100 - (int)Math.round(getDist(_joystick, _yellow) * 100.0 / maxDist);
 }

 private int getDist(Point a, Point b) {
 return (int)Math.round(Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2)));
 }

@Override
 public String toString() {
 return "ColValues [red=" + red + ", blue=" + blue + ", orange=" + orange + "]";
 }
 }
}

Spoka.java
-------------------------------------------------------
package trandi.spokalightbluetooth;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.util.Log;

public class Spoka {
 // Debugging
 private static final String TAG = "SPOKA";

 // Unique UUID for this application
 // has to be this precise value for SERIAL port profile (SPP)
 private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
// private static final UUID MY_UUID_SECURE = UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66");
// private static final UUID MY_UUID_INSECURE = UUID.fromString("8ce255c0-200a-11e0-ac64-0800200c9a66");

 private BluetoothSocket _btSocket;
 private InputStream _socketIS;
 private OutputStream _socketOS;
 // will need to SYNCHRONISE on this as it's used from multiple threads !
// private Queue<String> _incomingData = new ConcurrentLinkedQueue<String>();
// private String _incomingDataStr = "";

 public Spoka(BluetoothAdapter bluetoothAdapter, String address) throws IOException{
 Log.d(TAG, "++ CONNECT SPOKA ++");

// Get the BluetoothDevice object
 BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address);

 // Get a BluetoothSocket for a connection with the given BluetoothDevice
 try {
 _btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
 } catch (IOException e) {
 Log.e(TAG, "createRfcommSocketToServiceRecord() failed", e);
 throw e;
 }

// Attempt to connect to the device
 // Always cancel discovery because it will slow down a connection
 bluetoothAdapter.cancelDiscovery();

 // Make a connection to the BluetoothSocket
 try {
 // This is a blocking call and will only return on a successful connection or an exception
 _btSocket.connect();
 Log.d(TAG, "++ connectED SPOKA ++");
 } catch (IOException e) {
 disconnect();
 Log.e(TAG, "Can't connect to the Spoka", e);
 throw e;
 }

 // !!! COMMENTED OUT THE WHOLE INCOMING SERIAL DATA READ, AS THIS SEEMS TO CAUSE THE RECEIVED BYTES TO BE GARBLED !!!
 // start a separate thread that monitors any incoming data, and stores it in a queue
 // this is done so, because we don't seem to have an asynchronous read() on the BluetoothSocket InputStream
// new Thread(){
// @Override
// public void run(){
// byte[] buff = new byte[100];
// while(true){
// try{
// if(_socketIS == null) _socketIS = new BufferedInputStream(_btSocket.getInputStream(), 1024);
//
// if(_socketIS != null){
// // blocking on the read when there's nothing...
// int readCount = _socketIS.read(buff);
// // for some VERY SILLY reason, if we store the bytes here, they come garbled...
// _incomingData.add(new String(buff, 0, readCount));
// }
// } catch (Exception e) {
// Log.e(TAG, "Can't read message from the Spoka", e);
// }
// }
// }
// }.start();
 }

 public void disconnect() {
 try {
 if(_btSocket != null) _btSocket.close();
 } catch (IOException e) {
 Log.e(TAG, "Unable to close() socket during connection failure", e);
 }
 }

 public boolean updateColours(int redPerc, int bluePerc, int orangePerc) {
 byte red = checkPerc(redPerc);
 byte blue = checkPerc(bluePerc);
 byte orange = checkPerc(orangePerc);

 // 1. send the special char "#" indicating to the Spoka that we want to manually update the colours
 sendBytes((byte)'#');

 // 2. check that we receive back the "BRO " acknowledgement( Blue, Red, Orange)
 //if(waitForIncomingData("BRO", 100)){
 // 3. send the values & the check sum
 sendBytes(blue, red, orange, (byte)(((int)blue + (int)red + (int)orange) % 100));

 // 4. check that we receive back confirmation
 //return waitForIncomingData("OK", 100);
 //}

 return false;
 }

 private byte checkPerc(int perc){
 return (byte)(perc < 0 ? 0 : (perc > 100 ? 100 : perc));
 }

 private void sendBytes(byte...bytes) {
 try{
 if(_socketOS == null) _socketOS = new BufferedOutputStream(_btSocket.getOutputStream(), 1024);

 if(_socketOS != null){
 _socketOS.write(bytes);
 _socketOS.flush();
 }
 } catch (IOException e) {
 Log.e(TAG, "Can't send message to the Spoka", e);
 }
 }

 private boolean waitForIncomingData(String expectedData, int timeoutMillis){
 final long startTime = System.currentTimeMillis();
 byte[] buff = new byte[100];
 String incomingData = "";
 try{
 if(_socketIS == null) _socketIS = new BufferedInputStream(_btSocket.getInputStream(), 1024);

 if(_socketIS != null){
 while((! incomingData.contains(expectedData))
 && (System.currentTimeMillis() - startTime < timeoutMillis))
 {
 // blocking on the read when there's nothing...
 int readCount = _socketIS.read(buff);
 // for some VERY SILLY reason, if we store the bytes here, they come garbled...
 incomingData += new String(buff, 0, readCount);
 }
 }
 }catch(IOException e){
 Log.e(TAG, "Can't receive incoming data", e);
 }

 Log.d(TAG, "RECV: " + incomingData);

 if(incomingData.contains(expectedData)){
 return true;
 }

return false;
 }

// private boolean waitForIncomingData(String expectedData, int timeoutMillis){
// final long startTime = System.currentTimeMillis();
//
// while((! _incomingDataStr.contains(expectedData))
// && (System.currentTimeMillis() - startTime < timeoutMillis))
// {
// final String head = _incomingData.poll();
// if(head != null) _incomingDataStr += head;
// Thread.yield();
// }
//
// if(_incomingDataStr.contains(expectedData)){
// // REMOVE the expected data (and whatever was before) from the string buffer
// final int pos = _incomingDataStr.indexOf(expectedData) + expectedData.length();
// _incomingDataStr = _incomingDataStr.substring(pos);
//
// return true;
// }
//
// Log.d(TAG, "RECV: " + _incomingDataStr);
// return false;
// }

// /**
// * @return true if the expected incoming data has been received, false if timeout
// */
// private boolean waitForIncomingData(String incomingData, int timeoutMillis){
// final long startTime = System.currentTimeMillis();
// // wait until received expected data or timeout
// while(! peekIncomingData().contains(incomingData) && (System.currentTimeMillis() - startTime < timeoutMillis)){
// try {
// Thread.sleep(10);
// } catch (InterruptedException e) {
// // nothing to do
// }
// }
//
// final int position = peekIncomingData().indexOf(incomingData);
// if(position > 0){
// // now remove the data from the incoming queue
// for(int i=0; i < position + incomingData.length(); i++){
// _incomingData.poll();
// }
// return true; // found the expected string
// }else{
// Log.e(TAG, peekIncomingData());
// }
//
// return false; // NOT found the expected string and timed out
// }
//
// private String peekIncomingData() {
// Byte[] data = _incomingData.toArray(new Byte[]{});
// byte[] dataChars = new byte[data.length];
// for(int i=0; i
// dataChars[i] = data[i];
// }
// return new String(dataChars);
// }
}

And here are all the XML configuration files, specific to Android platforms:

custom_title.xml
-------------------------------------------------------
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:gravity="center_vertical">

 <TextView android:id="@+id/title_left_text"
 android:layout_alignParentLeft="true"
 android:ellipsize="end"
 android:singleLine="true"
 style="?android:attr/windowTitleStyle"
 android:layout_width="wrap_content"
 android:layout_height="match_parent"
 android:layout_weight="1"/>

 <TextView android:id="@+id/title_right_text"
 android:layout_alignParentRight="true"
 android:ellipsize="end"
 android:singleLine="true"
 android:layout_width="wrap_content"
 android:layout_height="match_parent"
 android:textColor="#fff"
 android:layout_weight="1"/>

</RelativeLayout>

device_list.xml
-------------------------------------------------------
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:orientation="vertical"
 android:layout_width="match_parent"
 android:layout_height="match_parent">

 <TextView android:id="@+id/title_paired_devices"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:text="@string/title_paired_devices"
 android:visibility="gone"
 android:background="#666"
 android:textColor="#fff"
 android:paddingLeft="5dp"/>

 <ListView android:id="@+id/paired_devices"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:stackFromBottom="true"
 android:layout_weight="1"/>

 <TextView android:id="@+id/title_new_devices"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:text="@string/title_other_devices"
 android:visibility="gone"
 android:background="#666"
 android:textColor="#fff"
 android:paddingLeft="5dp"/>

 <ListView android:id="@+id/new_devices"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:stackFromBottom="true"
 android:layout_weight="2"/>

 <Button android:id="@+id/button_scan"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:text="@string/button_scan"/>

</LinearLayout>

device_name.xml
-------------------------------------------------------
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:textSize="18sp"
 android:padding="5dp"/>

main.xml
-------------------------------------------------------
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:trandi= "http://schemas.android.com/apk/res/trandi.spokalightbluetooth"
 android:orientation="vertical"
 android:layout_width="match_parent"
 android:layout_height="match_parent">

 <FrameLayout android:id="@+id/drawingFrame" android:layout_height="match_parent" android:layout_width="match_parent">
 <trandi.spokalightbluetooth.MainView android:id="@+id/myMainView" android:layout_height="match_parent" android:layout_width="match_parent"/>
 </FrameLayout>
</LinearLayout>

strings.xml
-------------------------------------------------------
<?xml version="1.0" encoding="utf-8"?>
<resources>
 <string name="app_name">Spoka Light Bluetooth</string>

 <!-- BTDeviceListActivity -->
 <string name="scanning">scanning for devices...</string>
 <string name="select_device">select a device to connect</string>
 <string name="none_paired">No devices have been paired</string>
 <string name="none_found">No devices found</string>
 <string name="title_paired_devices">Paired Devices</string>
 <string name="title_other_devices">Other Available Devices</string>
 <string name="button_scan">Scan for devices</string>
</resources>

5. Final result

Everything connected, ready for final tests

On the stripboard

Putting everything back

Naked, without the silicone skin...

About these ads

133 Responses to SPOKA Night Light controlled from and Android Phone

  1. Andreas Krause says:

    hi trandi,
    its a really great project and i like it very much!
    can you please post the schematic or send it by mail?
    thanks.

    andi

  2. Raphael Kiefmann says:

    I really like your project. I’m almost done, the only thing that isn’t running fine is the app. So I wonder if it would be possible to send the apk to me?

  3. yang says:

    Hello, I ‘m a student.Can you give me more information? I want to try. THX ~

  4. Raphael says:

    I really like your project. I am almost done with the whole thing. But there’s still a problem: I don’t know much about programming in C. So would it be possible to send me the uart header file?

  5. Pingback: TM1638 Display Driver for Stellaris Launchpad « Robotics / Electronics / Physical Computing

  6. Frank says:

    Hi trandi,

    just found your project. I’m a student an would love to start with Android and some BT programming.

    Could you please also send me the files?

    I’ll report as I was successful :)

    Thank you

  7. melinda says:

    i just need the ac adapter for mine! I bought it from a thrift store not knowing an adapter was needed!

  8. Martin says:

    Hi! I’ve tried hard to get this working, but I don’t get it. Can you please send me your sourcecodes for attiny and your app?

  9. Wu says:

    hey i’m from China , i can’t speak very well.. don’t mind….

    can you send me this app..?

    I use ECLIPSE to try but found many errors…

    thx !

  10. utsav says:

    Hey.. great project !!!
    m a student in India…
    m planning on making a bluetooth tag kind of something …which is to b controlled by a smartphone….after pairing it up with a application which ill develop on the smartphone….if the tag is taken apart say about more than 5m from the phone….it should trigger a alarm on the phone……can u plzzz guide me on that …….i wud love to b guided by u….

    • trandi says:

      Hi Utsav,

      I know you would love me to do your homework for you, but unsurprisingly I have other priorities…:)

      On a more serious note, if you have any SPECIFIC and PRECISE technical question about your project, then please don’t hesitate to ask.

      Dan

      • utsav says:

        thnx for the reply…how do make a bluetooth tag using a module..that can b paired up with a smartphone???

  11. Peter Halverson says:

    Is it possible to post the hex file? I’m having a difficulty compiling.

  12. michael blomquist says:

    Hi Trandi !
    Awesome project , really !
    I am about to create something similar with 2 or3 spokas.
    could you provide me with your code (zip)

    thanks alot
    mike

  13. Pingback: SPOKA Night Light & Android – RETURNS « Robotics / Electronics / Physical Computing

  14. Soenke says:

    Hey there,

    i’m a student in Germany.
    For a project in school I want to realize that project on my own.
    But i’m focusing on the MCU-Part and not on programming on the Android-platform.

    It would be very nice if you could send me the apk you created.

    Thank you very much!!

    Greets

    Soenke

    • trandi says:

      Done, sent to your e-mail.

      I would love to see some pictures of your project…

      Dan

      • Felix says:

        Hi trandi,

        can you also send me the android code for the bluetooth connection? Thanks in advance.

      • trandi says:

        done, check your e-mail… hope it will help you !
        Do let me know if you create anything interesting…

        Dan

      • taydog1 says:

        could I also have a copy of the android code. Would like to break it down and see how it works. thanks in advance

    • yang says:

      Hi Soenke,

      i’m a student in Taiwan.
      I want try it.
      Can you also send me the android code for the bluetooth connection? Thanks in advance.
      My E-mail:sdestiny20a@gmail.com

  15. wejp says:

    Good work there! You’ve have inspired me with your SPÖKA mod to do something similar myself. I have modified a SPÖKA light to be controlled over USB:

    http://wejp.k.vu/hacks/usb_controlled_spoka_night_light

    I have used the other SPÖKA model and I found it a little surprising that IKEA actually used different PCBs inside of the two SPOKA models.

    • trandi says:

      Wow, this is really great work !

      I don’t think the PCB’s are so different because you have the taller SPOKA model, but simply because you bought it at another time (mine was bought several years ago !) and they must have updated the design… I could check as I have a tall light too, but I don’t think my wife would let me hack the other one too … :)

      Dan

    • trandi says:

      Hi,
      I have submitted your work to hack a day, and here you are, featured on their website :

      http://hackaday.com/2012/03/14/usb-controlled-spoka-night-light/

      Now we have to keep up the SPOKA light hacks chain… lol…:)

      Dan

  16. birdming22 says:

    excellent,
    What license are this code under?

    • trandi says:

      there’s not really a “license” : the code is posted for everybody to use as they see fit, but obviously WITHOUT ANY GUARANTEE, explicit or implicit !

      dan

      • birdming22 says:

        thanks for your reply.
        I need the bluetooth part of android program.
        thank you so much

      • trandi says:

        It’s in there ! Right at the beginning of the big chunk of pasted code there is something starting with “BTDeviceListActivity.java”…

        dan

  17. Hi,
    Sorry this is a bit off-topic, but:
    Does your modified (or virgin) SPOKA stay on during a power-cut?
    If mine is on and mains-powered, and then I turn off the mains power at the wall, the light goes out (rather than automatically switching to battery power): I wanted a nightlight for my son that would stay on during a power cut (he is badly scared of the dark), so the out-of-the-box functionality is no good for me.
    I loved your hack by the way, I’d love to see a row of those responding to music at a party:)

    • trandi says:

      Yes it does.
      Funny, I had never noticed that before, the fact that when you unplug the power the original Spoka goes off.

      Yeah, this idea of having several of them connected to the same phone and lighting up in sync seems to be one of the most interesting… However, I don’t feel like going through all the soldering again several times… :)

      Dan

  18. Pingback: IKEA Nightlight Hacked for Android-based Control | The Copy Paste Blog

  19. Pingback: 床頭燈變迷幻燈,全靠 Android 改做 | UNWIRE.HK 流動科技生活

  20. Pingback: IKEA Nightlight Hacked for Android-based Control : LinuxBuzz.net

  21. Pingback: IKEA Nightlight Hacked for Android-based Control | CisforComputers

  22. Pingback: IKEA Nightlight Hacked for Android-based Control | iPhone 2 die 4

  23. Pingback: DIY 用手機控制燈光不是夢? App-Goog-les

  24. Pingback: Android también puede gobernar lámparas: ¡Aquí la prueba en vídeo! | TecnoBlog

  25. Pingback: Android también puede gobernar lámparas: ¡Aquí la prueba en vídeo! | Vida-gamer.com

  26. Pingback: Android también puede gobernar lámparas: ¡Aquí la prueba en vídeo! | Tecno BLog celulares nuevos, descargas de temas juegos y aplicaciones,todo sobre nuevos modelos de celulares

  27. Pingback: Lámparas de Ikea podrán controlarse con Android | Universo Digital Noticias

  28. Pingback: Android también puede gobernar lámparas: ¡Aquí la prueba en vídeo! - Wayerless

  29. Pingback: DIY用手機控制燈光不是夢? | TechOrange

  30. alex73 says:

    1st, came here via heise.de too :-)

    2nd, went to IKEA got one of the lights myself to try this nice mod :-)
    Reverse engineered the PCB and asked myself: what the heck do they do with Q1??!!
    Do you have any idea what this piece of circuit is for?
    How did you connect IC.3 and IC.4 to your uC?

    • trandi says:

      Have no idea about Q1, I haven’t spent too long understanding everything, I simply assumed all the transistors were there to drive the LEDs.
      ALL I did, was to connect 3 of the pins of my new MCU to the lines controlling the LEDs (1 per colour) and 1 extra pin to the switch button at the top of the Spoka.

      For pins 3 & 4, as you can see from my little diagram (the only one :) ) IC3 was connected to the switch which is now connected to PB1 on the ATTiny2313 and the old IC4, is not connected to anything, I think it just pulls up IC3.

      Dan

      • alex73 says:

        Ha! Most important for now to me is, that I can simply/safely ignore Q1 surroundings, great :-)

        After the mod, did you encounter a significant decrease of battery lifetime?

      • trandi says:

        YES, indeed, now the battery only lasts for a couple of days, even if the lights are off ! You can see that my simple hack doesn’t deal with any “sleep mode” for the ATTiny, so it keeps running in the background all the time… worst probably, is the bluetooth module though.

        Normally, I should have a mode where both the MCU and the bluetooth board go to sleep (I think it’s supported by both, but I couldn’t be bothered… lol… :) )

        dan

    • TuX11 says:

      Judging from the circuitry surrounding Q1 my first thought was that it is some sort of switch. The idea is that it shuts down power to the µc when it is put into off mode. Pressing the pushbutton turns q1 on for the time of the button press. the µc then initializes and pulls an output-pin to ground level so that Q1 will remain conductive after the button has been released. I might be wrong about this, as i did not verify this yet. I’m putting some more time in improving the C code which is quite difficult as i never did anything in C before, only Perl. I thought about porting to the atmega8, including obdev.at’s v-usb stack and add some features like RFM12-radio relay support(so you can control multiple lights wirelessly from one master light that is controlled by usb or bluetooth) but this is easier to do in asm than in C (at least to me it is :) )

      • trandi says:

        Initially the switch had 3 modes (so NOT only on /off) : off, on and constant light, on and changing colours… so I’m not sure about the Q1 used to put the uC in off mode… but I’m really not an expert…lol…

        Dan

  31. Pingback: IKEA Spoka Lamp Controlled By Android Smartphone (Video) - IT Lounge

  32. Pingback: IKEA Spoka Lamp Controlled By Android Smartphone (Video) - Mobile Magazine

  33. Pingback: Controla una lampara de Ikea con tu dispositivo Android [VIDEO]

  34. Pingback: CoCoLink - Tu Web de Electronica!..

  35. Pingback: Controla una lámpara de Ikea con tu móvil Android - La Isla Buscada

  36. Mike says:

    Awesome job Ytai, before we know it everything will be Android controlled!

    I have had pretty much exactly the same problem while making my Android controlled LED shirt (www.youtube.com/watch?v=DcY8FnuDObA). I ended up only using unidirectional SPP communication on my Android HTC INC2 using the BlueSmirf modules from Sparkfun, so you can feel good that spending more money wouldn’t have solved the problem. Most of my BT code came from the example from the SDK.

    I think we’re onto the right track on concatenating the results of different read operations. I had my best results reading one byte at a time without a timeout in a separate thread, and padding all transmissions with a start string (say ***) and an end string (say &&&). This way I would keep reading until I saw an end string and the length was correct. This got pretty close, but still wasn’t perfect.

    And as the Halloween party I was going to wear the shirt to came closer, I came to the same conclusion as you that ACKs weren’t really necessary.

    I hope sometime in the future someone figures this out and shares the code. If I do, I’ll keep you up to date, and I hope you’ll do the same.

    Cheers,

    • trandi says:

      Great video, and great T-Shirt !

      So you’re saying that you have the same problems with the BlueSmirf module from Sparkfun… interesting…
      Ytai mentioned that his does indeed uses bi-directional SPP over bluetooth for IOIO, and it works OK without him having done anything special …

      I’ll definitely be interested if you find a solution, and will obviously post an update here if I find one myself !

      Dan

    • Ytai says:

      I think you meant “Awesome job, Dan”, but thanks anyway :)

      That shirt is just awsome!!! I’ve made a similar project with IOIO here:

      http://www.instructables.com/id/Android-Controlled-LED-Strip-IOIO-Powered/

      The code is there too, in case it helps…

      Would be great if your shirt could take audio input from the Android and sync to the music :)

    • Mike says:

      Ooops, yea I meant “Awesome job, Dan”. That will teach me to stay up late posting comments. I am planning on upgrading the shirt soon to sync with music using the Android mic. Hoping I can adapt the code from here (http://code.google.com/p/moonblink/wiki/Audalyzer) I’ll take a look at Ytai’s code when I do.

  37. Pingback: Spoka light controlled from Android phone « Ikea « Cool Internet Projects

  38. Pingback: Hack an ikea night light to be controlled from your phone - BuildLounge » BuildLounge

  39. Pingback: Ikea Night Light controlled by an Android Phone « « SNAP-DIYSNAP-DIY

  40. Pingback: Ikea Night Light controlled by an Android Phone | iGadgetView.com

  41. TuX11 says:

    I picked up your project on heise.de and was fascinated as i am building led lights for quite some time and this seems like a perfect device for starting something new. I think you can replace the LEDs with red, green and blue ones. As the voltages of typical blue almost matches the built in purple and typical green almost matches the built in orange, this shouldn’t be too much of a problem. I also thought about replacing the mcu with something similar to http://davehillier.wordpress.com/2009/03/30/building-the-cube/ . But your project is even better as it can be used as a wireless notifier, even from a pc :) However i’d still prefer the wireless transceivers (RFM12, about €5 each on various sources). They have a much greater range than bluetooth.

    • trandi says:

      Thanks for your comment !

      Wow, the light cube you link to is quite nice… I like the minimalistic approach, small MCU that must be doing the USB in software, etc. … I was too lazy to implement even software UART, even less USB… hence the ATtiny2313 as opposed to a an ATtiny45 (which was also my initial idea).

      Regarding you wireless transceivers, again that was my initial idea, and I had even got 2 of them (see this post http://trandi.wordpress.com/2011/10/24/simple-serial-transceiver/) specifically for this task…
      However, bluetooth is so much cleaner on the phone side… otherwise I would have had to have the transceiver AND a IOIO or similar hanging from the phone.
      With bluetooth, you can use ANY phone, no need to attach any extra hardware…

      Dan

  42. Pingback: Ikea Night Light controlled by an Android Phone - Free Plans, Hacks, Howto's and other DIY stuff - Free Plans Online

  43. Pingback: Ikea Night Light controlled by an Android Phone - Amazing! Gadgets

  44. Pingback: Ikea Night Light controlled by an Android Phone | Price Gadget Reviews

  45. Pingback: Ikea Night Light controlled by an Android Phone » Geko Geek

  46. Pingback: Ikea Night Light controlled by an Android Phone - Hacked Gadgets – DIY Tech Blog

  47. hans harkens says:

    Hi,

    brilliant idea.
    can you upload the connection scheme/wiring diagram so we can rebuild it on our own??

    Thanks a lot! One again: Brilliant idea! I really like it!

    best regards!

    • trandi says:

      Hi Hans,

      Thank you.
      For the schematic, I don’t have any formal drawing (and I won’t open the lamp again now to draw it retrospectively… lol… :) ) but it’s quite simple.
      You can find some more details in one of the other comments…

  48. Andreas Meerkamp says:

    Thats a really nice project. While I know my way around electronics a little bit I never created an app on Android. Can you outline how to create an app using your source code? Do I need additional tools like an SDK?
    Kind regards from Munich, Germany, Andreas
    PS I came to your site by the http://www.heise.de portal

    • trandi says:

      Yes you don need an Android SDK.
      Here is really not the place to go into details about how to get started with Android, if you do a search on the internet you’ll find literally thousands of articles about it…

      This is the official site: http://developer.android.com/index.html so it might be a good start.

      Once you have the SDK, here on the post you have all the necessary Java code (split into separate files) and configurations (xml files).
      The only one little missing thing is the icon :)

      Dan

      • Andreas Meerkamp says:

        I started working with the SDK and Eclipse. I created an AVD for Android 2.2 , API level 8. In Eclipse i have the 4 java files under src/trandi.spokalightbluetooth, the 4 XMLs under res/layout and strings.xml under res/values. I got errors for BTDeviceListActivity.java and MainActivity.java and exclamation marks for the other 2 java files as well as for custom_title.xml, main.xml and strings.xml.

        Could it be that with Android 2.2 I use the wrong version and this beeing the reason for the errors?

        Andreas

      • trandi says:

        “exclamation marks for the other 2 java files” is not that helpful… :) You need to be more specific, about the EXACT ERROR message

        dan

      • Andreas Meerkamp says:

        I checked further with eclipse and found a lot of syntax errors in the BTDeviceListActivity.java like:

        public void onItemClick(AdapterView av, View v, int arg2, long arg3) {

        Is there still something wrong with the code as mentioned by others?

        Another question is which Android Version do you use.

        Thanks for you help, Andreas

        PS If its easy for you to mail the code it would be nice to receive it at

      • trandi says:

        Done, I’ve just sent you a zip with all the code.
        Actually it has one little improvement: a button that if toggled will automatically generate random movements of the “joystick” and hence random colours, every 1 seconds.

        Dan

        P.S. also, it’s never a great idea to post your e-mail in clear on a blog as you’ll receive plenty of spam from web crawlers… hence I’ve deleted it from your comment…

      • hanswurst says:

        I have the same problems in Eclipse. I use like Andras Eclipse with the Android 2.2 SDK

      • hanswurst says:

        ahh I forgot. I just have errors in BTDeviceListActivity.java
        I had to import the R.java. in the MainActivity.java, because it was not imported there, but needed. My first Error in BTDeviceListActivity.java is in line 68 following.
        ” private OnItemClickListener _deviceClickListener = new OnItemClickListener() {
        @Override”

      • trandi says:

        Same thing, check your e-mail.

        HOWEVER, let me make this clear: the code bits in this post are “snippets” and are not meant to be directly “compilable” ! It’s meant to give you an idea (and serve as a reminder for myself :) ) but it does require some work to put together.
        The post itself is NOT an instructable where you follow some exact steps and voila you get the final result… it’s provided as information only, and you are supposed to provide some extra work to have a functioning prototype…

        In any case I’m curious to see if you guys can get other working Spokas, do let me know or send me photos as soon as you get to something !!!
        Dan

      • hanswurst says:

        Can’t you upload the compiled App or the whole sourcecode? I guess you already compiled an app. Would be really nice!

      • Nils Quester says:

        I have great problems to compile the Android sources!
        Please send it to me :-)

        Thank you!

      • trandi says:

        Hi Nils,

        As already mentioned, the code provided here is for indication only, it’s not meant to be directly downloaded and work out of the box…

        I’ve sent you a zip with all the code that works for me, but again it’s up to you to compile and fine tune it.

        Dan

      • Andreas Meerkamp says:

        I’m a totally Android beginner but after I got the source and upgraded JAVA to 1.6 (or 6) I have been able to create an app and put it on my phone (HTC Desire with 2.2) even if the app is 2.3. With JAVA 1.5 (or 5) the code won’t work.
        I go step by step and the first step is completed. I just ordered a bluetooth transceiver from EBAy but that will take some time.
        Kind regards from Germany
        Andreas

  49. Frank says:

    Nice!
    I want to build the same as well.
    As mentioned above the code (C, java and xml) is truncated. Can you provide the same?

    Thanks, frank

    • trandi says:

      Fixed BOTH the XML and the AVR code in the post.
      Have a look and let me know if you’re still seeing anything wrong.

      dan

      • Frank says:

        AVR code is compiling now!
        Initially was confused, because my evaluation board hooks up a 90s2312 which is different.
        Have to get a ATTiny2312 immedeately.

        Thanks

      • trandi says:

        Glad to hear it compiles :) Yes, it probably won’t do what expected if you have a different MCU on the board… :)

        Dan

      • Andreas Meerkamp says:

        I installed AVR Studio 4.19 and AVR Toolchain 3.2.3. During build I got 4 warnings:

        c:\program files (x86)\atmel\avr tools\avr toolchain\bin\../lib/gcc/avr/4.5.1/../../../../avr/include/util/delay.h:89:3: warning: #warning “F_CPU not defined for ”
        ../uart.c: In function ‘uart_getChar':
        ../uart.c:28:2: warning: implicit declaration of function ‘uart_putChar’
        ../uart.c: At top level:
        ../uart.c:32:6: warning: conflicting types for ‘uart_putChar’
        ../uart.c:28:2: note: previous implicit declaration of ‘uart_putChar’ was here
        ../uart.c:69:0: warning: “F_CPU” redefined
        c:\program files (x86)\atmel\avr tools\avr toolchain\bin\../lib/gcc/avr/4.5.1/../../../../avr/include/util/delay.h:90:0: note: this is the location of the previous definition

        Is this a serious problem?
        @Frank: Do you get warnings as well? Which version did you use from AVR?

        Regards, Andreas

      • trandi says:

        It should be ok. To be honest I don’t pay much attention to the warnings myself, so can’t be sure.

        I’ve sent you my .hex compiled binary, in case it helps.

        As mentioned, this is experimental code and I’m working on other projects right now(the kind that need their nappies changed every 3 hours and scream when hungry… :) ) so don’t have time to look into the details…

        It’s also an occasion for you to learn more and debug the code… lol :)

        Dan

      • Frank says:

        I finished “my” Spoka :-)
        I replaced the PCB and put the AVR with SMD5050 RGB LEDs on the same, these give nice colored bright light. Iapplied some “improvements” to the code w.r.t. deep sleep mode and the color change (dynamic) mode.

        Thanks again for the nice Idea. I can share my mods and some snaps if you are interested.

        Frank

      • trandi says:

        Wow, great ! Of course, I would love to see some pictures / video of your device !

        dan

    • yang says:

      I want,too.
      As mentioned above the code (C, java and xml) is truncated. Can you provide the same?

      Thanks, yang.
      E-mail: sdestiny20a@gmail.com

  50. Pingback: A little Android automation turns SPOKA to SPOOKTACULAR! » IKEA FANS | THE IKEA Fan Community

  51. ulrich ranke says:

    please contact us: http://www.ceratecaudio.de, ulrich.ranke@ceratecaudio

    • Thomas says:

      Trandi did a great job here I hope you are not aiming for a patent lawsuit.

      • trandi says:

        :) NOT AT ALL !

        I didn’t even think about it until you mentioned it… it’s crazy what a conflicting society we live in today… !

  52. Martin says:

    Great project! Very cool!

    Could you please post a list of components needed excapt the BT tranciever? (processor…?) Thank you very much!

    • trandi says:

      Hi Martin,

      It’s really just the bluetooth board and the ATtiny2313 ! And some wires :)
      The great thing about the Spoka is that it’s simple but has everything needed to drive the LEDs included… obviously given that the original MCU had to do the same thing.

      You’ll have to keep in mind that it runs on 3 AAA rechargeable batteries, so you need an MCU that runs at 3.3 volts (as opposed to 5V).
      It’s also good to have and MCU with hardware UART if you want to avoid having to implement it in software, as per my post…

      Dan

      • Martin says:

        Sorry for asking again: I’d like to start with programming hardware, and this project seems to be the best start for it.

        OK, I got it, ATtiny2313 and BT board. Has the ATtiny2313 20 pins? What do you mean with you last sentence?

      • trandi says:

        Yes, but it’s not relevant. The last sentence means that some microcontrollers (which is a CPU + hardware peripherals) have a hardware that does UART and others not, and you have to use a library…

        But if you’re just starting with microcontrollers, there are plenty of better places on the net to learn about, I can’t go into that many details in a comment… (and I’m not an expert myself anyhow…)

        dan

  53. Kai says:

    Perfect! – I’ve dissasembled the Spoka some month ago and just finished an microcontroller project ( Competition Pro – C64 Joystick on iPad for C64 Emulator ) and the Spoka has to be controlled with an iPhone soon! :-)

    • trandi says:

      Wow great !
      I’m really NOT a “iFan”, but it can only be nice to see the Spoka work with an iPhone too… should be quite straight forward, it’s just a matter of programming the bluetooth …

      Do let me know or send some pictures / info once it’s done !

      Dan

  54. joe says:

    Hi Dan,
    this is really a cool project!

    As I am an android beginner I wanted to download your java and xml files and understand them. Unfortunately the syntaxhighligher used on this page scamles the xml files, e.g. some tags and beginings of lines sterting with “<keyword … " are partly removed. In particular the main.xml file and device-list.xml are not usable anymore. Is there an alternative way to post the code? e.g. a link to a zip file which includes all source files unencoded?

    • trandi says:

      no worries , check your e-mail I’ve sent you the whole thing.

      Do come back and post your updates if you reproduce this on your own, I’d be curious to see what you obtain…

      dan

    • trandi says:

      I have fixed this on the post too, let me know if you find other issues.

  55. Thomas says:

    Hey Dan,

    congrats to your interesting project! Could you please provide a schematic of the whole circuit? Guessing it from the pictures is not that easy :-) Thanks a lot in advance!

    • trandi says:

      Hi Thomas,

      This is a fair point, I’ve never made a “formal” schematic given that the circuit is fairly simple :
      – connect the 3 LEDs pins to 1 PWM pin of the micro-controller each
      – connect the switch to a digital input pin
      – connect the bluetooth module to the UART pins
      – then all you need is to connect GND and VCC to both the MCU and the bluetooth board and that’s it you’re all settled

      In my pictures you’ll see a 6 pin ISP header connected to the MCU, so that I can re-program it at will, but you don’t technically need that.

      If you want the exact pin numbers, just have a look at the C code running on the ATtiny.

      Hope this helps,
      Dan

  56. Florian says:

    I was thingking about the same project many times.
    I would love to use it as a wakeup light controlled and triggerd from the mobile phone.
    but thats now only a software thing! Thanks for that!
    the next free time i have will go into rebuilding you project.

  57. Jon says:

    Hi! Nice project. Could you say a few words on how you have removed the internals from the silicone skin? Did you use any tools for removing it?

    • trandi says:

      Hi Jon,

      You’re right it wasn’t that easy… I haven’t used any tools, just strong fingers ! Same for putting back.
      Once you remove the plastic shell from the silicone skin however, you need some sort of screwdriver to open it up. It’s actually glued together so you have to be careful to slowly break the glue without damaging the plastic shell too much…

      However you don’t need to glue it back again, as the 2 halfs fit well together and the silicone skin holds everything nicely in place.

      Hope this helps,
      Dan

    • trandi says:

      yeah, didn’t even know about this German site before, but it seems to be quite a big thing… some of the guys there argue however (they certainly have a point) that it’s not recycling enough stuff to be worth mentioning… lol…:)

      • Daniel says:

        just found your project on heise.de. it is awesome!
        heise is a great publisher in germany which publishes the c’t-magazine. it is one of the best known technical magazine in germany. (print run about 400.000 pieces)
        congratulations for that! ;)

      • trandi says:

        Wow, thanks ! I’m indeed impressed myself !
        Now I’m waiting for feedback regarding how to imporove / evolve the project…

      • hoschi says:

        I came here from heise.de as well … nice project!
        My idea would be to make the lamp glow/flash to music played on the android device or on a pc.

        red = bass
        green=mids
        blue=highs

        the colour / intensity should “mix” to the tone of the music and flash a little to the beat :)

        … dont know if that is even possible but I think it would look nice though.

        regards, hoschi

      • trandi says:

        Indeed I thought about this too, as well as flashing different colours when you receive an e-mail or a tweet, etc. …

        The flashing the lights bit is easy, what I don’t know is how hard it would be intercept and filter the music on the phone to extract some statistics in real time ?

        The other thing is, would the music be played by the phone itself, or would it use its microphone to “listen” to it and de-compose its frequencies…?

        Anyhow great idea, I don’t know if I’ll explore it as there’s far too much software programming involved, and I do enough of this during my day job…lol…:)

        Dan

  58. Dorle says:

    What a great idea! I think with more of those you can create a light orchestra show :)

  59. Pingback: Controlling a cute Ikea night light with Android on the cheap « Hackaday « Cool Internet Projects

  60. HSPalm says:

    Hey, I’ve used the same bluetooth module here http://microhobby.net/26-06-2011/projects/bluetooth-robot-bt-bot/ but I did not do any android programming though, I used an app which is very configurable, great for testing and prototyping before developing your own. So this is my suggestion to you, download that same app (QkCtrl) and use the terminal window to see if you receive garbage there also, then you know if it’s your app or not.,

    • trandi says:

      Thank you Henrik, this is a good suggestion!
      I’ve already tested the connection from the PC and it ALL works ok, but haven’t tried from the Android phone with a different bluetooth application…

      Will try this when I have some time.

      thanks,
      dan

  61. Pingback: Controlling a cute Ikea night light with Android on the cheap | My Blog

  62. ScottInNH says:

    Re changing the pinout of your favorite 8-pin micro: just use 2 sockets, one atop of the other. On the upper one, clip all the legs and solder in short lengths of solid core wire. Plug the proto wire into the lower socket. Apply liberal hot glue inside the sandwich and the sides, and you would have the adapter you wanted.

    Not that you need it here – the 2313 is more capable and better solution. But you presented the above pinout differences as a barrier.. there’s always a way. :-)

    Nice hack btw, especially with the cheapie bluetooth success.

    • trandi says:

      That’s actually a surprisingly simple and effective idea !
      Will definitely apply it in the future…

      Thanks,
      dan

  63. Pingback: Controlling a cute Ikea night light with Android on the cheap » Geko Geek

  64. Pingback: Controlling a cute Ikea night light with Android on the cheap | ro-Stire

  65. Pingback: Controlling a cute Ikea night light with Android on the cheap - Hack a Day

  66. Ytai says:

    I saw the bat-light :)
    I don’t think your problems are Bluetooth-related. From my experience, once the socket is open, the connection is reliable. You’re doing some weird stuff in your reader thread. First, I don’t understand why you’re creating the BufferedIS inside the loop and not once before the look. Second, you’re segmenting your incoming data to Strings according to how many bytes a give read() operation has returned, which is absolutely arbitrary (i.e. if the remote end sends a 3-byte message, it might be received as a 1-byte read followed by a 2-byte read). So your “contains()” call is not likely to succeed in many cases. It is also very wasteful, since you’re checking the last string over and over…
    What you really want here just is a read with timeout. For that purpose, you don’t need a thread and you don’t need a buffered IS. You can have a simple loop checking IS.available() and the elapsed time and sleeping shortly. A more elegant and slightly more complicated solution can involve a thread that notify()’s the main thread and an additional timeout timer that notify()’s is too. The main thread can simply wait() until either event occurs. But don’t go there unless you’re super-comfortable with multithreading.
    Last, why do you need the ack? Why don’t you just do unidirectional communications?

    Anyhow, very neat project. Hope it’ll help your baby sleep and get you some rest :)
    Congrats!!!

    • trandi says:

      Hi Ytai,

      Thank you for your prompt reply / help !

      I totally agree about the “weird” stuff in the reader thread… it’s actually shameful for a Java programmer by day… :) However, here’s the story and why I ended up with that abomination:

      – I started with no separate thread, exactly as you suggested, just reading the bytes, but I got crappy characters

      – then after looking at the example that comes with the Android SDK I introduced the reading thread. Simply storing the bytes received in a thread safe list. To no avail !

      – then after looking on the Internet for a while, I discovered that I had the exact same problem as THIS guy ! For obscure and un-comprehensible reasons, if I was creating the String in the reader thread and store than rather than the bytes, the text would be OK !!!

      – however, again as you mention, the generated / stored strings are completely arbitrary, so I then attempted to put them together in one big string / array so that I can do “contains” on that. And then, again completely inexplicably, I stopped working, I get garbled text !!!

      – this started to become quite frustrating, so I gave up and currently (as you can see all that code is commented out) I’m doing only unidirectional communication.

      The perfectionist in me would have wanted the ACK, for the same reasons why I put that basic check-sum byte… to be able to re-send if necessary, and to have a more “robust” code (euphemism for “over engineering” :) )

      So, when you say “From my experience, once the socket is open, the connection is reliable.” were you using SPP over bluetooth, or something else ? If yes, then it might be some obscure problem with the native bluetooth driver on my specific phone model ??? (I know it’s always easy to blame others rather than your own code… :) )

      Thank you again ! Dan

      • Ytai says:

        An easy way to check if your phone’s Bluetooth is reliable is to check whether it works OK with the IOIO over Bluetooth. It indeed uses SPP as well.
        Either way, as I said, I think the ACKs are completely redundant. The BT stack already takes care of providing a reliable connection, so you can assume that either your message got through, or the connection dropped and you’ll get an exception.

  67. sebastian says:

    very nice,good job :d

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 129 other followers

%d bloggers like this: