Home
Weekly Assignments
Final Project

14. Interface and application programming

Chrome App

Some time ago, during the Input devices assignment, I decided to learn how to read the information the microcontroller sends through the serial port. So instead of using a third party serial console, such us the Arduino Serial Monitor, I started coding a Google Chrome App. Doing this I could read the information received through the serial port and send information to the AVR using a simple HTML form.

If the 4 first bytes received are [1, 2, 3, 4], the fifth byte will be read as the number of bytes to be read from then on that contains the sent value. Once the information has been reconstructed, it will be displayed in the progress bar right below the dropdown list.

base

As seen, the serial port to talk to can be selected in the dropdownlist of the upper left corner.

The received information will be displayed in the textbox below the dropdownlist. Therefore, if the received information is a char sequence it will be readable using this console.

In order to install this plugin, you have to download the file chrome_app.crx using google chrome. Moreover, you can import the sources from the Extension menu.

In3 Android app

Going a step further, I decided to create an Android native app to control de Incubator. At this moment I don't have all the final peripherals configured, but I know, for sure, that the temperature must be controlled.

Simulating the temperature

At first I designed the "incubator console" for Android using the "control panel shield".

This Android Application has built in a Bluetooth controller (Enable/disable bluetooth, connect to paired devices and pair with new devices). Once the connection is stablished with the HC-05 module, it will start receiving floats. Depending on the "Header information" that float will represent the measured temperature or the target temperature. This information will be potted on a graph in real time.

While the measurements are plotted, the user of the App can change the target temperature using the slicer.

