SPOKA Night Light controlled from and Android Phone
January 13, 2012 134 Comments
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:
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):
- slowly change between the 3 available colours (blue, red and orange) while dimming the intensity
- 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
Thank you Ikea for having easily hackable, through hole boards !
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.
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.
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:
- if the first character is a ‘#’ then you simply provide the intensity for each colour
- 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); } }
3. Connect the bluetooth transceiver
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>
Pingback: Game on IKEA Obergransad LED display | Robotics / Electronics / Physical Computing
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
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?
Hello, I ‘m a student.Can you give me more information? I want to try. THX ~
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?
Thanks, no worries, the C code is all in the post, after the “And here’s the C code.”
Hope this helps,
Dan
Thank you very much.
Pingback: TM1638 Display Driver for Stellaris Launchpad « Robotics / Electronics / Physical Computing
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
i just need the ac adapter for mine! I bought it from a thrift store not knowing an adapter was needed!
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?
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 !
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….
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
thnx for the reply…how do make a bluetooth tag using a module..that can b paired up with a smartphone???
Is it possible to post the hex file? I’m having a difficulty compiling.
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
Done.
Can’t wait to see some pictures/details of your project ! Keep me updated…
dan
Pingback: SPOKA Night Light & Android – RETURNS « Robotics / Electronics / Physical Computing
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
Done, sent to your e-mail.
I would love to see some pictures of your project…
Dan
Hi trandi,
can you also send me the android code for the bluetooth connection? Thanks in advance.
done, check your e-mail… hope it will help you !
Do let me know if you create anything interesting…
Dan
could I also have a copy of the android code. Would like to break it down and see how it works. thanks in advance
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
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.
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
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
excellent,
What license are this code under?
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
thanks for your reply.
I need the bluetooth part of android program.
thank you so much
It’s in there ! Right at the beginning of the big chunk of pasted code there is something starting with “BTDeviceListActivity.java”…
dan
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:)
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
Pingback: IKEA Nightlight Hacked for Android-based Control | The Copy Paste Blog
Pingback: 床頭燈變迷幻燈,全靠 Android 改做 | UNWIRE.HK 流動科技生活
Pingback: IKEA Nightlight Hacked for Android-based Control : LinuxBuzz.net
Pingback: IKEA Nightlight Hacked for Android-based Control | CisforComputers
Pingback: IKEA Nightlight Hacked for Android-based Control | iPhone 2 die 4
Pingback: DIY 用手機控制燈光不是夢? App-Goog-les
Pingback: Android también puede gobernar lámparas: ¡Aquí la prueba en vídeo! | TecnoBlog
Pingback: Android también puede gobernar lámparas: ¡Aquí la prueba en vídeo! | Vida-gamer.com
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
Pingback: Lámparas de Ikea podrán controlarse con Android | Universo Digital Noticias
Pingback: Android también puede gobernar lámparas: ¡Aquí la prueba en vídeo! - Wayerless
Pingback: DIY用手機控制燈光不是夢? | TechOrange
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?
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
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?
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
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 🙂 )
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
Pingback: IKEA Spoka Lamp Controlled By Android Smartphone (Video) - IT Lounge
Pingback: IKEA Spoka Lamp Controlled By Android Smartphone (Video) - Mobile Magazine
Pingback: Controla una lampara de Ikea con tu dispositivo Android [VIDEO]
Pingback: CoCoLink - Tu Web de Electronica!..
Pingback: Controla una lámpara de Ikea con tu móvil Android - La Isla Buscada
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,
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
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 🙂
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.
Pingback: Spoka light controlled from Android phone « Ikea « Cool Internet Projects
Pingback: Hack an ikea night light to be controlled from your phone - BuildLounge » BuildLounge
Pingback: Ikea Night Light controlled by an Android Phone « « SNAP-DIYSNAP-DIY
Pingback: Ikea Night Light controlled by an Android Phone | iGadgetView.com
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.
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 https://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
Pingback: Ikea Night Light controlled by an Android Phone - Free Plans, Hacks, Howto's and other DIY stuff - Free Plans Online
Pingback: Ikea Night Light controlled by an Android Phone - Amazing! Gadgets
Pingback: Ikea Night Light controlled by an Android Phone | Price Gadget Reviews
Pingback: Ikea Night Light controlled by an Android Phone » Geko Geek
Pingback: Ikea Night Light controlled by an Android Phone - Hacked Gadgets – DIY Tech Blog
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!
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…
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
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
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
“exclamation marks for the other 2 java files” is not that helpful… 🙂 You need to be more specific, about the EXACT ERROR message
dan
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
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…
I have the same problems in Eclipse. I use like Andras Eclipse with the Android 2.2 SDK
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”
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
Can’t you upload the compiled App or the whole sourcecode? I guess you already compiled an app. Would be really nice!
I have great problems to compile the Android sources!
Please send it to me 🙂
Thank you!
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
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
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
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
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
Glad to hear it compiles 🙂 Yes, it probably won’t do what expected if you have a different MCU on the board… 🙂
Dan
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
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
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
Wow, great ! Of course, I would love to see some pictures / video of your device !
dan
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
Pingback: A little Android automation turns SPOKA to SPOOKTACULAR! » IKEA FANS | THE IKEA Fan Community
please contact us: http://www.ceratecaudio.de, ulrich.ranke@ceratecaudio
Trandi did a great job here I hope you are not aiming for a patent lawsuit.
🙂 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… !
Great project! Very cool!
Could you please post a list of components needed excapt the BT tranciever? (processor…?) Thank you very much!
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
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?
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
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! 🙂
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
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?
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
I have fixed this on the post too, let me know if you find other issues.
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!
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
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.
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?
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
mentioned on heise.de
http://www.heise.de/newsticker/meldung/LED-Lampe-von-Ikea-aufgemotzt-1418509.html
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…:)
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! 😉
Wow, thanks ! I’m indeed impressed myself !
Now I’m waiting for feedback regarding how to imporove / evolve the project…
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
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
What a great idea! I think with more of those you can create a light orchestra show 🙂
Pingback: Controlling a cute Ikea night light with Android on the cheap « Hackaday « Cool Internet Projects
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.,
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
Pingback: Controlling a cute Ikea night light with Android on the cheap | My Blog
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.
That’s actually a surprisingly simple and effective idea !
Will definitely apply it in the future…
Thanks,
dan
Pingback: Controlling a cute Ikea night light with Android on the cheap » Geko Geek
Pingback: Controlling a cute Ikea night light with Android on the cheap | ro-Stire
Pingback: Controlling a cute Ikea night light with Android on the cheap - Hack a Day
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!!!
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
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.
very nice,good job :d