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.
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.
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.
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.
- /********************************************************
- *
- * bluetooth.fabduino.c
- *
- * 9600 baud FTDI interface
- *
- * Alejandro Escario Méndez
- * 29/04/2015
- *
- * MIT license
- *********************************************************/
- #include <avr/io.h>
- #include <util/delay.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <avr/pgmspace.h>
- #include <avr/interrupt.h>
- #include "../lib/definitions.h"
- #include "../lib/serial.h"
- #define output(directions,pin) (directions |= pin) // set port direction for output
- #define input(directions,pin) (directions &= (~pin)) // set port direction for input
- #define set(port,pin) (port |= pin) // set port pin
- #define clear(port,pin) (port &= (~pin)) // clear port pin
- #define pin_test(pins,pin) (pins & pin) // test for port pin
- #define bit_test(byte,bit) (byte & (1 << bit)) // test for bit set
- #define RGB_PORT PORTB
- #define RGB_DIRECTION DDRB
- #define RGB_PIN PINB
- #define RGB_RED PB1
- #define RGB_GREEN PB0
- #define RGB_BLUE PB2
- #define BUZZER_PORT PORTD
- #define BUZZER_DIRECTION DDRD
- #define BUZZER_PIN PIND
- #define BUZZER_I PD7
- #define BUTTON_PORT PORTD
- #define BUTTON_DIRECTION DDRD
- #define BUTTON_PIN PIND
- #define BUTTON_I PD2
- #define lcd_delay() _delay_ms(10) // delay between commands
- #define strobe_delay() _delay_us(1) // delay for strobe
- #define LCD_PORT PORTC
- #define LCD_DIRECTION DDRC
- #define LCD_DB7 (1 << PC0)
- #define LCD_DB6 (1 << PC1)
- #define LCD_DB5 (1 << PC2)
- #define LCD_DB4 (1 << PC3)
- #define LCD_E (1 << PC4)
- #define LCD_RS (1 << PC5)
- #define JOY_X 7
- #define JOY_Y 6
- int x = 0, y = 0, target_temp = 3650;
- char buff[6];
- void button_init(){
- set(BUTTON_PORT, (1 << BUTTON_I)); // turn on pull-up
- input(BUTTON_DIRECTION, (1 << BUTTON_I));
- }
- void button_on_click(void (*fn)()){
- if (0 == pin_test(BUTTON_PIN, (1 << BUTTON_I))){
- (*fn)();
- }
- }
- void buzzer_init(){
- clear(BUZZER_PORT, (1 << BUZZER_I));
- output(BUZZER_DIRECTION, (1 << BUZZER_I));
- }
- void buzzer_beep(){
- set(BUZZER_PORT, (1 << BUZZER_I));
- _delay_ms(10);
- clear(BUZZER_PORT, (1 << BUZZER_I));
- }
- void rgb_init(){
- clear(RGB_PORT, (1 << RGB_RED));
- output(RGB_DIRECTION, (1 << RGB_RED));
- clear(RGB_PORT, (1 << RGB_GREEN));
- output(RGB_DIRECTION, (1 << RGB_GREEN));
- clear(RGB_PORT, (1 << RGB_BLUE));
- output(RGB_DIRECTION, (1 << RGB_BLUE));
- rgb_off();
- }
- void rgb_green(){
- clear(PORTB, (1 << RGB_GREEN));
- set(PORTB, (1 << RGB_RED));
- set(PORTB, (1 << RGB_BLUE));
- }
- void rgb_off(){
- set(PORTB, (1 << RGB_GREEN));
- set(PORTB, (1 << RGB_RED));
- set(PORTB, (1 << RGB_BLUE));
- }
- void rgb_red(){
- clear(PORTB, (1 << RGB_RED));
- set(PORTB, (1 << RGB_GREEN));
- set(PORTB, (1 << RGB_BLUE));
- }
- void rgb_blue(){
- set(PORTB, (1 << RGB_RED));
- set(PORTB, (1 << RGB_GREEN));
- clear(PORTB, (1 << RGB_BLUE));
- }
- void rgb_yellow(){
- clear(PORTB, (1 << RGB_GREEN));
- clear(PORTB, (1 << RGB_RED));
- set(PORTB, (1 << RGB_BLUE));
- }
- void rgb_white(){
- clear(PORTB, (1 << RGB_GREEN));
- clear(PORTB, (1 << RGB_RED));
- clear(PORTB, (1 << RGB_BLUE));
- }
- void joystick_init(){
- ADCSRA = (1 << ADEN) // enable
- | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); // prescaler /128
- }
- void joystick_change_port(volatile uint8_t pc){
- ADMUX = (0 << REFS1) | (0 << REFS0) // VCC ref
- | (0 << ADLAR) // right adjust for 10bit precision
- | pc;
- }
- int joystick_read_x(){
- joystick_change_port(JOY_X);
- return read_adc();
- }
- int joystick_read_y(){
- joystick_change_port(JOY_Y);
- return read_adc();
- }
- int read_adc(){
- ADCSRA |= (1 << ADSC); // conversion init
- while (ADCSRA & (1 << ADSC)); // wait for completion
- return ADC; // return value
- }
- //
- // lcd_putchar
- // put character in lcdbyte
- //
- void lcd_putchar(char lcdbyte) {
- //
- // set RS for data
- //
- set(LCD_PORT, LCD_RS);
- //
- // output high nibble
- //
- if bit_test(lcdbyte, 7)
- set(LCD_PORT, LCD_DB7);
- else
- clear(LCD_PORT, LCD_DB7);
- if bit_test(lcdbyte, 6)
- set(LCD_PORT, LCD_DB6);
- else
- clear(LCD_PORT, LCD_DB6);
- if bit_test(lcdbyte, 5)
- set(LCD_PORT, LCD_DB5);
- else
- clear(LCD_PORT, LCD_DB5);
- if bit_test(lcdbyte, 4)
- set(LCD_PORT, LCD_DB4);
- else
- clear(LCD_PORT, LCD_DB4);
- //
- // strobe E
- //
- strobe_delay();
- set(LCD_PORT, LCD_E);
- strobe_delay();
- clear(LCD_PORT, LCD_E);
- //
- // wait
- //
- lcd_delay();
- //
- // output low nibble
- //
- if bit_test(lcdbyte, 3)
- set(LCD_PORT, LCD_DB7);
- else
- clear(LCD_PORT, LCD_DB7);
- if bit_test(lcdbyte, 2)
- set(LCD_PORT, LCD_DB6);
- else
- clear(LCD_PORT, LCD_DB6);
- if bit_test(lcdbyte, 1)
- set(LCD_PORT, LCD_DB5);
- else
- clear(LCD_PORT, LCD_DB5);
- if bit_test(lcdbyte, 0)
- set(LCD_PORT, LCD_DB4);
- else
- clear(LCD_PORT, LCD_DB4);
- //
- // strobe E
- //
- strobe_delay();
- set(LCD_PORT, LCD_E);
- strobe_delay();
- clear(LCD_PORT, LCD_E);
- //
- // wait and return
- //
- lcd_delay();
- }
- //
- // lcd_putcmd
- // put command in lcdbyte
- //
- void lcd_putcmd(char lcdbyte) {
- //
- // clear RS for command
- //
- clear(LCD_PORT, LCD_RS);
- //
- // output command bits
- //
- PORTC = lcdbyte;
- //
- // strobe E
- //
- strobe_delay();
- set(LCD_PORT, LCD_E);
- strobe_delay();
- clear(LCD_PORT, LCD_E);
- //
- // wait and return
- //
- lcd_delay();
- }
- //
- // lcd_putstring
- // put a null-terminated string in flash
- //
- void lcd_putstring(char* message) {
- static uint8_t i;
- static char chr;
- i = 0;
- while (1) {
- chr = message[i];
- if (chr == 0)
- return;
- lcd_putchar(chr);
- ++i;
- }
- }
- void lcd_putline(char* message, int line){
- if(line == 1){
- lcd_putcmd(0);
- lcd_putcmd(LCD_DB5);
- }else if(line == 2){
- lcd_putcmd(LCD_DB7+LCD_DB6);
- lcd_putcmd(0);
- }
- lcd_putstring(message);
- }
- void lcd_clear(){
- lcd_putcmd(0);
- lcd_putcmd(LCD_DB4);
- }
- void lcd_cursor_off(){
- lcd_putcmd(0);
- lcd_putcmd(LCD_DB7+LCD_DB6);
- }
- //
- // lcd_init
- // initialize the LCD
- //
- void lcd_init() {
- //
- // initialize LCD pins
- //
- clear(LCD_PORT, LCD_DB7);
- output(LCD_DIRECTION, LCD_DB7);
- clear(LCD_PORT, LCD_DB6);
- output(LCD_DIRECTION, LCD_DB6);
- clear(LCD_PORT, LCD_DB5);
- output(LCD_DIRECTION, LCD_DB5);
- clear(LCD_PORT, LCD_DB4);
- output(LCD_DIRECTION, LCD_DB4);
- clear(LCD_PORT, LCD_E);
- output(LCD_DIRECTION, LCD_E);
- clear(LCD_PORT, LCD_RS);
- output(LCD_DIRECTION, LCD_RS);
- //
- // power-up delay
- //
- lcd_delay();
- //
- // initialization sequence
- //
- lcd_putcmd(LCD_DB5+LCD_DB4);
- lcd_putcmd(LCD_DB5+LCD_DB4);
- lcd_putcmd(LCD_DB5+LCD_DB4);
- //
- // 4-bit interface
- //
- lcd_putcmd(LCD_DB5);
- //
- // two lines, 5x7 font
- //
- lcd_putcmd(LCD_DB5);
- lcd_putcmd(LCD_DB7);
- //
- // display on
- //
- lcd_putcmd(0);
- lcd_putcmd(LCD_DB7+LCD_DB6+LCD_DB5);
- //
- // entry mode
- //
- lcd_putcmd(0);
- lcd_putcmd(LCD_DB6+LCD_DB5);
- }
- ISR (USART_RX_vect) {
- buff[0] = buff[1];
- buff[1] = buff[2];
- buff[2] = buff[3];
- buff[3] = buff[4];
- buff[4] = buff[5];
- buff[5] = UDR0;
- if(buff[0] == 1 && buff[1] == 2 && buff[2] == 3 && buff[3] == 4){
- target_temp=buff[4]*100+buff[5];
- }
- }
- void send_float(int val){
- int integer = val / 100;
- int frac = val - integer * 100;
- usart_putchar(1);
- usart_putchar(2);
- usart_putchar(3);
- usart_putchar(4);
- usart_putchar(integer);
- usart_putchar(frac);
- integer = target_temp / 100;
- frac = target_temp - integer * 100;
- usart_putchar(4);
- usart_putchar(3);
- usart_putchar(2);
- usart_putchar(1);
- usart_putchar(integer);
- usart_putchar(frac);
- }
- int main(void) {
- char line[16], num[10];
- int x, y, i = 0;
- int deg = 3650;
- buzzer_init();
- rgb_init();
- lcd_init();
- button_init();
- joystick_init();
- lcd_init();
- lcd_clear();
- lcd_cursor_off();
- usart_init(R_UBRR);
- sei();
- while(1){
- deg += (rand() % 9) - 4;
- if(deg < target_temp){
- deg += 2;
- rgb_red();
- }else if(deg > target_temp){
- deg -= 2;
- rgb_blue();
- }else{
- rgb_green();
- }
- //dtostrf(deg, 1, 2, num);
- sprintf(line, "%d/%d", deg, target_temp);
- lcd_putline(line, 1);
- send_float(deg);
- _delay_ms(500);
- }
- }
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.
- /********************************************************
- *
- * sensors.fabduino.c
- *
- * Sensor shield v1
- * 9600 baud FTDI interface
- *
- * Alejandro Escario Méndez
- * 14/04/2015
- *
- * (c) Massachusetts Institute of Technology 2015
- * Permission granted for experimental and personal use;
- * license for commercial sale available from MIT.
- *********************************************************/
- // std libs
- #include <avr/io.h>
- #include <avr/interrupt.h>
- // common libs
- #include "../lib/serial.h"
- #include "../lib/definitions.h"
- #include "temperature.h"
- #define LED_DIRECTION DDRB
- #define LED_PORT PORTB
- #define LED_PIN (1 << PB5)
- volatile float target_temp = 25;
- char buff[6];
- void send_float(float val){
- int integer = val;
- int frac = (val - integer) * 100;
- usart_putchar(1);
- usart_putchar(2);
- usart_putchar(3);
- usart_putchar(4);
- usart_putchar(integer);
- usart_putchar(frac);
- integer = target_temp;
- frac = (target_temp - integer) * 100;
- usart_putchar(4);
- usart_putchar(3);
- usart_putchar(2);
- usart_putchar(1);
- usart_putchar(integer);
- usart_putchar(frac);
- }
- ISR (USART_RX_vect) {
- buff[0] = buff[1];
- buff[1] = buff[2];
- buff[2] = buff[3];
- buff[3] = buff[4];
- buff[4] = buff[5];
- buff[5] = UDR0;
- if(buff[0] == 1 && buff[1] == 2 && buff[2] == 3 && buff[3] == 4){
- target_temp=buff[4]+buff[5]/100;
- }
- }
- int main(void) {
- output(LED_DIRECTION, LED_PIN);
- temperature_init(
- (0 << MUX3) | (1 << MUX2) | (0 << MUX1) | (0 << MUX0), // PC4
- (0 << MUX3) | (1 << MUX2) | (0 << MUX1) | (1 << MUX0) // PC5
- );
- usart_init(R_UBRR);
- sei();
- int i = 0;
- int filter = 250;
- float temp = .0f;
- float last_temp = 9999;
- while (1) {
- ++i;
- temp += read_temperature()/filter;
- if(i >= filter){
- i = 0;
- //temp /= filter;
- send_float(temp);
- last_temp = temp;
- temp = 0;
- }
- if(last_temp < target_temp){
- set(LED_PORT,LED_PIN);
- } else{
- clear(LED_PORT,LED_PIN);
- }
- }
- }
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)
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="org.escario.in3" >
- <uses-permission android:name="android.permission.BLUETOOTH"/>
- <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
- <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
- <application
- android:allowBackup="true"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:theme="@style/AppTheme" >
- <activity
- android:name=".MainActivity"
- android:label="@string/app_name"
- android:screenOrientation="portrait">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
- </manifest>
This application consists of one activity that holds two fragments:
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.
- package org.escario.in3.fragments;
- import android.app.Fragment;
- import android.bluetooth.BluetoothAdapter;
- import android.bluetooth.BluetoothDevice;
- import android.bluetooth.BluetoothSocket;
- 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.LayoutInflater;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.BaseAdapter;
- import android.widget.ImageButton;
- import android.widget.ListView;
- import android.widget.TabHost;
- import android.widget.TextView;
- import android.widget.Toast;
- import android.widget.ToggleButton;
- import org.escario.in3.R;
- import java.io.IOException;
- import java.lang.reflect.InvocationTargetException;
- import java.lang.reflect.Method;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Set;
- public class MainActivityFragment extends Fragment {
- private final static String TAB_PAIRED = "paired";
- private final static String TAB_DISCOVER = "discover";
- private Set<BluetoothDevice> pairedDevices;
- private List<BluetoothDevice> discoveredDevices;
- private BluetoothAdapter bluetoothAdapter;
- private View view;
- private ToggleButton btnBluettothEnabled;
- private TabHost tbhConnect;
- private ListView lstPairedDevices, lstDiscoverDevices;
- public MainActivityFragment() {
- }
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- view = inflater.inflate(R.layout.fragment_main, container, false);
- bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
- btnBluettothEnabled = (ToggleButton) view.findViewById(R.id.btnBluetoothEnabled);
- btnBluettothEnabled.setTextOff(getString(R.string.bluetooth) + " " + getString(R.string.off));
- btnBluettothEnabled.setTextOn(getString(R.string.bluetooth) + " " + getString(R.string.on));
- btnBluettothEnabled.setOnClickListener(toggleBluetooth);
- tbhConnect =(TabHost)view.findViewById(R.id.tbhConnect);
- tbhConnect.setOnTabChangedListener(new TabHost.OnTabChangeListener() {
- @Override
- public void onTabChanged(String tabId) {
- switch (tabId){
- case TAB_PAIRED:
- break;
- case TAB_DISCOVER:
- startDiscoverTask();
- break;
- }
- }
- });
- tbhConnect.setup();
- TabHost.TabSpec spec=tbhConnect.newTabSpec(TAB_PAIRED);
- spec.setContent(R.id.lstPairedDevices);
- spec.setIndicator(getString(R.string.pairedDevices));
- tbhConnect.addTab(spec);
- spec=tbhConnect.newTabSpec(TAB_DISCOVER);
- spec.setContent(R.id.lstDiscoverDevices);
- spec.setIndicator(getString(R.string.discoverDevices));
- tbhConnect.addTab(spec);
- lstPairedDevices = (ListView) view.findViewById(R.id.lstPairedDevices);
- lstDiscoverDevices = (ListView) view.findViewById(R.id.lstDiscoverDevices);
- return view;
- }
- @Override
- public void onStart(){
- super.onStart();
- bluetoothStatusChanged();
- //Register the BroadcastReceiver
- IntentFilter filter = new IntentFilter();
- filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); // bluetooth enabled/disabled
- filter.addAction(BluetoothDevice.ACTION_FOUND);
- getActivity().registerReceiver(bluetoothReceiver, filter);
- }
- @Override
- public void onStop(){
- super.onStop();
- stopDiscoverTask();
- getActivity().unregisterReceiver(bluetoothReceiver);
- }
- public void bluetoothStatusChanged(){
- btnBluettothEnabled.setChecked(bluetoothAdapter.isEnabled());
- if(bluetoothAdapter.isEnabled()){
- getPairedDevices();
- }
- }
- public void getPairedDevices(){
- pairedDevices = bluetoothAdapter.getBondedDevices();
- PairedDeviceListAdapter adapter = new PairedDeviceListAdapter(pairedDevices);
- lstPairedDevices.setAdapter(adapter);
- }
- public void startDiscoverTask(){
- if(!bluetoothAdapter.isDiscovering() && discoveredDevices == null){
- bluetoothAdapter.startDiscovery();
- discoveredDevices = new ArrayList<>();
- }
- }
- public void stopDiscoverTask(){
- if(bluetoothAdapter.isDiscovering()){
- bluetoothAdapter.cancelDiscovery();
- }
- }
- public View.OnClickListener toggleBluetooth = new View.OnClickListener(){
- @Override
- public void onClick(View v) {
- if(bluetoothAdapter.isEnabled()){
- bluetoothAdapter.disable();
- }else{
- Intent turnOn = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
- startActivityForResult(turnOn, 1);
- }
- }
- };
- private final BroadcastReceiver bluetoothReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- switch (intent.getAction()){
- case BluetoothAdapter.ACTION_STATE_CHANGED:
- bluetoothStatusChanged();
- break;
- case BluetoothDevice.ACTION_FOUND:
- discoveredDevices.add((BluetoothDevice)intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
- lstDiscoverDevices.setAdapter(new PairedDeviceListAdapter(discoveredDevices));
- default:
- Log.e("ERROR", intent.getAction());
- }
- }
- };
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- // workaround to avoid wrong status if the user tries to enable the bluetooth from the app
- // but it denies the action in the action dialog
- bluetoothStatusChanged();
- }
- public class PairedDeviceListAdapter extends BaseAdapter {
- List<BluetoothDevice> devices;
- public PairedDeviceListAdapter(List<BluetoothDevice> list){
- this.devices = list;
- }
- public PairedDeviceListAdapter(Set<BluetoothDevice> set){
- devices = new ArrayList();
- devices.addAll(set);
- }
- @Override
- public int getCount() {
- return devices.size();
- }
- @Override
- public Object getItem(int position) {
- return devices.get(position);
- }
- @Override
- public long getItemId(int position) {
- return position;
- }
- @Override
- public View getView(int position, View view, final ViewGroup parent) {
- if (view == null) {
- LayoutInflater inflater = LayoutInflater.from(parent.getContext());
- view = inflater.inflate(R.layout.list_item_paired_devices, parent, false);
- }
- final BluetoothDevice device = devices.get(position);
- TextView txtName = (TextView) view.findViewById(R.id.txtName);
- txtName.setText(device.getName());
- ImageButton imgbtn = (ImageButton) view.findViewById(R.id.btnConnect);
- imgbtn.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- try {
- BluetoothSocket socket = connect(device);
- initIn3ControlPanel(socket);
- } catch (IOException e) {
- toast("error");
- e.printStackTrace();
- } catch (InvocationTargetException e) {
- toast("error");
- e.printStackTrace();
- } catch (NoSuchMethodException e) {
- toast("error");
- e.printStackTrace();
- } catch (IllegalAccessException e) {
- toast("error");
- e.printStackTrace();
- }
- }
- });
- return view;
- }
- }
- private void initIn3ControlPanel(BluetoothSocket socket) {
- In3ControlPanelFragment f = new In3ControlPanelFragment();
- f.setSocket(socket);
- getFragmentManager().beginTransaction()
- .replace(R.id.fragment_container, f).addToBackStack(null).commit();
- }
- public BluetoothSocket connect(BluetoothDevice device) throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
- BluetoothSocket socket = null;
- if(!pairedDevices.contains(device)){
- pairDevice(device);
- }
- socket = getSocket(device);
- socket.connect();
- return socket;
- }
- private void pairDevice(BluetoothDevice device) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
- Method method = device.getClass().getMethod("createBond", (Class[]) null);
- method.invoke(device, (Object[]) null);
- }
- private BluetoothSocket getSocket(BluetoothDevice device) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
- stopDiscoverTask();
- Method m=device.getClass().getMethod("createRfcommSocket",new Class<?>[] {Integer.TYPE});
- return (BluetoothSocket)m.invoke(device,Integer.valueOf(1));
- }
- private void toast(String msg){
- Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
- }
- }
And in the fragment_main.xml file, the layout is coded.
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- tools:context=".MainActivityFragment">
- <ToggleButton
- android:id="@+id/btnBluetoothEnabled"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
- <TabHost
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:id="@+id/tbhConnect"
- android:layout_below="@+id/btnBluetoothEnabled"
- android:layout_alignParentLeft="true">
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
- <TabWidget
- android:id="@android:id/tabs"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"></TabWidget>
- <FrameLayout
- android:id="@android:id/tabcontent"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- <ListView android:id="@+id/lstPairedDevices"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"/>
- <ListView android:id="@+id/lstDiscoverDevices"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"/>
- </FrameLayout>
- </LinearLayout>
- </TabHost>
- </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.
- package org.escario.in3.fragments;
- import android.app.Fragment;
- import android.bluetooth.BluetoothSocket;
- import android.graphics.Color;
- import android.graphics.LinearGradient;
- import android.graphics.Paint;
- import android.graphics.Point;
- import android.graphics.Shader;
- import android.os.AsyncTask;
- import android.os.Bundle;
- import android.view.Display;
- import android.view.LayoutInflater;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.SeekBar;
- import android.widget.TextView;
- import com.androidplot.xy.LineAndPointFormatter;
- import com.androidplot.xy.XYPlot;
- import com.androidplot.xy.XYSeries;
- import com.androidplot.xy.XYStepMode;
- import org.escario.in3.R;
- import org.escario.in3.model.Temperature;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
- import java.text.DecimalFormat;
- import java.text.FieldPosition;
- import java.text.Format;
- import java.text.ParsePosition;
- import java.text.SimpleDateFormat;
- import java.util.ArrayList;
- import java.util.Date;
- /**
- * Created by alejandro on 7/5/15.
- */
- public class In3ControlPanelFragment extends Fragment {
- private static BluetoothSocket socket;
- private View view;
- private SeekBar skbTemp;
- private TextView txtTarget;
- private XYPlot plot;
- ArrayList<Temperature> temp_val, target;
- final static int MAX_TEMP = 100;
- float target_temp = 36.5f;
- public In3ControlPanelFragment(){
- temp_val = new ArrayList<>();
- target = new ArrayList<>();
- }
- public void setSocket(BluetoothSocket socket){
- this.socket = socket;
- }
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- if(view == null) {
- view = inflater.inflate(R.layout.in3_console, container, false);
- txtTarget = (TextView) view.findViewById(R.id.txtTarget);
- txtTarget.setText(String.valueOf(target_temp));
- skbTemp = (SeekBar) view.findViewById(R.id.skbTemp);
- skbTemp.setProgress((int)target_temp);
- skbTemp.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
- @Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- target_temp = progress;
- byte[] arr = {1, 2, 3, 4, (byte) target_temp, 0};
- txtTarget.setText(String.valueOf(progress));
- sendMessage(arr);
- }
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) {
- }
- @Override
- public void onStopTrackingTouch(SeekBar seekBar) {
- }
- });
- setUpPlot();
- }
- return view;
- }
- @Override
- public void onDestroyView(){
- super.onStop();
- if(socket.isConnected()) {
- try {
- socket.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- private void addTargetTemp2Plot(){
- SampleDynamicSeries sine1Series = new SampleDynamicSeries(target, "Target");
- LineAndPointFormatter formatter1 = new LineAndPointFormatter(
- Color.rgb(250, 0, 0), // line color
- null, // point color
- null,
- null);
- formatter1.getLinePaint().setStrokeJoin(Paint.Join.ROUND);
- formatter1.getLinePaint().setStrokeWidth(5);
- plot.addSeries(sine1Series,
- formatter1);
- }
- private int getWindowHeight(){
- Display display = getActivity().getWindowManager().getDefaultDisplay();
- Point size = new Point();
- display.getSize(size);
- return size.y;
- }
- private void addTemp2Plot(){
- SampleDynamicSeries sine2Series = new SampleDynamicSeries(temp_val, "Temp");
- LineAndPointFormatter formatter2 =
- new LineAndPointFormatter(Color.rgb(0, 250, 0), null, null, null);
- formatter2.getLinePaint().setStrokeWidth(5);
- Paint lineFill = new Paint();
- lineFill.setAlpha(200);
- lineFill.setShader(new LinearGradient(0, 0, 0, getWindowHeight(), Color.WHITE, Color.GREEN, Shader.TileMode.MIRROR));
- formatter2.setFillPaint(lineFill);
- formatter2.getLinePaint().setStrokeJoin(Paint.Join.ROUND);
- plot.addSeries(sine2Series, formatter2);
- }
- private void setUpPlot() {
- // get handles to our View defined in layout.xml:
- plot = (XYPlot) view.findViewById(R.id.dynamicXYPlot);
- // only display whole numbers in domain labels
- plot.getGraphWidget().setDomainValueFormat(new DecimalFormat("0"));
- // getInstance and position datasets:
- addTemp2Plot();
- addTargetTemp2Plot();
- // thin out domain tick labels so they dont overlap each other:
- plot.setDomainStepMode(XYStepMode.SUBDIVIDE);
- plot.setDomainStepValue(5);
- plot.setDomainValueFormat(new Format() {
- private SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
- @Override
- public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
- long timestamp = ((Number) obj).longValue();
- Date date = new Date(timestamp);
- return dateFormat.format(date, toAppendTo, pos);
- }
- @Override
- public Object parseObject(String source, ParsePosition pos) {
- return null;
- }
- });
- plot.setRangeStepMode(XYStepMode.INCREMENT_BY_PIXELS);
- plot.setRangeStepValue(100);
- plot.setRangeValueFormat(new DecimalFormat("##.##"));
- plot.setBorderStyle(XYPlot.BorderStyle.NONE, null, null);
- plot.getGraphWidget().getBackgroundPaint().setColor(Color.TRANSPARENT);
- plot.getGraphWidget().getGridBackgroundPaint().setColor(Color.WHITE);
- plot.getGraphWidget().getDomainLabelPaint().setColor(Color.BLACK);
- plot.getGraphWidget().getRangeLabelPaint().setColor(Color.BLACK);
- plot.getGraphWidget().getDomainOriginLabelPaint().setColor(Color.BLACK);
- plot.getLegendWidget().getTextPaint().setColor(Color.BLACK);
- plot.getDomainLabelWidget().getLabelPaint().setColor(Color.BLACK);
- plot.getRangeLabelWidget().getLabelPaint().setColor(Color.BLACK);
- plot.getTitleWidget().getLabelPaint().setColor(Color.BLACK);
- }
- @Override
- public void onStart() {
- super.onStart();
- if(!socket.isConnected()){
- try {
- socket.connect();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- new ListenThread().execute(socket);
- }
- private class ListenThread extends AsyncTask<BluetoothSocket, Float, Void> {
- protected Void doInBackground(BluetoothSocket... socket) {
- byte[] buffer = new byte[6];
- byte[] read = new byte[1];
- try{
- InputStream is = socket[0].getInputStream();
- while(true){
- int readBytes = is.read(read);
- if(readBytes != -1){
- buffer[0] = buffer[1];
- buffer[1] = buffer[2];
- buffer[2] = buffer[3];
- buffer[3] = buffer[4];
- buffer[4] = buffer[5];
- buffer[5] = read[0];
- }
- if(buffer[0] == 1 && buffer[1] == 2 && buffer[2] == 3 && buffer[3] == 4) {
- Float temp = buffer[4] + (float)buffer[5]/100;
- publishProgress(temp);
- }else if(buffer[3] == 1 && buffer[2] == 2 && buffer[1] == 3 && buffer[0] == 4){
- Float temp = buffer[4] + (float)buffer[5]/100;
- target_temp = temp;
- skbTemp.setProgress(buffer[4]);
- }
- }
- }catch(IOException e){
- e.printStackTrace();
- }
- return null;
- }
- protected void onProgressUpdate(Float... temp) {
- long now = System.currentTimeMillis();
- Temperature val = new Temperature(temp[0], now);
- temp_val.add(val);
- if(temp_val.size() >= MAX_TEMP){
- temp_val.remove(0);
- }
- val = new Temperature(target_temp, now);
- target.add(val);
- if(target.size() >= MAX_TEMP){
- target.remove(0);
- }
- plot.redraw();
- }
- };
- class SampleDynamicSeries implements XYSeries {
- private ArrayList<Temperature> datasource;
- private String title;
- public SampleDynamicSeries(ArrayList<Temperature> datasource, String title) {
- this.datasource = datasource;
- this.title = title;
- }
- @Override
- public String getTitle() {
- return title;
- }
- @Override
- public int size() {
- return datasource.size();
- }
- @Override
- public Number getX(int index) {
- return datasource.get(index).time;
- }
- @Override
- public Number getY(int index) {
- return datasource.get(index).temperature;
- }
- }
- private void sendMessage(byte[] msg){
- OutputStream os;
- try {
- os = socket.getOutputStream();
- os.write(msg);
- }catch(IOException e){
- e.printStackTrace();
- }
- }
- }
- <?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">
- <RelativeLayout
- android:orientation="horizontal"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
- <TextView
- android:layout_width="30dp"
- android:layout_height="wrap_content"
- android:text="100"
- android:id="@+id/txtTarget"
- android:layout_alignParentRight="true"
- android:layout_centerVertical="true"/>
- <SeekBar
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:max="100"
- android:id="@+id/skbTemp"
- android:progress="5"
- android:layout_gravity="center_horizontal"
- android:layout_toLeftOf="@id/txtTarget"/>
- </RelativeLayout>
- <com.androidplot.xy.XYPlot
- android:id="@+id/dynamicXYPlot"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- androidplot.renderMode="use_background_thread"
- androidPlot.title="In3 temperature"
- androidPlot.domainLabel="Time"
- androidPlot.rangeLabel="Temperature"
- androidPlot.titleWidget.labelPaint.textSize="@dimen/title_font_size"
- androidPlot.domainLabelWidget.labelPaint.textSize="@dimen/domain_label_font_size"
- androidPlot.rangeLabelWidget.labelPaint.textSize="@dimen/range_label_font_size"
- androidPlot.graphWidget.marginTop="20dp"
- androidPlot.graphWidget.marginLeft="15dp"
- androidPlot.graphWidget.marginBottom="25dp"
- androidPlot.graphWidget.marginRight="10dp"
- androidPlot.graphWidget.rangeLabelPaint.textSize="@dimen/range_tick_label_font_size"
- androidPlot.graphWidget.rangeOriginLabelPaint.textSize="@dimen/range_tick_label_font_size"
- androidPlot.graphWidget.domainLabelPaint.textSize="@dimen/domain_tick_label_font_size"
- androidPlot.graphWidget.domainOriginLabelPaint.textSize="@dimen/domain_tick_label_font_size"
- androidPlot.legendWidget.textPaint.textSize="@dimen/legend_text_font_size"
- androidPlot.legendWidget.iconSizeMetrics.heightMetric.value="15dp"
- androidPlot.legendWidget.iconSizeMetrics.widthMetric.value="15dp"
- androidPlot.legendWidget.heightMetric.value="25dp"
- androidPlot.legendWidget.positionMetrics.anchor="right_bottom"/>
- </LinearLayout>
To plot the received information, I am using a library called AndroidPlot
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.
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.
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.