This shield does not have a thermistor, so I started simulating the temperature with an algorithm that is running in the AVR. The current "temperature" and the target temperature will be displayed in the LCD display and the led will change its color if the temperature is below, equals or above the target temperature.

  1. /********************************************************
  2. *
  3. * bluetooth.fabduino.c
  4. *
  5. * 9600 baud FTDI interface
  6. *
  7. * Alejandro Escario Méndez
  8. * 29/04/2015
  9. *
  10. * MIT license
  11. *********************************************************/
  12.  
  13. #include <avr/io.h>
  14. #include <util/delay.h>
  15. #include <stdio.h>
  16. #include <stdlib.h>
  17. #include <avr/pgmspace.h>
  18. #include <avr/interrupt.h>
  19.  
  20. #include "../lib/definitions.h"
  21. #include "../lib/serial.h"
  22.  
  23. #define output(directions,pin) (directions |= pin) // set port direction for output
  24. #define input(directions,pin) (directions &= (~pin)) // set port direction for input
  25. #define set(port,pin) (port |= pin) // set port pin
  26. #define clear(port,pin) (port &= (~pin)) // clear port pin
  27. #define pin_test(pins,pin) (pins & pin) // test for port pin
  28. #define bit_test(byte,bit) (byte & (1 << bit)) // test for bit set
  29.  
  30. #define RGB_PORT PORTB
  31. #define RGB_DIRECTION DDRB
  32. #define RGB_PIN PINB
  33. #define RGB_RED PB1
  34. #define RGB_GREEN PB0
  35. #define RGB_BLUE PB2
  36.  
  37. #define BUZZER_PORT PORTD
  38. #define BUZZER_DIRECTION DDRD
  39. #define BUZZER_PIN PIND
  40. #define BUZZER_I PD7
  41.  
  42. #define BUTTON_PORT PORTD
  43. #define BUTTON_DIRECTION DDRD
  44. #define BUTTON_PIN PIND
  45. #define BUTTON_I PD2
  46.  
  47. #define lcd_delay() _delay_ms(10) // delay between commands
  48. #define strobe_delay() _delay_us(1) // delay for strobe
  49. #define LCD_PORT PORTC
  50. #define LCD_DIRECTION DDRC
  51. #define LCD_DB7 (1 << PC0)
  52. #define LCD_DB6 (1 << PC1)
  53. #define LCD_DB5 (1 << PC2)
  54. #define LCD_DB4 (1 << PC3)
  55. #define LCD_E (1 << PC4)
  56. #define LCD_RS (1 << PC5)
  57.  
  58. #define JOY_X 7
  59. #define JOY_Y 6
  60.  
  61. int x = 0, y = 0, target_temp = 3650;
  62. char buff[6];
  63.  
  64. void button_init(){
  65. set(BUTTON_PORT, (1 << BUTTON_I)); // turn on pull-up
  66. input(BUTTON_DIRECTION, (1 << BUTTON_I));
  67. }
  68.  
  69. void button_on_click(void (*fn)()){
  70. if (0 == pin_test(BUTTON_PIN, (1 << BUTTON_I))){
  71. (*fn)();
  72. }
  73. }
  74.  
  75. void buzzer_init(){
  76. clear(BUZZER_PORT, (1 << BUZZER_I));
  77. output(BUZZER_DIRECTION, (1 << BUZZER_I));
  78. }
  79.  
  80. void buzzer_beep(){
  81. set(BUZZER_PORT, (1 << BUZZER_I));
  82. _delay_ms(10);
  83. clear(BUZZER_PORT, (1 << BUZZER_I));
  84. }
  85.  
  86. void rgb_init(){
  87. clear(RGB_PORT, (1 << RGB_RED));
  88. output(RGB_DIRECTION, (1 << RGB_RED));
  89. clear(RGB_PORT, (1 << RGB_GREEN));
  90. output(RGB_DIRECTION, (1 << RGB_GREEN));
  91. clear(RGB_PORT, (1 << RGB_BLUE));
  92. output(RGB_DIRECTION, (1 << RGB_BLUE));
  93.  
  94. rgb_off();
  95. }
  96.  
  97. void rgb_green(){
  98. clear(PORTB, (1 << RGB_GREEN));
  99. set(PORTB, (1 << RGB_RED));
  100. set(PORTB, (1 << RGB_BLUE));
  101. }
  102.  
  103. void rgb_off(){
  104. set(PORTB, (1 << RGB_GREEN));
  105. set(PORTB, (1 << RGB_RED));
  106. set(PORTB, (1 << RGB_BLUE));
  107. }
  108.  
  109. void rgb_red(){
  110. clear(PORTB, (1 << RGB_RED));
  111. set(PORTB, (1 << RGB_GREEN));
  112. set(PORTB, (1 << RGB_BLUE));
  113. }
  114.  
  115. void rgb_blue(){
  116. set(PORTB, (1 << RGB_RED));
  117. set(PORTB, (1 << RGB_GREEN));
  118. clear(PORTB, (1 << RGB_BLUE));
  119. }
  120.  
  121. void rgb_yellow(){
  122. clear(PORTB, (1 << RGB_GREEN));
  123. clear(PORTB, (1 << RGB_RED));
  124. set(PORTB, (1 << RGB_BLUE));
  125. }
  126.  
  127. void rgb_white(){
  128. clear(PORTB, (1 << RGB_GREEN));
  129. clear(PORTB, (1 << RGB_RED));
  130. clear(PORTB, (1 << RGB_BLUE));
  131. }
  132.  
  133. void joystick_init(){
  134. ADCSRA = (1 << ADEN) // enable
  135. | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); // prescaler /128
  136. }
  137.  
  138. void joystick_change_port(volatile uint8_t pc){
  139. ADMUX = (0 << REFS1) | (0 << REFS0) // VCC ref
  140. | (0 << ADLAR) // right adjust for 10bit precision
  141. | pc;
  142. }
  143.  
  144. int joystick_read_x(){
  145. joystick_change_port(JOY_X);
  146. return read_adc();
  147. }
  148.  
  149. int joystick_read_y(){
  150. joystick_change_port(JOY_Y);
  151. return read_adc();
  152. }
  153.  
  154. int read_adc(){
  155. ADCSRA |= (1 << ADSC); // conversion init
  156. while (ADCSRA & (1 << ADSC)); // wait for completion
  157. return ADC; // return value
  158. }
  159.  
  160. //
  161. // lcd_putchar
  162. // put character in lcdbyte
  163. //
  164. void lcd_putchar(char lcdbyte) {
  165. //
  166. // set RS for data
  167. //
  168. set(LCD_PORT, LCD_RS);
  169. //
  170. // output high nibble
  171. //
  172. if bit_test(lcdbyte, 7)
  173. set(LCD_PORT, LCD_DB7);
  174. else
  175. clear(LCD_PORT, LCD_DB7);
  176. if bit_test(lcdbyte, 6)
  177. set(LCD_PORT, LCD_DB6);
  178. else
  179. clear(LCD_PORT, LCD_DB6);
  180. if bit_test(lcdbyte, 5)
  181. set(LCD_PORT, LCD_DB5);
  182. else
  183. clear(LCD_PORT, LCD_DB5);
  184. if bit_test(lcdbyte, 4)
  185. set(LCD_PORT, LCD_DB4);
  186. else
  187. clear(LCD_PORT, LCD_DB4);
  188. //
  189. // strobe E
  190. //
  191. strobe_delay();
  192. set(LCD_PORT, LCD_E);
  193. strobe_delay();
  194. clear(LCD_PORT, LCD_E);
  195. //
  196. // wait
  197. //
  198. lcd_delay();
  199. //
  200. // output low nibble
  201. //
  202. if bit_test(lcdbyte, 3)
  203. set(LCD_PORT, LCD_DB7);
  204. else
  205. clear(LCD_PORT, LCD_DB7);
  206. if bit_test(lcdbyte, 2)
  207. set(LCD_PORT, LCD_DB6);
  208. else
  209. clear(LCD_PORT, LCD_DB6);
  210. if bit_test(lcdbyte, 1)
  211. set(LCD_PORT, LCD_DB5);
  212. else
  213. clear(LCD_PORT, LCD_DB5);
  214. if bit_test(lcdbyte, 0)
  215. set(LCD_PORT, LCD_DB4);
  216. else
  217. clear(LCD_PORT, LCD_DB4);
  218. //
  219. // strobe E
  220. //
  221. strobe_delay();
  222. set(LCD_PORT, LCD_E);
  223. strobe_delay();
  224. clear(LCD_PORT, LCD_E);
  225. //
  226. // wait and return
  227. //
  228. lcd_delay();
  229. }
  230. //
  231. // lcd_putcmd
  232. // put command in lcdbyte
  233. //
  234. void lcd_putcmd(char lcdbyte) {
  235. //
  236. // clear RS for command
  237. //
  238. clear(LCD_PORT, LCD_RS);
  239. //
  240. // output command bits
  241. //
  242. PORTC = lcdbyte;
  243. //
  244. // strobe E
  245. //
  246. strobe_delay();
  247. set(LCD_PORT, LCD_E);
  248. strobe_delay();
  249. clear(LCD_PORT, LCD_E);
  250. //
  251. // wait and return
  252. //
  253. lcd_delay();
  254. }
  255. //
  256. // lcd_putstring
  257. // put a null-terminated string in flash
  258. //
  259. void lcd_putstring(char* message) {
  260. static uint8_t i;
  261. static char chr;
  262. i = 0;
  263. while (1) {
  264. chr = message[i];
  265. if (chr == 0)
  266. return;
  267. lcd_putchar(chr);
  268. ++i;
  269. }
  270. }
  271.  
  272. void lcd_putline(char* message, int line){
  273. if(line == 1){
  274. lcd_putcmd(0);
  275. lcd_putcmd(LCD_DB5);
  276. }else if(line == 2){
  277. lcd_putcmd(LCD_DB7+LCD_DB6);
  278. lcd_putcmd(0);
  279. }
  280. lcd_putstring(message);
  281. }
  282.  
  283. void lcd_clear(){
  284. lcd_putcmd(0);
  285. lcd_putcmd(LCD_DB4);
  286. }
  287.  
  288. void lcd_cursor_off(){
  289. lcd_putcmd(0);
  290. lcd_putcmd(LCD_DB7+LCD_DB6);
  291. }
  292. //
  293. // lcd_init
  294. // initialize the LCD
  295. //
  296. void lcd_init() {
  297. //
  298. // initialize LCD pins
  299. //
  300. clear(LCD_PORT, LCD_DB7);
  301. output(LCD_DIRECTION, LCD_DB7);
  302. clear(LCD_PORT, LCD_DB6);
  303. output(LCD_DIRECTION, LCD_DB6);
  304. clear(LCD_PORT, LCD_DB5);
  305. output(LCD_DIRECTION, LCD_DB5);
  306. clear(LCD_PORT, LCD_DB4);
  307. output(LCD_DIRECTION, LCD_DB4);
  308. clear(LCD_PORT, LCD_E);
  309. output(LCD_DIRECTION, LCD_E);
  310. clear(LCD_PORT, LCD_RS);
  311. output(LCD_DIRECTION, LCD_RS);
  312. //
  313. // power-up delay
  314. //
  315. lcd_delay();
  316. //
  317. // initialization sequence
  318. //
  319. lcd_putcmd(LCD_DB5+LCD_DB4);
  320. lcd_putcmd(LCD_DB5+LCD_DB4);
  321. lcd_putcmd(LCD_DB5+LCD_DB4);
  322. //
  323. // 4-bit interface
  324. //
  325. lcd_putcmd(LCD_DB5);
  326. //
  327. // two lines, 5x7 font
  328. //
  329. lcd_putcmd(LCD_DB5);
  330. lcd_putcmd(LCD_DB7);
  331. //
  332. // display on
  333. //
  334. lcd_putcmd(0);
  335. lcd_putcmd(LCD_DB7+LCD_DB6+LCD_DB5);
  336. //
  337. // entry mode
  338. //
  339. lcd_putcmd(0);
  340. lcd_putcmd(LCD_DB6+LCD_DB5);
  341. }
  342.  
  343. ISR (USART_RX_vect) {
  344. buff[0] = buff[1];
  345. buff[1] = buff[2];
  346. buff[2] = buff[3];
  347. buff[3] = buff[4];
  348. buff[4] = buff[5];
  349. buff[5] = UDR0;
  350. if(buff[0] == 1 && buff[1] == 2 && buff[2] == 3 && buff[3] == 4){
  351. target_temp=buff[4]*100+buff[5];
  352. }
  353. }
  354.  
  355. void send_float(int val){
  356. int integer = val / 100;
  357. int frac = val - integer * 100;
  358. usart_putchar(1);
  359. usart_putchar(2);
  360. usart_putchar(3);
  361. usart_putchar(4);
  362. usart_putchar(integer);
  363. usart_putchar(frac);
  364. integer = target_temp / 100;
  365. frac = target_temp - integer * 100;
  366. usart_putchar(4);
  367. usart_putchar(3);
  368. usart_putchar(2);
  369. usart_putchar(1);
  370. usart_putchar(integer);
  371. usart_putchar(frac);
  372. }
  373.  
  374. int main(void) {
  375. char line[16], num[10];
  376. int x, y, i = 0;
  377. int deg = 3650;
  378.  
  379. buzzer_init();
  380. rgb_init();
  381. lcd_init();
  382. button_init();
  383. joystick_init();
  384. lcd_init();
  385. lcd_clear();
  386. lcd_cursor_off();
  387.  
  388. usart_init(R_UBRR);
  389. sei();
  390.  
  391. while(1){
  392. deg += (rand() % 9) - 4;
  393. if(deg < target_temp){
  394. deg += 2;
  395. rgb_red();
  396. }else if(deg > target_temp){
  397. deg -= 2;
  398. rgb_blue();
  399. }else{
  400. rgb_green();
  401. }
  402. //dtostrf(deg, 1, 2, num);
  403. sprintf(line, "%d/%d", deg, target_temp);
  404. lcd_putline(line, 1);
  405. send_float(deg);
  406. _delay_ms(500);
  407. }
  408. }

Reading an NTC

The Android App is the same than before (I just changed some colors to make it a little bit more attractive). But this time, instead of simulating the temperature, the readings to be plotted are real.

Also, if the target temperature is over the real temperature, the red led of the FabKit will be turned on.

  1. /********************************************************
  2. *
  3. * sensors.fabduino.c
  4. *
  5. * Sensor shield v1
  6. * 9600 baud FTDI interface
  7. *
  8. * Alejandro Escario Méndez
  9. * 14/04/2015
  10. *
  11. * (c) Massachusetts Institute of Technology 2015
  12. * Permission granted for experimental and personal use;
  13. * license for commercial sale available from MIT.
  14. *********************************************************/
  15.  
  16. // std libs
  17. #include <avr/io.h>
  18. #include <avr/interrupt.h>
  19. // common libs
  20. #include "../lib/serial.h"
  21. #include "../lib/definitions.h"
  22. #include "temperature.h"
  23.  
  24. #define LED_DIRECTION DDRB
  25. #define LED_PORT PORTB
  26. #define LED_PIN (1 << PB5)
  27.  
  28. volatile float target_temp = 25;
  29. char buff[6];
  30.  
  31. void send_float(float val){
  32. int integer = val;
  33. int frac = (val - integer) * 100;
  34. usart_putchar(1);
  35. usart_putchar(2);
  36. usart_putchar(3);
  37. usart_putchar(4);
  38. usart_putchar(integer);
  39. usart_putchar(frac);
  40. integer = target_temp;
  41. frac = (target_temp - integer) * 100;
  42. usart_putchar(4);
  43. usart_putchar(3);
  44. usart_putchar(2);
  45. usart_putchar(1);
  46. usart_putchar(integer);
  47. usart_putchar(frac);
  48. }
  49.  
  50. ISR (USART_RX_vect) {
  51. buff[0] = buff[1];
  52. buff[1] = buff[2];
  53. buff[2] = buff[3];
  54. buff[3] = buff[4];
  55. buff[4] = buff[5];
  56. buff[5] = UDR0;
  57. if(buff[0] == 1 && buff[1] == 2 && buff[2] == 3 && buff[3] == 4){
  58. target_temp=buff[4]+buff[5]/100;
  59. }
  60. }
  61.  
  62. int main(void) {
  63. output(LED_DIRECTION, LED_PIN);
  64. temperature_init(
  65. (0 << MUX3) | (1 << MUX2) | (0 << MUX1) | (0 << MUX0), // PC4
  66. (0 << MUX3) | (1 << MUX2) | (0 << MUX1) | (1 << MUX0) // PC5
  67. );
  68.  
  69. usart_init(R_UBRR);
  70. sei();
  71. int i = 0;
  72. int filter = 250;
  73. float temp = .0f;
  74. float last_temp = 9999;
  75. while (1) {
  76. ++i;
  77. temp += read_temperature()/filter;
  78. if(i >= filter){
  79. i = 0;
  80. //temp /= filter;
  81. send_float(temp);
  82. last_temp = temp;
  83. temp = 0;
  84. }
  85. if(last_temp < target_temp){
  86. set(LED_PORT,LED_PIN);
  87. } else{
  88. clear(LED_PORT,LED_PIN);
  89. }
  90. }
  91. }

Architecture

Coding an Android (or iOS) App is quite different than doing so for a computer. For example, accessing the bluetooth module is not immediate, you must ask for permission to the owner of the phone.

That permission should be asked in the file "AndroidManifest.xml" (lines 5, 6 & 7)

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="org.escario.in3" >
  4.  
  5. <uses-permission android:name="android.permission.BLUETOOTH"/>
  6. <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
  7. <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
  8.  
  9. <application
  10. android:allowBackup="true"
  11. android:icon="@mipmap/ic_launcher"
  12. android:label="@string/app_name"
  13. android:theme="@style/AppTheme" >
  14. <activity
  15. android:name=".MainActivity"
  16. android:label="@string/app_name"
  17. android:screenOrientation="portrait">
  18. <intent-filter>
  19. <action android:name="android.intent.action.MAIN" />
  20.  
  21. <category android:name="android.intent.category.LAUNCHER" />
  22. </intent-filter>
  23. </activity>
  24. </application>
  25.  
  26. </manifest>

This application consists of one activity that holds two fragments:

Main Activity Fragment

This fragments allows to the user to enable/disable the bluetooth device and connect with new "in range" devices or already paired devices.

In order to do that, a Fragment and its layout must be created. In the MainActivityFragment.java file, the logic of this "window" will be programmed.

  1. package org.escario.in3.fragments;
  2.  
  3. import android.app.Fragment;
  4. import android.bluetooth.BluetoothAdapter;
  5. import android.bluetooth.BluetoothDevice;
  6. import android.bluetooth.BluetoothSocket;
  7. import android.content.BroadcastReceiver;
  8. import android.content.Context;
  9. import android.content.Intent;
  10. import android.content.IntentFilter;
  11. import android.os.Bundle;
  12. import android.util.Log;
  13. import android.view.LayoutInflater;
  14. import android.view.View;
  15. import android.view.ViewGroup;
  16. import android.widget.BaseAdapter;
  17. import android.widget.ImageButton;
  18. import android.widget.ListView;
  19. import android.widget.TabHost;
  20. import android.widget.TextView;
  21. import android.widget.Toast;
  22. import android.widget.ToggleButton;
  23.  
  24. import org.escario.in3.R;
  25.  
  26. import java.io.IOException;
  27. import java.lang.reflect.InvocationTargetException;
  28. import java.lang.reflect.Method;
  29. import java.util.ArrayList;
  30. import java.util.List;
  31. import java.util.Set;
  32.  
  33. public class MainActivityFragment extends Fragment {
  34.  
  35. private final static String TAB_PAIRED = "paired";
  36. private final static String TAB_DISCOVER = "discover";
  37.  
  38. private Set<BluetoothDevice> pairedDevices;
  39. private List<BluetoothDevice> discoveredDevices;
  40.  
  41. private BluetoothAdapter bluetoothAdapter;
  42.  
  43. private View view;
  44. private ToggleButton btnBluettothEnabled;
  45. private TabHost tbhConnect;
  46. private ListView lstPairedDevices, lstDiscoverDevices;
  47.  
  48. public MainActivityFragment() {
  49. }
  50.  
  51. @Override
  52. public View onCreateView(LayoutInflater inflater, ViewGroup container,
  53. Bundle savedInstanceState) {
  54. view = inflater.inflate(R.layout.fragment_main, container, false);
  55.  
  56. bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
  57.  
  58. btnBluettothEnabled = (ToggleButton) view.findViewById(R.id.btnBluetoothEnabled);
  59. btnBluettothEnabled.setTextOff(getString(R.string.bluetooth) + " " + getString(R.string.off));
  60. btnBluettothEnabled.setTextOn(getString(R.string.bluetooth) + " " + getString(R.string.on));
  61. btnBluettothEnabled.setOnClickListener(toggleBluetooth);
  62.  
  63. tbhConnect =(TabHost)view.findViewById(R.id.tbhConnect);
  64. tbhConnect.setOnTabChangedListener(new TabHost.OnTabChangeListener() {
  65. @Override
  66. public void onTabChanged(String tabId) {
  67. switch (tabId){
  68. case TAB_PAIRED:
  69. break;
  70. case TAB_DISCOVER:
  71. startDiscoverTask();
  72. break;
  73. }
  74. }
  75. });
  76. tbhConnect.setup();
  77.  
  78. TabHost.TabSpec spec=tbhConnect.newTabSpec(TAB_PAIRED);
  79. spec.setContent(R.id.lstPairedDevices);
  80. spec.setIndicator(getString(R.string.pairedDevices));
  81. tbhConnect.addTab(spec);
  82.  
  83. spec=tbhConnect.newTabSpec(TAB_DISCOVER);
  84. spec.setContent(R.id.lstDiscoverDevices);
  85. spec.setIndicator(getString(R.string.discoverDevices));
  86. tbhConnect.addTab(spec);
  87.  
  88. lstPairedDevices = (ListView) view.findViewById(R.id.lstPairedDevices);
  89.  
  90. lstDiscoverDevices = (ListView) view.findViewById(R.id.lstDiscoverDevices);
  91.  
  92. return view;
  93. }
  94.  
  95. @Override
  96. public void onStart(){
  97. super.onStart();
  98.  
  99. bluetoothStatusChanged();
  100.  
  101. //Register the BroadcastReceiver
  102. IntentFilter filter = new IntentFilter();
  103. filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); // bluetooth enabled/disabled
  104. filter.addAction(BluetoothDevice.ACTION_FOUND);
  105. getActivity().registerReceiver(bluetoothReceiver, filter);
  106. }
  107.  
  108. @Override
  109. public void onStop(){
  110. super.onStop();
  111. stopDiscoverTask();
  112. getActivity().unregisterReceiver(bluetoothReceiver);
  113. }
  114.  
  115. public void bluetoothStatusChanged(){
  116. btnBluettothEnabled.setChecked(bluetoothAdapter.isEnabled());
  117. if(bluetoothAdapter.isEnabled()){
  118. getPairedDevices();
  119. }
  120. }
  121.  
  122. public void getPairedDevices(){
  123. pairedDevices = bluetoothAdapter.getBondedDevices();
  124.  
  125. PairedDeviceListAdapter adapter = new PairedDeviceListAdapter(pairedDevices);
  126. lstPairedDevices.setAdapter(adapter);
  127. }
  128.  
  129. public void startDiscoverTask(){
  130. if(!bluetoothAdapter.isDiscovering() && discoveredDevices == null){
  131. bluetoothAdapter.startDiscovery();
  132. discoveredDevices = new ArrayList<>();
  133. }
  134. }
  135.  
  136. public void stopDiscoverTask(){
  137. if(bluetoothAdapter.isDiscovering()){
  138. bluetoothAdapter.cancelDiscovery();
  139. }
  140. }
  141.  
  142. public View.OnClickListener toggleBluetooth = new View.OnClickListener(){
  143. @Override
  144. public void onClick(View v) {
  145. if(bluetoothAdapter.isEnabled()){
  146. bluetoothAdapter.disable();
  147. }else{
  148. Intent turnOn = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
  149. startActivityForResult(turnOn, 1);
  150. }
  151. }
  152. };
  153.  
  154. private final BroadcastReceiver bluetoothReceiver = new BroadcastReceiver() {
  155.  
  156. @Override
  157. public void onReceive(Context context, Intent intent) {
  158. switch (intent.getAction()){
  159. case BluetoothAdapter.ACTION_STATE_CHANGED:
  160. bluetoothStatusChanged();
  161. break;
  162. case BluetoothDevice.ACTION_FOUND:
  163. discoveredDevices.add((BluetoothDevice)intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
  164. lstDiscoverDevices.setAdapter(new PairedDeviceListAdapter(discoveredDevices));
  165. default:
  166. Log.e("ERROR", intent.getAction());
  167. }
  168. }
  169. };
  170.  
  171. @Override
  172. public void onActivityResult(int requestCode, int resultCode, Intent data) {
  173. // workaround to avoid wrong status if the user tries to enable the bluetooth from the app
  174. // but it denies the action in the action dialog
  175. bluetoothStatusChanged();
  176. }
  177.  
  178. public class PairedDeviceListAdapter extends BaseAdapter {
  179.  
  180. List<BluetoothDevice> devices;
  181.  
  182. public PairedDeviceListAdapter(List<BluetoothDevice> list){
  183. this.devices = list;
  184. }
  185.  
  186. public PairedDeviceListAdapter(Set<BluetoothDevice> set){
  187. devices = new ArrayList();
  188. devices.addAll(set);
  189. }
  190.  
  191. @Override
  192. public int getCount() {
  193. return devices.size();
  194. }
  195.  
  196. @Override
  197. public Object getItem(int position) {
  198. return devices.get(position);
  199. }
  200.  
  201. @Override
  202. public long getItemId(int position) {
  203. return position;
  204. }
  205.  
  206. @Override
  207. public View getView(int position, View view, final ViewGroup parent) {
  208. if (view == null) {
  209. LayoutInflater inflater = LayoutInflater.from(parent.getContext());
  210. view = inflater.inflate(R.layout.list_item_paired_devices, parent, false);
  211. }
  212.  
  213. final BluetoothDevice device = devices.get(position);
  214.  
  215. TextView txtName = (TextView) view.findViewById(R.id.txtName);
  216. txtName.setText(device.getName());
  217.  
  218. ImageButton imgbtn = (ImageButton) view.findViewById(R.id.btnConnect);
  219. imgbtn.setOnClickListener(new View.OnClickListener() {
  220. @Override
  221. public void onClick(View view) {
  222. try {
  223. BluetoothSocket socket = connect(device);
  224. initIn3ControlPanel(socket);
  225. } catch (IOException e) {
  226. toast("error");
  227. e.printStackTrace();
  228. } catch (InvocationTargetException e) {
  229. toast("error");
  230. e.printStackTrace();
  231. } catch (NoSuchMethodException e) {
  232. toast("error");
  233. e.printStackTrace();
  234. } catch (IllegalAccessException e) {
  235. toast("error");
  236. e.printStackTrace();
  237. }
  238. }
  239. });
  240. return view;
  241. }
  242. }
  243.  
  244. private void initIn3ControlPanel(BluetoothSocket socket) {
  245. In3ControlPanelFragment f = new In3ControlPanelFragment();
  246. f.setSocket(socket);
  247.  
  248. getFragmentManager().beginTransaction()
  249. .replace(R.id.fragment_container, f).addToBackStack(null).commit();
  250. }
  251.  
  252. public BluetoothSocket connect(BluetoothDevice device) throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
  253. BluetoothSocket socket = null;
  254. if(!pairedDevices.contains(device)){
  255. pairDevice(device);
  256. }
  257. socket = getSocket(device);
  258. socket.connect();
  259. return socket;
  260. }
  261.  
  262.  
  263. private void pairDevice(BluetoothDevice device) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
  264. Method method = device.getClass().getMethod("createBond", (Class[]) null);
  265. method.invoke(device, (Object[]) null);
  266. }
  267.  
  268. private BluetoothSocket getSocket(BluetoothDevice device) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
  269. stopDiscoverTask();
  270. Method m=device.getClass().getMethod("createRfcommSocket",new Class<?>[] {Integer.TYPE});
  271. return (BluetoothSocket)m.invoke(device,Integer.valueOf(1));
  272. }
  273.  
  274. private void toast(String msg){
  275. Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
  276. }
  277. }

And in the fragment_main.xml file, the layout is coded.

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:orientation="vertical"
  6. tools:context=".MainActivityFragment">
  7.  
  8. <ToggleButton
  9. android:id="@+id/btnBluetoothEnabled"
  10. android:layout_width="match_parent"
  11. android:layout_height="wrap_content" />
  12.  
  13. <TabHost
  14. android:layout_width="match_parent"
  15. android:layout_height="match_parent"
  16. android:id="@+id/tbhConnect"
  17. android:layout_below="@+id/btnBluetoothEnabled"
  18. android:layout_alignParentLeft="true">
  19.  
  20. <LinearLayout
  21. android:layout_width="match_parent"
  22. android:layout_height="match_parent"
  23. android:orientation="vertical">
  24.  
  25. <TabWidget
  26. android:id="@android:id/tabs"
  27. android:layout_width="match_parent"
  28. android:layout_height="wrap_content"></TabWidget>
  29.  
  30. <FrameLayout
  31. android:id="@android:id/tabcontent"
  32. android:layout_width="match_parent"
  33. android:layout_height="match_parent">
  34.  
  35. <ListView android:id="@+id/lstPairedDevices"
  36. android:layout_width="fill_parent"
  37. android:layout_height="fill_parent"/>
  38. <ListView android:id="@+id/lstDiscoverDevices"
  39. android:layout_width="fill_parent"
  40. android:layout_height="fill_parent"/>
  41. </FrameLayout>
  42. </LinearLayout>
  43. </TabHost>
  44. </LinearLayout>

Once the user connects to a device, the second fragment pops up. It will display a graph that represents, in real time the temperature received by the bluetooth module.

  1. package org.escario.in3.fragments;
  2.  
  3. import android.app.Fragment;
  4. import android.bluetooth.BluetoothSocket;
  5. import android.graphics.Color;
  6. import android.graphics.LinearGradient;
  7. import android.graphics.Paint;
  8. import android.graphics.Point;
  9. import android.graphics.Shader;
  10. import android.os.AsyncTask;
  11. import android.os.Bundle;
  12. import android.view.Display;
  13. import android.view.LayoutInflater;
  14. import android.view.View;
  15. import android.view.ViewGroup;
  16. import android.widget.SeekBar;
  17. import android.widget.TextView;
  18.  
  19. import com.androidplot.xy.LineAndPointFormatter;
  20. import com.androidplot.xy.XYPlot;
  21. import com.androidplot.xy.XYSeries;
  22. import com.androidplot.xy.XYStepMode;
  23.  
  24. import org.escario.in3.R;
  25. import org.escario.in3.model.Temperature;
  26.  
  27. import java.io.IOException;
  28. import java.io.InputStream;
  29. import java.io.OutputStream;
  30. import java.text.DecimalFormat;
  31. import java.text.FieldPosition;
  32. import java.text.Format;
  33. import java.text.ParsePosition;
  34. import java.text.SimpleDateFormat;
  35. import java.util.ArrayList;
  36. import java.util.Date;
  37.  
  38. /**
  39. * Created by alejandro on 7/5/15.
  40. */
  41. public class In3ControlPanelFragment extends Fragment {
  42.  
  43. private static BluetoothSocket socket;
  44. private View view;
  45. private SeekBar skbTemp;
  46. private TextView txtTarget;
  47.  
  48. private XYPlot plot;
  49. ArrayList<Temperature> temp_val, target;
  50. final static int MAX_TEMP = 100;
  51. float target_temp = 36.5f;
  52.  
  53. public In3ControlPanelFragment(){
  54. temp_val = new ArrayList<>();
  55. target = new ArrayList<>();
  56. }
  57.  
  58. public void setSocket(BluetoothSocket socket){
  59. this.socket = socket;
  60. }
  61.  
  62.  
  63. @Override
  64. public View onCreateView(LayoutInflater inflater, ViewGroup container,
  65. Bundle savedInstanceState) {
  66. if(view == null) {
  67. view = inflater.inflate(R.layout.in3_console, container, false);
  68. txtTarget = (TextView) view.findViewById(R.id.txtTarget);
  69. txtTarget.setText(String.valueOf(target_temp));
  70. skbTemp = (SeekBar) view.findViewById(R.id.skbTemp);
  71. skbTemp.setProgress((int)target_temp);
  72. skbTemp.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
  73. @Override
  74. public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
  75. target_temp = progress;
  76. byte[] arr = {1, 2, 3, 4, (byte) target_temp, 0};
  77. txtTarget.setText(String.valueOf(progress));
  78. sendMessage(arr);
  79. }
  80.  
  81. @Override
  82. public void onStartTrackingTouch(SeekBar seekBar) {
  83.  
  84. }
  85.  
  86. @Override
  87. public void onStopTrackingTouch(SeekBar seekBar) {
  88.  
  89. }
  90. });
  91. setUpPlot();
  92. }
  93. return view;
  94. }
  95.  
  96.  
  97. @Override
  98. public void onDestroyView(){
  99. super.onStop();
  100. if(socket.isConnected()) {
  101. try {
  102. socket.close();
  103. } catch (IOException e) {
  104. e.printStackTrace();
  105. }
  106. }
  107. }
  108.  
  109. private void addTargetTemp2Plot(){
  110. SampleDynamicSeries sine1Series = new SampleDynamicSeries(target, "Target");
  111.  
  112. LineAndPointFormatter formatter1 = new LineAndPointFormatter(
  113. Color.rgb(250, 0, 0), // line color
  114. null, // point color
  115. null,
  116. null);
  117. formatter1.getLinePaint().setStrokeJoin(Paint.Join.ROUND);
  118. formatter1.getLinePaint().setStrokeWidth(5);
  119. plot.addSeries(sine1Series,
  120. formatter1);
  121. }
  122.  
  123. private int getWindowHeight(){
  124. Display display = getActivity().getWindowManager().getDefaultDisplay();
  125. Point size = new Point();
  126. display.getSize(size);
  127. return size.y;
  128. }
  129.  
  130. private void addTemp2Plot(){
  131. SampleDynamicSeries sine2Series = new SampleDynamicSeries(temp_val, "Temp");
  132.  
  133. LineAndPointFormatter formatter2 =
  134. new LineAndPointFormatter(Color.rgb(0, 250, 0), null, null, null);
  135. formatter2.getLinePaint().setStrokeWidth(5);
  136. Paint lineFill = new Paint();
  137. lineFill.setAlpha(200);
  138. lineFill.setShader(new LinearGradient(0, 0, 0, getWindowHeight(), Color.WHITE, Color.GREEN, Shader.TileMode.MIRROR));
  139.  
  140. formatter2.setFillPaint(lineFill);
  141. formatter2.getLinePaint().setStrokeJoin(Paint.Join.ROUND);
  142.  
  143. plot.addSeries(sine2Series, formatter2);
  144. }
  145.  
  146. private void setUpPlot() {
  147. // get handles to our View defined in layout.xml:
  148. plot = (XYPlot) view.findViewById(R.id.dynamicXYPlot);
  149.  
  150. // only display whole numbers in domain labels
  151. plot.getGraphWidget().setDomainValueFormat(new DecimalFormat("0"));
  152.  
  153. // getInstance and position datasets:
  154. addTemp2Plot();
  155. addTargetTemp2Plot();
  156.  
  157. // thin out domain tick labels so they dont overlap each other:
  158. plot.setDomainStepMode(XYStepMode.SUBDIVIDE);
  159. plot.setDomainStepValue(5);
  160. plot.setDomainValueFormat(new Format() {
  161. private SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
  162.  
  163. @Override
  164. public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
  165. long timestamp = ((Number) obj).longValue();
  166. Date date = new Date(timestamp);
  167. return dateFormat.format(date, toAppendTo, pos);
  168. }
  169.  
  170. @Override
  171. public Object parseObject(String source, ParsePosition pos) {
  172. return null;
  173.  
  174. }
  175. });
  176.  
  177. plot.setRangeStepMode(XYStepMode.INCREMENT_BY_PIXELS);
  178. plot.setRangeStepValue(100);
  179.  
  180. plot.setRangeValueFormat(new DecimalFormat("##.##"));
  181.  
  182. plot.setBorderStyle(XYPlot.BorderStyle.NONE, null, null);
  183. plot.getGraphWidget().getBackgroundPaint().setColor(Color.TRANSPARENT);
  184. plot.getGraphWidget().getGridBackgroundPaint().setColor(Color.WHITE);
  185. plot.getGraphWidget().getDomainLabelPaint().setColor(Color.BLACK);
  186. plot.getGraphWidget().getRangeLabelPaint().setColor(Color.BLACK);
  187.  
  188. plot.getGraphWidget().getDomainOriginLabelPaint().setColor(Color.BLACK);
  189.  
  190. plot.getLegendWidget().getTextPaint().setColor(Color.BLACK);
  191. plot.getDomainLabelWidget().getLabelPaint().setColor(Color.BLACK);
  192. plot.getRangeLabelWidget().getLabelPaint().setColor(Color.BLACK);
  193. plot.getTitleWidget().getLabelPaint().setColor(Color.BLACK);
  194. }
  195.  
  196. @Override
  197. public void onStart() {
  198. super.onStart();
  199. if(!socket.isConnected()){
  200. try {
  201. socket.connect();
  202. } catch (IOException e) {
  203. e.printStackTrace();
  204. }
  205. }
  206. new ListenThread().execute(socket);
  207. }
  208.  
  209. private class ListenThread extends AsyncTask<BluetoothSocket, Float, Void> {
  210.  
  211. protected Void doInBackground(BluetoothSocket... socket) {
  212. byte[] buffer = new byte[6];
  213. byte[] read = new byte[1];
  214. try{
  215. InputStream is = socket[0].getInputStream();
  216. while(true){
  217. int readBytes = is.read(read);
  218. if(readBytes != -1){
  219. buffer[0] = buffer[1];
  220. buffer[1] = buffer[2];
  221. buffer[2] = buffer[3];
  222. buffer[3] = buffer[4];
  223. buffer[4] = buffer[5];
  224. buffer[5] = read[0];
  225. }
  226. if(buffer[0] == 1 && buffer[1] == 2 && buffer[2] == 3 && buffer[3] == 4) {
  227. Float temp = buffer[4] + (float)buffer[5]/100;
  228. publishProgress(temp);
  229. }else if(buffer[3] == 1 && buffer[2] == 2 && buffer[1] == 3 && buffer[0] == 4){
  230. Float temp = buffer[4] + (float)buffer[5]/100;
  231. target_temp = temp;
  232. skbTemp.setProgress(buffer[4]);
  233. }
  234. }
  235. }catch(IOException e){
  236. e.printStackTrace();
  237. }
  238. return null;
  239. }
  240.  
  241. protected void onProgressUpdate(Float... temp) {
  242. long now = System.currentTimeMillis();
  243. Temperature val = new Temperature(temp[0], now);
  244. temp_val.add(val);
  245. if(temp_val.size() >= MAX_TEMP){
  246. temp_val.remove(0);
  247. }
  248.  
  249. val = new Temperature(target_temp, now);
  250. target.add(val);
  251. if(target.size() >= MAX_TEMP){
  252. target.remove(0);
  253. }
  254.  
  255. plot.redraw();
  256. }
  257. };
  258.  
  259. class SampleDynamicSeries implements XYSeries {
  260. private ArrayList<Temperature> datasource;
  261. private String title;
  262.  
  263. public SampleDynamicSeries(ArrayList<Temperature> datasource, String title) {
  264. this.datasource = datasource;
  265. this.title = title;
  266. }
  267.  
  268. @Override
  269. public String getTitle() {
  270. return title;
  271. }
  272.  
  273. @Override
  274. public int size() {
  275. return datasource.size();
  276. }
  277.  
  278. @Override
  279. public Number getX(int index) {
  280. return datasource.get(index).time;
  281. }
  282.  
  283. @Override
  284. public Number getY(int index) {
  285. return datasource.get(index).temperature;
  286. }
  287. }
  288.  
  289.  
  290.  
  291. private void sendMessage(byte[] msg){
  292. OutputStream os;
  293. try {
  294. os = socket.getOutputStream();
  295. os.write(msg);
  296. }catch(IOException e){
  297. e.printStackTrace();
  298. }
  299. }
  300. }
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical" android:layout_width="match_parent"
  4. android:layout_height="match_parent">
  5.  
  6. <RelativeLayout
  7. android:orientation="horizontal"
  8. android:layout_width="match_parent"
  9. android:layout_height="wrap_content">
  10. <TextView
  11. android:layout_width="30dp"
  12. android:layout_height="wrap_content"
  13. android:text="100"
  14. android:id="@+id/txtTarget"
  15. android:layout_alignParentRight="true"
  16. android:layout_centerVertical="true"/>
  17. <SeekBar
  18. android:layout_width="match_parent"
  19. android:layout_height="wrap_content"
  20. android:max="100"
  21. android:id="@+id/skbTemp"
  22. android:progress="5"
  23. android:layout_gravity="center_horizontal"
  24. android:layout_toLeftOf="@id/txtTarget"/>
  25. </RelativeLayout>
  26.  
  27. <com.androidplot.xy.XYPlot
  28. android:id="@+id/dynamicXYPlot"
  29. android:layout_width="fill_parent"
  30. android:layout_height="fill_parent"
  31. androidplot.renderMode="use_background_thread"
  32. androidPlot.title="In3 temperature"
  33. androidPlot.domainLabel="Time"
  34. androidPlot.rangeLabel="Temperature"
  35. androidPlot.titleWidget.labelPaint.textSize="@dimen/title_font_size"
  36. androidPlot.domainLabelWidget.labelPaint.textSize="@dimen/domain_label_font_size"
  37. androidPlot.rangeLabelWidget.labelPaint.textSize="@dimen/range_label_font_size"
  38. androidPlot.graphWidget.marginTop="20dp"
  39. androidPlot.graphWidget.marginLeft="15dp"
  40. androidPlot.graphWidget.marginBottom="25dp"
  41. androidPlot.graphWidget.marginRight="10dp"
  42. androidPlot.graphWidget.rangeLabelPaint.textSize="@dimen/range_tick_label_font_size"
  43. androidPlot.graphWidget.rangeOriginLabelPaint.textSize="@dimen/range_tick_label_font_size"
  44. androidPlot.graphWidget.domainLabelPaint.textSize="@dimen/domain_tick_label_font_size"
  45. androidPlot.graphWidget.domainOriginLabelPaint.textSize="@dimen/domain_tick_label_font_size"
  46. androidPlot.legendWidget.textPaint.textSize="@dimen/legend_text_font_size"
  47. androidPlot.legendWidget.iconSizeMetrics.heightMetric.value="15dp"
  48. androidPlot.legendWidget.iconSizeMetrics.widthMetric.value="15dp"
  49. androidPlot.legendWidget.heightMetric.value="25dp"
  50. androidPlot.legendWidget.positionMetrics.anchor="right_bottom"/>
  51. </LinearLayout>

To plot the received information, I am using a library called AndroidPlot

Bluetooth console

Finally, in order to create a Universal tool for bluetooth debugging, I started coding a bluetooth console app. This application displays (currently) the received bytes (in hexadecimal and as a character) in a scrollable textview.

Each line contains 8 bytes of information. On the left we can find the hexadecimal representation of the received information and, on the right, the characters. If the received byte does not have visual representation, a '.' will be displayed instead.

Right at the bottom of the screen a small form can be found. It can be used to send information through the serial port (Bluetooth). It will send the written string unless it is has the following structure:

0x 01 ab 43

That is to say: it must start with 0x and continue with pairs of bytes written on its hexadecimal form. Between each byte or the 0x flag and the first byte MUST be a space (' ') character.

base

As seen above, before sending the command 0x 01 02 03 04 ab 00 the target temperature was 0A. After sending the command (once it is interpreted by the AVR), that value changes to AB.

Files

Chrome App

In3 Android App

Bluetooth Console

The icon and the name of the app is the same than in the In3 Android App. I will continue developing this tool and publish it on GitHub + google play.

AVR files

Control panel Shield

Sensor board

Libraries